Files
leaf/interpreter/src/instruction_cache.rs
T

347 lines
8.7 KiB
Rust
Raw Normal View History

2025-11-11 20:11:55 +01:00
use crate::layout_cache::{GetLayout, LayoutCache};
use fxhash::{FxBuildHasher, FxHashMap};
use leaf_assembly::{
functions::{
Function,
ir::{Cmp, Instruction, InstructionVariant},
},
types::{Type, intrinsics::IntT},
2026-02-26 19:40:27 +01:00
values::{Const, ConstData, Int, Value},
2025-11-11 20:11:55 +01:00
};
use scc::HashMap;
use std::{alloc::Layout, fmt::Debug, ops::Range, sync::Arc};
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub enum OpCode<'l> {
Store_CL_U8(u8, usize),
Store_CL_U16(u16, usize),
Store_CL_U32(u32, usize),
Store_CL_U64(u64, usize),
Add_LL_U32(usize, usize, usize),
Add_LC_U32(usize, u32, usize),
CmpEq_LC_U32(usize, u32, usize),
CmpLt_LC_U32(usize, u32, usize),
CmpGt_LC_U32(usize, u32, usize),
CmpLe_LC_U32(usize, u32, usize),
CmpGe_LC_U32(usize, u32, usize),
Call(&'l Function<'l>, Vec<Range<usize>>, Range<usize>),
Jump(usize),
Branch {
cond: usize,
true_case: usize,
false_case: usize,
},
CopyRange(Range<usize>, Range<usize>),
ReturnRange(Range<usize>),
}
#[non_exhaustive]
pub struct InstructionCacheEntry<'l> {
pub layout: Layout,
pub opcodes: Vec<OpCode<'l>>,
}
impl Debug for InstructionCacheEntry<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct OpCodes<'a, 'b>(&'a [OpCode<'b>]);
impl Debug for OpCodes<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_list();
for op in self.0 {
dbg.entry(&format_args!("{op:?}"));
}
dbg.finish()
}
}
f.debug_struct("InstructionCacheEntry")
.field("layout", &self.layout)
.field("opcodes", &OpCodes(&self.opcodes))
.finish()
}
}
impl<'l> InstructionCacheEntry<'l> {
pub fn new(func: &'l Function<'l>, layouts: &LayoutCache<'l>) -> Self {
let mut opcodes = Vec::new();
let mut layout = Layout::new::<()>();
let mut memory_ranges = FxHashMap::default();
macro_rules! alloc_range {
($ty:expr, $id:expr) => {{
&*memory_ranges.entry($id).or_insert_with(|| {
let l = layouts.get_layout($ty);
let (new_layout, offset) = layout.extend(l.layout).unwrap();
layout = new_layout;
offset..offset + l.layout.size()
})
}};
}
let Some(body) = func.body() else {
unreachable!("InstructionCacheEntry::new called with a function without a body.");
};
let mut backpatch = vec![];
let mut block_starts = Vec::with_capacity(body.blocks.len());
for inst in body.blocks.iter().flat_map(|b| b.instructions()) {
match &inst.variant {
InstructionVariant::StackAlloc(ty) => {
let _ = alloc_range!(*ty, inst.id());
}
_ => {}
}
}
for block in &body.blocks {
block_starts.push(opcodes.len());
for inst in block.instructions() {
match &inst.variant {
InstructionVariant::StackAlloc(_) => {}
InstructionVariant::Store(
Value::Instruction(
t @ Instruction {
variant: InstructionVariant::StackAlloc(ty),
..
},
),
2026-02-26 19:40:27 +01:00
Value::Const(Const {
data: ConstData::Int(Int::U8(v)),
ctx,
}),
2025-11-11 20:11:55 +01:00
) => {
assert_eq!(ctx.u8_t(), *ty);
opcodes.push(OpCode::Store_CL_U8(*v, memory_ranges[&t.id()].start));
}
InstructionVariant::Store(
Value::Instruction(
t @ Instruction {
variant: InstructionVariant::StackAlloc(ty),
..
},
),
2026-02-26 19:40:27 +01:00
Value::Const(Const {
data: ConstData::Int(Int::U16(v)),
ctx,
}),
2025-11-11 20:11:55 +01:00
) => {
assert_eq!(ctx.u16_t(), *ty);
opcodes.push(OpCode::Store_CL_U16(*v, memory_ranges[&t.id()].start));
}
InstructionVariant::Store(
Value::Instruction(
t @ Instruction {
variant: InstructionVariant::StackAlloc(ty),
..
},
),
2026-02-26 19:40:27 +01:00
Value::Const(Const {
data: ConstData::Int(Int::U32(v)),
ctx,
}),
2025-11-11 20:11:55 +01:00
) => {
assert_eq!(ctx.u32_t(), *ty);
opcodes.push(OpCode::Store_CL_U32(*v, memory_ranges[&t.id()].start));
}
InstructionVariant::Store(
Value::Instruction(
t @ Instruction {
variant: InstructionVariant::StackAlloc(ty),
..
},
),
2026-02-26 19:40:27 +01:00
Value::Const(Const {
data: ConstData::Int(Int::U64(v)),
ctx,
}),
2025-11-11 20:11:55 +01:00
) => {
assert_eq!(ctx.u64_t(), *ty);
opcodes.push(OpCode::Store_CL_U64(*v, memory_ranges[&t.id()].start));
}
InstructionVariant::Store(
Value::Instruction(
t @ Instruction {
variant: InstructionVariant::StackAlloc(ty),
..
},
),
Value::Instruction(v),
) => {
let src = alloc_range!(*ty, v.id()).clone();
let dst = alloc_range!(*ty, t.id()).clone();
opcodes.push(OpCode::CopyRange(src, dst));
}
InstructionVariant::Load(Value::Instruction(
t @ Instruction {
variant: InstructionVariant::StackAlloc(ty),
..
},
)) => {
let src = alloc_range!(*ty, t.id()).clone();
let dst = alloc_range!(*ty, inst.id()).clone();
opcodes.push(OpCode::CopyRange(src, dst));
}
InstructionVariant::IAdd(Value::Instruction(a), Value::Instruction(b)) => {
match a.value_ty() {
ty @ Type::Int(IntT {
signed: false,
precision: 32,
..
}) => {
let a = memory_ranges[&a.id()].start;
let b = memory_ranges[&b.id()].start;
let c = alloc_range!(ty, inst.id()).start;
opcodes.push(OpCode::Add_LL_U32(a, b, c));
}
_ => todo!("Unimplemented type `{}`", a.value_ty()),
}
}
InstructionVariant::IAdd(
Value::Instruction(a),
2026-02-26 19:40:27 +01:00
Value::Const(Const {
data: ConstData::Int(Int::U32(b)),
ctx,
}),
2025-11-11 20:11:55 +01:00
) => {
assert!(matches!(
a.value_ty(),
Type::Int(IntT {
signed: false,
precision: 32,
..
})
));
let a = memory_ranges[&a.id()].start;
let c = alloc_range!(ctx.u32_t(), inst.id()).start;
opcodes.push(OpCode::Add_LC_U32(a, *b, c));
}
InstructionVariant::ICmp(
Value::Instruction(a),
2026-02-26 19:40:27 +01:00
Value::Const(Const {
data: ConstData::Int(Int::U32(b)),
ctx,
}),
2025-11-11 20:11:55 +01:00
cmp,
) => {
assert!(matches!(
a.value_ty(),
Type::Int(IntT {
signed: false,
precision: 32,
..
})
));
let a = memory_ranges[&a.id()].start;
let target = alloc_range!(ctx.bool_t(), inst.id()).start;
match *cmp {
Cmp::Eq => opcodes.push(OpCode::CmpEq_LC_U32(a, *b, target)),
Cmp::Lt => opcodes.push(OpCode::CmpLt_LC_U32(a, *b, target)),
Cmp::Gt => opcodes.push(OpCode::CmpGt_LC_U32(a, *b, target)),
Cmp::Le => opcodes.push(OpCode::CmpLe_LC_U32(a, *b, target)),
Cmp::Ge => opcodes.push(OpCode::CmpGe_LC_U32(a, *b, target)),
};
}
InstructionVariant::Call(func, args) => {
let mut ranges = Vec::with_capacity(args.len());
for arg in args {
match arg {
Value::Instruction(i) => {
ranges.push(memory_ranges[&i.id()].clone());
}
_ => todo!("Unimplemented variant `{arg}`."),
}
}
opcodes.push(OpCode::Call(
func,
ranges,
alloc_range!(func.ty.ret_t, inst.id()).clone(),
));
}
InstructionVariant::Jump(target) => {
backpatch.push(opcodes.len());
opcodes.push(OpCode::Jump(target.id as usize));
}
InstructionVariant::Branch {
cond: Value::Instruction(i),
true_case,
false_case,
} => {
assert!(matches!(i.value_ty(), Type::Bool(_)));
backpatch.push(opcodes.len());
opcodes.push(OpCode::Branch {
cond: memory_ranges[&i.id()].start,
true_case: true_case.id as usize,
false_case: false_case.id as usize,
});
}
InstructionVariant::Return(Some(Value::Instruction(t))) => {
assert_eq!(func.ty.ret_t, t.value_ty());
opcodes.push(OpCode::ReturnRange(memory_ranges[&t.id()].clone()));
}
_ => todo!("Unimplemented instruction `{inst:?}`"),
}
}
}
for idx in backpatch {
match &mut opcodes[idx] {
OpCode::Jump(target) => {
*target = block_starts[*target];
}
OpCode::Branch {
true_case,
false_case,
..
} => {
*true_case = block_starts[*true_case];
*false_case = block_starts[*false_case];
}
_ => unreachable!(),
}
}
Self { layout, opcodes }
}
}
pub struct InstructionCache<'l> {
layouts: Arc<LayoutCache<'l>>,
entries: HashMap<*const Function<'l>, Arc<InstructionCacheEntry<'l>>, FxBuildHasher>,
}
impl<'l> InstructionCache<'l> {
pub fn new(layouts: Arc<LayoutCache<'l>>) -> Self {
Self {
layouts,
entries: HashMap::default(),
}
}
pub fn get(&self, func: &'l Function<'l>) -> Arc<InstructionCacheEntry<'l>> {
self.entries
.entry_sync(func as *const Function<'l>)
.or_insert_with(|| Arc::new(InstructionCacheEntry::new(func, &self.layouts)))
.clone()
}
}