diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..e69de29 diff --git a/Cargo.lock b/Cargo.lock index b796e99..816a75c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" version = "1.2.45" @@ -137,6 +143,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "half" version = "2.7.1" @@ -185,6 +200,17 @@ dependencies = [ "scc", ] +[[package]] +name = "leaf_interpreter" +version = "0.1.0" +dependencies = [ + "fxhash", + "leaf_allocators", + "leaf_assembly", + "scc", + "smallvec", +] + [[package]] name = "leaf_parsing" version = "0.1.0" @@ -373,6 +399,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "stacker" version = "0.1.22" diff --git a/Cargo.toml b/Cargo.toml index 4c59d24..9344244 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "3" -members = ["allocators","assembly", "parsing"] +members = ["allocators","assembly", "interpreter", "parsing"] diff --git a/assembly/src/functions/ir.rs b/assembly/src/functions/ir.rs index 3eb79fd..339de97 100644 --- a/assembly/src/functions/ir.rs +++ b/assembly/src/functions/ir.rs @@ -5,21 +5,17 @@ use crate::{ values::{Value, ValueFlags}, }; use derive_more::Debug; -use std::{ - borrow::Cow, - hash::Hash, - ops::Deref, - sync::{ - OnceLock, - atomic::{AtomicU32, Ordering}, - }, - u32, -}; +use std::{borrow::Cow, cell::UnsafeCell, hash::Hash, ops::Deref, sync::OnceLock}; + +// Maybe unsafe but honestly it's extremely unlikely that this will go wrong and it won't cause any issues. +struct Id(UnsafeCell); +unsafe impl Send for Id {} +unsafe impl Sync for Id {} #[derive(Debug)] -#[debug("%{}: {variant:?}", self.id())] +#[debug("{variant:?}")] pub struct Instruction<'l> { - id: AtomicU32, + id: Id, pub parent_block: &'l Block<'l>, pub variant: InstructionVariant<'l>, } @@ -64,7 +60,7 @@ impl<'l> Instruction<'l> { #[inline] pub fn id(&self) -> u32 { - self.id.load(Ordering::Relaxed) + unsafe { *self.id.0.get() } } pub fn value_flags(&self) -> ValueFlags { @@ -75,26 +71,51 @@ impl<'l> Instruction<'l> { pub fn value_ty(&self) -> Type<'l> { match self.variant { - _ => todo!(), + InstructionVariant::Return(_) => self.ctx().void_t(), + InstructionVariant::Store(_, _) => self.ctx().void_t(), + InstructionVariant::StackAlloc(ty) => ty.make_ptr(true).into(), + InstructionVariant::Load(value) => match value.ty() { + Type::Ptr(PtrT { base, .. }) => *base, + _ => unreachable!(), + }, + InstructionVariant::IAdd(a, _) => a.ty(), + InstructionVariant::FAdd(a, _) => a.ty(), + InstructionVariant::ICmp(_, _, _) => self.ctx().bool_t(), + _ => todo!("{self:?}"), } } } -#[derive(Debug, PartialEq, Eq, Hash)] -pub enum InstructionVariant<'l> { - #[debug("stackalloc {_0}")] - StackAlloc(Type<'l>), +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Cmp { + Eq, + Lt, + Gt, + Le, + Ge, +} - #[debug("gcalloc {_0}")] +#[derive(PartialEq, Eq)] +pub enum InstructionVariant<'l> { + StackAlloc(Type<'l>), GCAlloc(Type<'l>), - #[debug("load {_0}")] Load(Value<'l>), - - #[debug("store {_0}, {_1}")] Store(Value<'l>, Value<'l>), - #[debug("return{}", _0.map(|v| format!(" {v}")).unwrap_or_default())] + IAdd(Value<'l>, Value<'l>), + FAdd(Value<'l>, Value<'l>), + + ICmp(Value<'l>, Value<'l>, Cmp), + FCmp(Value<'l>, Value<'l>, Cmp), + + Call(&'l Function<'l>, Vec>), + Jump(&'l Block<'l>), + Branch { + cond: Value<'l>, + true_case: &'l Block<'l>, + false_case: &'l Block<'l>, + }, Return(Option>), } @@ -102,14 +123,48 @@ impl InstructionVariant<'_> { pub fn is_block_termination(&self) -> bool { match self { Self::Return(_) => true, + Self::Jump(_) => true, + Self::Branch { .. } => true, _ => false, } } } +impl std::fmt::Debug for InstructionVariant<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::StackAlloc(ty) => write!(f, "stackalloc {ty}"), + Self::GCAlloc(ty) => write!(f, "gcalloc {ty}"), + Self::Load(v) => write!(f, "load {v}"), + Self::Store(t, v) => write!(f, "store {t}, {v}"), + Self::IAdd(a, b) => write!(f, "iadd {a}, {b}"), + Self::FAdd(a, b) => write!(f, "fadd {a}, {b}"), + Self::ICmp(a, b, c) => write!(f, "icmp {c:?} {a}, {b}"), + Self::FCmp(a, b, c) => write!(f, "fcmp {c:?} {a}, {b}"), + Self::Call(func, args) => { + write!(f, "call {:#?}(", *func as *const Function)?; + let mut separator = ""; + for arg in args { + write!(f, "{separator}{arg}")?; + separator = ", "; + } + write!(f, ")") + } + Self::Branch { + cond, + true_case, + false_case, + } => write!(f, "br {cond} #{}, #{}", true_case.id, false_case.id), + Self::Jump(b) => write!(f, "jump #{}", b.id), + Self::Return(None) => write!(f, "return"), + Self::Return(Some(v)) => write!(f, "return {v}"), + } + } +} + pub struct Block<'l> { pub id: u32, - func: &'l Function<'l>, + pub func: &'l Function<'l>, instructions: OnceLock>>, } @@ -123,6 +178,14 @@ impl<'l> Block<'l> { } } +impl Eq for Block<'_> {} + +impl PartialEq for Block<'_> { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } +} + pub struct BlockBuilder<'l> { block: &'l Block<'l>, instructions: Vec<&'l Instruction<'l>>, @@ -179,6 +242,102 @@ impl<'l> BlockBuilder<'l> { Ok(inst.into()) } + pub fn add(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + let [a_ty, b_ty] = [a.ty(), b.ty()]; + + match (a_ty, b_ty) { + (Type::Int(a_ty), Type::Int(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::IAdd(a, b))?; + Ok(inst.into()) + } + (Type::Float(a_ty), Type::Float(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::FAdd(a, b))?; + Ok(inst.into()) + } + _ => Err(format!("Cannot add values of type `{a_ty}` and `b_ty`.").into()), + } + } + + pub fn cmp( + &mut self, + a: Value<'l>, + b: Value<'l>, + cmp: Cmp, + ) -> BlockBuilderResult<'l, Value<'l>> { + let [a_ty, b_ty] = [a.ty(), b.ty()]; + + match (a_ty, b_ty) { + (Type::Int(a_ty), Type::Int(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::ICmp(a, b, cmp))?; + Ok(inst.into()) + } + (Type::Float(a_ty), Type::Float(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::FCmp(a, b, cmp))?; + Ok(inst.into()) + } + _ => Err(format!("Cannot compare values of type `{a_ty}` and `b_ty`.").into()), + } + } + + pub fn jump(&mut self, block: &'l Block<'l>) -> BlockBuilderResult<'l, Value<'l>> { + if !std::ptr::eq(block.func, self.block.func) { + return Err("Block does not belong to this function.".into()); + } + let inst = self.push_instruction(InstructionVariant::Jump(block))?; + Ok(inst.into()) + } + + pub fn branch( + &mut self, + cond: Value<'l>, + true_case: &'l Block<'l>, + false_case: &'l Block<'l>, + ) -> BlockBuilderResult<'l, Value<'l>> { + if !std::ptr::eq(true_case.func, self.block.func) { + return Err("Block does not belong to this function.".into()); + } + if !std::ptr::eq(false_case.func, self.block.func) { + return Err("Block does not belong to this function.".into()); + } + if !matches!(cond.ty(), Type::Bool(_)) { + return Err(format!("Expected value of type `bool`, found `{}`.", cond.ty()).into()); + } + let inst = self.push_instruction(InstructionVariant::Branch { + cond, + true_case, + false_case, + })?; + Ok(inst.into()) + } + + pub fn call( + &mut self, + func: &'l Function<'l>, + args: Vec>, + ) -> BlockBuilderResult<'l, Value<'l>> { + let par_t = &*func.ty.par_t; + if par_t.len() != args.len() { + return Err(format!( + "Invalid parameter count. Expected {}, found {}.", + par_t.len(), + args.len() + ) + .into()); + } + + if let Some(i) = par_t.iter().zip(&args).position(|(a, b)| *a != b.ty()) { + return Err(format!( + "Invalid parameter at position {i}. Expected type `{}`, found `{}`.", + &par_t[i], + args[i].ty() + ) + .into()); + } + + let inst = self.push_instruction(InstructionVariant::Call(func, args))?; + Ok(inst.into()) + } + pub fn ret(&mut self, value: Option>) -> BlockBuilderResult<'l, Value<'l>> { let ret_t = self.block.func.ty.ret_t; let value_ty = match value { @@ -219,7 +378,7 @@ impl<'l> BlockBuilder<'l> { return Err(format!("Block #{} has already terminated", self.block.id).into()); } let instruction = &*self.block.func.ctx.alloc.alloc(Instruction { - id: AtomicU32::new(u32::MAX), + id: Id(UnsafeCell::new(u32::MAX)), parent_block: self.block, variant, }); @@ -262,7 +421,7 @@ impl<'l> FunctionBodyBuilder<'l> { pub fn create_block(&mut self) -> &'l Block<'l> { let block = &*self.func.ctx.alloc.alloc(Block { - id: 0, + id: self.blocks.len() as u32, func: self.func, instructions: OnceLock::new(), }); @@ -280,7 +439,10 @@ impl<'l> FunctionBodyBuilder<'l> { for block in self.blocks { let block = block.build()?; for inst in block.instructions() { - inst.id.store(next_id.next().unwrap(), Ordering::Relaxed); + unsafe { + let ptr = inst.id.0.get(); + std::ptr::write(ptr, next_id.next().unwrap()); + } } blocks.push(block); } @@ -306,6 +468,40 @@ impl<'l> FunctionBodyBuilder<'l> { self.current_builder().load(value) } + pub fn add(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().add(a, b) + } + + pub fn cmp( + &mut self, + a: Value<'l>, + b: Value<'l>, + cmp: Cmp, + ) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().cmp(a, b, cmp) + } + + pub fn jump(&mut self, block: &'l Block<'l>) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().jump(block) + } + + pub fn branch( + &mut self, + cond: Value<'l>, + true_case: &'l Block<'l>, + false_case: &'l Block<'l>, + ) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().branch(cond, true_case, false_case) + } + + pub fn call( + &mut self, + func: &'l Function<'l>, + args: Vec>, + ) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().call(func, args) + } + pub fn ret(&mut self, value: Option>) -> BlockBuilderResult<'l, Value<'l>> { self.current_builder().ret(value) } diff --git a/assembly/src/functions/mod.rs b/assembly/src/functions/mod.rs index dc34e54..42b64a3 100644 --- a/assembly/src/functions/mod.rs +++ b/assembly/src/functions/mod.rs @@ -16,6 +16,10 @@ pub struct Function<'l> { } impl<'l> Function<'l> { + pub fn ctx(&self) -> Ctx<'l> { + self.ctx + } + pub fn body(&self) -> Option<&FunctionBody<'l>> { self.body.get() } @@ -28,6 +32,14 @@ impl<'l> Function<'l> { } } +impl Eq for Function<'_> {} + +impl PartialEq for Function<'_> { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } +} + impl FmtDebug for Function<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let body: &dyn FmtDebug = match self.body() { diff --git a/assembly/src/main.rs b/assembly/src/main.rs deleted file mode 100644 index e0b6f14..0000000 --- a/assembly/src/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use leaf_allocators::SyncArenaAllocator; -use leaf_assembly::context::{Context, CreateConst}; -use leaf_assembly::types::derivations::MakeTypeDerivations; - -fn main() { - let allocator = SyncArenaAllocator::default(); - let context = Context::new(&allocator); - let func = context.create_function(context.u32_t().make_fn([])); - let mut builder = func.create_body().unwrap(); - builder - .ret(Some(context.create_const(42u32).into())) - .unwrap(); - builder.build().unwrap(); - println!("{func:#?}"); -} diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml new file mode 100644 index 0000000..21759e3 --- /dev/null +++ b/interpreter/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "leaf_interpreter" +version = "0.1.0" +edition = "2024" + +[dependencies] +fxhash = "0.2.1" +leaf_allocators = { path = "../allocators" } +leaf_assembly = { path = "../assembly" } +scc = "3.3.7" +smallvec = "1.15.1" diff --git a/interpreter/src/instruction_cache.rs b/interpreter/src/instruction_cache.rs new file mode 100644 index 0000000..b374741 --- /dev/null +++ b/interpreter/src/instruction_cache.rs @@ -0,0 +1,328 @@ +use crate::layout_cache::{GetLayout, LayoutCache}; +use fxhash::{FxBuildHasher, FxHashMap}; +use leaf_assembly::{ + functions::{ + Function, + ir::{Cmp, Instruction, InstructionVariant}, + }, + types::{Type, intrinsics::IntT}, + values::{Const, Int, Value}, +}; +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), + Jump(usize), + Branch { + cond: usize, + true_case: usize, + false_case: usize, + }, + + CopyRange(Range, Range), + ReturnRange(Range), +} + +#[non_exhaustive] +pub struct InstructionCacheEntry<'l> { + pub layout: Layout, + pub opcodes: Vec>, +} + +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), + .. + }, + ), + Value::Const(Const::Int(Int::U8(v), ctx)), + ) => { + 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), + .. + }, + ), + Value::Const(Const::Int(Int::U16(v), ctx)), + ) => { + 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), + .. + }, + ), + Value::Const(Const::Int(Int::U32(v), ctx)), + ) => { + 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), + .. + }, + ), + Value::Const(Const::Int(Int::U64(v), ctx)), + ) => { + 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), + Value::Const(Const::Int(Int::U32(b), ctx)), + ) => { + 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), + Value::Const(Const::Int(Int::U32(b), ctx)), + 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>, + entries: HashMap<*const Function<'l>, Arc>, FxBuildHasher>, +} + +impl<'l> InstructionCache<'l> { + pub fn new(layouts: Arc>) -> Self { + Self { + layouts, + entries: HashMap::default(), + } + } + + pub fn get(&self, func: &'l Function<'l>) -> Arc> { + self.entries + .entry_sync(func as *const Function<'l>) + .or_insert_with(|| Arc::new(InstructionCacheEntry::new(func, &self.layouts))) + .clone() + } +} diff --git a/interpreter/src/interpreter.rs b/interpreter/src/interpreter.rs new file mode 100644 index 0000000..2ba1b28 --- /dev/null +++ b/interpreter/src/interpreter.rs @@ -0,0 +1,226 @@ +use crate::{ + instruction_cache::{InstructionCache, InstructionCacheEntry, OpCode}, + layout_cache::LayoutCache, +}; +use fxhash::FxHashMap; +use leaf_assembly::{ + context::Ctx, + functions::Function, + types::{Type, intrinsics::IntT}, + values::{Const, Int, Value}, +}; +use smallvec::SmallVec; +use std::{ops::Range, sync::Arc}; + +#[derive(Debug)] +pub enum Error { + InvalidParameterCount, + InvalidParameterType, + FunctionHasNoBody, +} + +#[derive(Debug)] +pub enum AnyValue { + Void, + Int(Int), + Ptr(*const u8), +} + +pub struct Interpreter<'l> { + ctx: Ctx<'l>, + stack: Vec, + layouts: Arc>, + instructions: Arc>, + native_funcs: FxHashMap< + *const Function<'l>, + Arc]) -> Result + 'l>, + >, +} + +impl<'l> Interpreter<'l> { + pub fn new(ctx: Ctx<'l>) -> Self { + let layouts = Arc::new(LayoutCache::default()); + Self { + ctx, + stack: vec![0; 1024], + layouts: layouts.clone(), + instructions: Arc::new(InstructionCache::new(layouts)), + native_funcs: FxHashMap::default(), + } + } + + pub fn register_function( + &mut self, + func: &'l Function<'l>, + fn_impl: impl Fn(&mut Self, &[Range]) -> Result + 'l, + ) { + self.native_funcs.insert(func, Arc::new(fn_impl)); + } + + pub fn run(&mut self, func: &'l Function<'l>, args: Vec>) -> Result { + if func.body().is_none() { + return Err(Error::FunctionHasNoBody); + }; + + if func.ty.par_t.len() != args.len() { + return Err(Error::InvalidParameterCount); + } + + if func + .ty + .par_t + .iter() + .zip(&args) + .any(|(ty, arg)| *ty != arg.ty()) + { + return Err(Error::InvalidParameterType); + } + + let mut sp = 0; + let mut args_ranges: SmallVec<[Range; 4]> = args + .into_iter() + .map(|v| self.push_ctx_val(&mut sp, &v)) + .collect(); + + let func_entry = self.instructions.get(func); + let ret_range = self.call(sp, &mut args_ranges, &func_entry); + match func.ty.ret_t { + Type::Int(IntT { + signed: false, + precision: 32, + .. + }) => { + let mem = &self.stack[ret_range]; + let val = u32::from_ne_bytes(mem.try_into().unwrap()); + Ok(AnyValue::Int(Int::U32(val))) + } + _ => todo!("Unsupported type `{}`", func.ty.ret_t), + } + } + + fn call( + &mut self, + sp: usize, + _args: &[Range], + function: &InstructionCacheEntry<'l>, + ) -> Range { + unsafe { + let l = function.layout; + let sp = sp + (self.stack.as_ptr().add(sp)).align_offset(l.align()); + assert!(sp + l.size() < self.stack.len()); + + // Since the earlier assertion guarantees the stack won't overflow, we can skip the range checks. + let mut i = 0; + while let Some(opcode) = function.opcodes.get(i) { + match opcode { + OpCode::Store_CL_U32(v, offset) => { + let start = sp + *offset; + self.stack + .get_unchecked_mut(start..start + 4) + .copy_from_slice(&v.to_ne_bytes()); + } + + OpCode::CopyRange(src, dst) => { + let len = src.len(); + let src = self.stack.as_ptr().add(sp + src.start); + let dst = self.stack.as_mut_ptr().add(sp + dst.start); + std::ptr::copy_nonoverlapping(src, dst, len); + } + + OpCode::Add_LL_U32(a, b, dst) => { + let [a, b, dst] = [ + sp + a..sp + a + 4, + sp + b..sp + b + 4, + sp + dst..sp + dst + 4, + ]; + let a = u32::from_ne_bytes(self.stack.get_unchecked(a).try_into().unwrap()); + let b = u32::from_ne_bytes(self.stack.get_unchecked(b).try_into().unwrap()); + self.stack + .get_unchecked_mut(dst) + .copy_from_slice(&a.wrapping_add(b).to_ne_bytes()); + } + + OpCode::Add_LC_U32(a, b, dst) => { + let [a, dst] = [sp + a..sp + a + 4, sp + dst..sp + dst + 4]; + let a = u32::from_ne_bytes(self.stack.get_unchecked(a).try_into().unwrap()); + self.stack + .get_unchecked_mut(dst) + .copy_from_slice(&a.wrapping_add(*b).to_ne_bytes()); + } + + OpCode::CmpLt_LC_U32(a, b, dst) => { + let a = sp + a..sp + a + 4; + let a = u32::from_ne_bytes(self.stack.get_unchecked(a).try_into().unwrap()); + *self.stack.get_unchecked_mut(*dst) = (a < *b) as u8; + } + + OpCode::Jump(target) => { + i = *target; + continue; + } + + OpCode::Branch { + cond, + true_case, + false_case, + } => { + i = match self.stack().get_unchecked(*cond) { + 0 => *false_case, + _ => *true_case, + }; + continue; + } + + OpCode::Call(func, args, out) => { + let key = *func as *const Function<'l>; + match self.native_funcs.get(&key).cloned() { + Some(native_func) => { + let res = native_func(self, args).unwrap(); + self.write_any_val(out, &res); + } + None => { + let func = self.instructions.get(func); + let res = self.call(sp, args, &func); + self.stack.copy_within(res, out.start); + } + } + } + + OpCode::ReturnRange(range) => { + return sp + range.start..sp + range.end; + } + _ => todo!("Unimplemented opcode `{opcode:?}`"), + } + i += 1; + } + } + unreachable!("Execution has produced no results"); + } + + pub fn stack(&self) -> &[u8] { + &self.stack + } + + fn push_ctx_val(&mut self, sp: &mut usize, value: &Value) -> Range { + let ptr = &self.stack[*sp] as *const u8; + match value { + Value::Const(Const::Int(Int::U32(v), _)) => { + *sp += ptr.align_offset(align_of::()); + let range = *sp..*sp + size_of::(); + self.stack[range.clone()].copy_from_slice(&v.to_ne_bytes()); + range + } + _ => todo!("Unsupported type `{}`", value.ty()), + } + } + + fn write_any_val(&mut self, range: &Range, value: &AnyValue) { + match value { + AnyValue::Void => assert_eq!(range.len(), 0), + AnyValue::Int(Int::U32(v)) => { + self.stack[range.clone()].copy_from_slice(&v.to_ne_bytes()); + } + _ => todo!("Unsupported value `{:?}`", value), + } + } +} diff --git a/interpreter/src/layout_cache.rs b/interpreter/src/layout_cache.rs new file mode 100644 index 0000000..17762b0 --- /dev/null +++ b/interpreter/src/layout_cache.rs @@ -0,0 +1,101 @@ +use fxhash::FxBuildHasher; +use leaf_assembly::types::{Type, derivations::PtrT, intrinsics::IntT}; +use scc::HashMap; +use std::{ + alloc::Layout, + any::Any, + marker::PhantomData, + ops::Range, + sync::{Arc, OnceLock}, +}; + +#[derive(Debug)] +pub struct LayoutElement { + pub offset: usize, + pub layout: Arc, +} + +impl LayoutElement { + pub fn range(&self) -> Range { + self.offset..self.offset + self.layout.layout.size() + } +} + +#[derive(Debug)] +pub struct TypeLayout { + pub layout: Layout, + pub elements: Vec<(usize, Arc)>, +} + +#[derive(Default)] +pub struct LayoutCache<'l> { + phantom: PhantomData<&'l ()>, + layouts: HashMap<*const u8, Arc, FxBuildHasher>, +} + +pub trait GetLayout { + type LayoutT; + fn get_layout(&self, value: T) -> Arc; +} + +impl<'l> GetLayout> for LayoutCache<'l> { + type LayoutT = TypeLayout; + + fn get_layout(&self, ty: Type<'_>) -> Arc { + match ty { + Type::Void(_) => { + static LAYOUT: OnceLock> = OnceLock::new(); + LAYOUT + .get_or_init(|| { + Arc::new(TypeLayout { + layout: Layout::new::<()>(), + elements: Vec::new(), + }) + }) + .clone() + } + + Type::Bool(_) => { + static LAYOUT: OnceLock> = OnceLock::new(); + LAYOUT + .get_or_init(|| { + Arc::new(TypeLayout { + layout: Layout::new::(), + elements: Vec::new(), + }) + }) + .clone() + } + + Type::Int(IntT { + signed: false, + precision: 32, + .. + }) => { + static LAYOUT: OnceLock> = OnceLock::new(); + LAYOUT + .get_or_init(|| { + Arc::new(TypeLayout { + layout: Layout::new::(), + elements: Vec::new(), + }) + }) + .clone() + } + + Type::Ptr(PtrT { .. }) => { + static LAYOUT: OnceLock> = OnceLock::new(); + LAYOUT + .get_or_init(|| { + Arc::new(TypeLayout { + layout: Layout::new::<*const u8>(), + elements: Vec::new(), + }) + }) + .clone() + } + + _ => todo!("Unsupported type {ty}"), + } + } +} diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs new file mode 100644 index 0000000..121a5d3 --- /dev/null +++ b/interpreter/src/lib.rs @@ -0,0 +1,3 @@ +pub mod interpreter; +pub mod instruction_cache; +pub mod layout_cache; diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs new file mode 100644 index 0000000..58317ec --- /dev/null +++ b/interpreter/src/main.rs @@ -0,0 +1,108 @@ +use leaf_allocators::SyncArenaAllocator; +use leaf_assembly::context::{Context, CreateConst}; +use leaf_assembly::functions::ir::Cmp; +use leaf_assembly::types::derivations::MakeTypeDerivations; +use leaf_interpreter::interpreter::{AnyValue, Error, Interpreter}; +use std::ffi::CStr; +use std::time::{Duration, Instant}; + +fn main() { + let allocator = SyncArenaAllocator::default(); + let context = Context::new(&allocator); + + let puts = context.create_function( + context + .void_t() + .make_fn([context.u8_t().make_ptr(false).into()]), + ); + + let print_u32 = context.create_function(context.void_t().make_fn([context.u32_t().into()])); + + let random_func = { + let func = context.create_function(context.u32_t().make_fn([])); + let mut builder = func.create_body().unwrap(); + + let loop_check = builder.create_block(); + let loop_body = builder.create_block(); + let loop_end = builder.create_block(); + + let var0 = builder.stack_alloc(context.u32_t()).unwrap(); + let var1 = builder.stack_alloc(context.u32_t()).unwrap(); + + builder + .store(var0, context.create_const(42u32).into()) + .unwrap(); + + builder + .store(var1, context.create_const(0u32).into()) + .unwrap(); + + builder.jump(loop_check).unwrap(); + builder.set_current_block(loop_check); + + let a = builder.load(var0).unwrap(); + let b = builder.load(var1).unwrap(); + let c = builder.add(a, b).unwrap(); + builder.call(print_u32, vec![c]).unwrap(); + + let cond = builder + .cmp(c, context.create_const(69u32).into(), Cmp::Lt) + .unwrap(); + builder.branch(cond, loop_body, loop_end).unwrap(); + + builder.set_current_block(loop_body); + let v = builder.load(var1).unwrap(); + let v = builder.add(v, context.create_const(1u32).into()).unwrap(); + builder.store(var1, v).unwrap(); + builder.jump(loop_check).unwrap(); + + builder.set_current_block(loop_end); + let v = builder.load(var1).unwrap(); + builder.ret(Some(v)).unwrap(); + + let body = builder.build().unwrap(); + println!("{body:#?}"); + func + }; + + let mut interpreter = Interpreter::new(context); + + interpreter.register_function(puts, |int, args| match args { + [range] => unsafe { + let mem = &int.stack()[range.clone()]; + let ptr = usize::from_ne_bytes(mem.try_into().unwrap()); + let cstr = CStr::from_ptr(ptr as *const i8); + print!("{cstr:?}"); + Ok(AnyValue::Void) + }, + _ => Err(Error::InvalidParameterCount), + }); + + interpreter.register_function(print_u32, |int, args| match args { + [range] => { + let mem = &int.stack()[range.clone()]; + let val = u32::from_ne_bytes(mem.try_into().unwrap()); + println!("{val}"); + Ok(AnyValue::Void) + } + _ => Err(Error::InvalidParameterCount), + }); + + { + let now = Instant::now(); + let result = interpreter.run(random_func, vec![]); + let elapsed = now.elapsed(); + println!("Ret Value: {result:?}"); + println!("Cold Time: {:?}", elapsed) + } + { + const LOOPS: u32 = 1; + let mut duration = Duration::default(); + for _ in 0..LOOPS { + let now = Instant::now(); + let _ = interpreter.run(random_func, vec![]); + duration += now.elapsed(); + } + println!("Warm Time: {:?}", duration / LOOPS); + } +}