Basic interpreter

This commit is contained in:
Mia
2025-11-11 20:11:55 +01:00
parent 65b48fe497
commit f50c68e440
12 changed files with 1045 additions and 43 deletions
View File
Generated
+32
View File
@@ -56,6 +56,12 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.45" version = "1.2.45"
@@ -137,6 +143,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "half" name = "half"
version = "2.7.1" version = "2.7.1"
@@ -185,6 +200,17 @@ dependencies = [
"scc", "scc",
] ]
[[package]]
name = "leaf_interpreter"
version = "0.1.0"
dependencies = [
"fxhash",
"leaf_allocators",
"leaf_assembly",
"scc",
"smallvec",
]
[[package]] [[package]]
name = "leaf_parsing" name = "leaf_parsing"
version = "0.1.0" version = "0.1.0"
@@ -373,6 +399,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "stacker" name = "stacker"
version = "0.1.22" version = "0.1.22"
+1 -1
View File
@@ -1,3 +1,3 @@
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["allocators","assembly", "parsing"] members = ["allocators","assembly", "interpreter", "parsing"]
+222 -26
View File
@@ -5,21 +5,17 @@ use crate::{
values::{Value, ValueFlags}, values::{Value, ValueFlags},
}; };
use derive_more::Debug; use derive_more::Debug;
use std::{ use std::{borrow::Cow, cell::UnsafeCell, hash::Hash, ops::Deref, sync::OnceLock};
borrow::Cow,
hash::Hash, // Maybe unsafe but honestly it's extremely unlikely that this will go wrong and it won't cause any issues.
ops::Deref, struct Id(UnsafeCell<u32>);
sync::{ unsafe impl Send for Id {}
OnceLock, unsafe impl Sync for Id {}
atomic::{AtomicU32, Ordering},
},
u32,
};
#[derive(Debug)] #[derive(Debug)]
#[debug("%{}: {variant:?}", self.id())] #[debug("{variant:?}")]
pub struct Instruction<'l> { pub struct Instruction<'l> {
id: AtomicU32, id: Id,
pub parent_block: &'l Block<'l>, pub parent_block: &'l Block<'l>,
pub variant: InstructionVariant<'l>, pub variant: InstructionVariant<'l>,
} }
@@ -64,7 +60,7 @@ impl<'l> Instruction<'l> {
#[inline] #[inline]
pub fn id(&self) -> u32 { pub fn id(&self) -> u32 {
self.id.load(Ordering::Relaxed) unsafe { *self.id.0.get() }
} }
pub fn value_flags(&self) -> ValueFlags { pub fn value_flags(&self) -> ValueFlags {
@@ -75,26 +71,51 @@ impl<'l> Instruction<'l> {
pub fn value_ty(&self) -> Type<'l> { pub fn value_ty(&self) -> Type<'l> {
match self.variant { 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Cmp {
Eq,
Lt,
Gt,
Le,
Ge,
}
#[derive(PartialEq, Eq)]
pub enum InstructionVariant<'l> { pub enum InstructionVariant<'l> {
#[debug("stackalloc {_0}")]
StackAlloc(Type<'l>), StackAlloc(Type<'l>),
#[debug("gcalloc {_0}")]
GCAlloc(Type<'l>), GCAlloc(Type<'l>),
#[debug("load {_0}")]
Load(Value<'l>), Load(Value<'l>),
#[debug("store {_0}, {_1}")]
Store(Value<'l>, Value<'l>), 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<Value<'l>>),
Jump(&'l Block<'l>),
Branch {
cond: Value<'l>,
true_case: &'l Block<'l>,
false_case: &'l Block<'l>,
},
Return(Option<Value<'l>>), Return(Option<Value<'l>>),
} }
@@ -102,14 +123,48 @@ impl InstructionVariant<'_> {
pub fn is_block_termination(&self) -> bool { pub fn is_block_termination(&self) -> bool {
match self { match self {
Self::Return(_) => true, Self::Return(_) => true,
Self::Jump(_) => true,
Self::Branch { .. } => true,
_ => false, _ => 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 struct Block<'l> {
pub id: u32, pub id: u32,
func: &'l Function<'l>, pub func: &'l Function<'l>,
instructions: OnceLock<Vec<&'l Instruction<'l>>>, instructions: OnceLock<Vec<&'l Instruction<'l>>>,
} }
@@ -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> { pub struct BlockBuilder<'l> {
block: &'l Block<'l>, block: &'l Block<'l>,
instructions: Vec<&'l Instruction<'l>>, instructions: Vec<&'l Instruction<'l>>,
@@ -179,6 +242,102 @@ impl<'l> BlockBuilder<'l> {
Ok(inst.into()) 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<Value<'l>>,
) -> 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<Value<'l>>) -> BlockBuilderResult<'l, Value<'l>> { pub fn ret(&mut self, value: Option<Value<'l>>) -> BlockBuilderResult<'l, Value<'l>> {
let ret_t = self.block.func.ty.ret_t; let ret_t = self.block.func.ty.ret_t;
let value_ty = match value { let value_ty = match value {
@@ -219,7 +378,7 @@ impl<'l> BlockBuilder<'l> {
return Err(format!("Block #{} has already terminated", self.block.id).into()); return Err(format!("Block #{} has already terminated", self.block.id).into());
} }
let instruction = &*self.block.func.ctx.alloc.alloc(Instruction { let instruction = &*self.block.func.ctx.alloc.alloc(Instruction {
id: AtomicU32::new(u32::MAX), id: Id(UnsafeCell::new(u32::MAX)),
parent_block: self.block, parent_block: self.block,
variant, variant,
}); });
@@ -262,7 +421,7 @@ impl<'l> FunctionBodyBuilder<'l> {
pub fn create_block(&mut self) -> &'l Block<'l> { pub fn create_block(&mut self) -> &'l Block<'l> {
let block = &*self.func.ctx.alloc.alloc(Block { let block = &*self.func.ctx.alloc.alloc(Block {
id: 0, id: self.blocks.len() as u32,
func: self.func, func: self.func,
instructions: OnceLock::new(), instructions: OnceLock::new(),
}); });
@@ -280,7 +439,10 @@ impl<'l> FunctionBodyBuilder<'l> {
for block in self.blocks { for block in self.blocks {
let block = block.build()?; let block = block.build()?;
for inst in block.instructions() { 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); blocks.push(block);
} }
@@ -306,6 +468,40 @@ impl<'l> FunctionBodyBuilder<'l> {
self.current_builder().load(value) 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<Value<'l>>,
) -> BlockBuilderResult<'l, Value<'l>> {
self.current_builder().call(func, args)
}
pub fn ret(&mut self, value: Option<Value<'l>>) -> BlockBuilderResult<'l, Value<'l>> { pub fn ret(&mut self, value: Option<Value<'l>>) -> BlockBuilderResult<'l, Value<'l>> {
self.current_builder().ret(value) self.current_builder().ret(value)
} }
+12
View File
@@ -16,6 +16,10 @@ pub struct Function<'l> {
} }
impl<'l> Function<'l> { impl<'l> Function<'l> {
pub fn ctx(&self) -> Ctx<'l> {
self.ctx
}
pub fn body(&self) -> Option<&FunctionBody<'l>> { pub fn body(&self) -> Option<&FunctionBody<'l>> {
self.body.get() 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<'_> { impl FmtDebug for Function<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let body: &dyn FmtDebug = match self.body() { let body: &dyn FmtDebug = match self.body() {
-15
View File
@@ -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:#?}");
}
+11
View File
@@ -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"
+328
View File
@@ -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<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),
..
},
),
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<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()
}
}
+226
View File
@@ -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<u8>,
layouts: Arc<LayoutCache<'l>>,
instructions: Arc<InstructionCache<'l>>,
native_funcs: FxHashMap<
*const Function<'l>,
Arc<dyn Fn(&mut Self, &[Range<usize>]) -> Result<AnyValue, Error> + '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<usize>]) -> Result<AnyValue, Error> + 'l,
) {
self.native_funcs.insert(func, Arc::new(fn_impl));
}
pub fn run(&mut self, func: &'l Function<'l>, args: Vec<Value<'l>>) -> Result<AnyValue, Error> {
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<usize>; 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<usize>],
function: &InstructionCacheEntry<'l>,
) -> Range<usize> {
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<usize> {
let ptr = &self.stack[*sp] as *const u8;
match value {
Value::Const(Const::Int(Int::U32(v), _)) => {
*sp += ptr.align_offset(align_of::<u32>());
let range = *sp..*sp + size_of::<u32>();
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<usize>, 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),
}
}
}
+101
View File
@@ -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<TypeLayout>,
}
impl LayoutElement {
pub fn range(&self) -> Range<usize> {
self.offset..self.offset + self.layout.layout.size()
}
}
#[derive(Debug)]
pub struct TypeLayout {
pub layout: Layout,
pub elements: Vec<(usize, Arc<TypeLayout>)>,
}
#[derive(Default)]
pub struct LayoutCache<'l> {
phantom: PhantomData<&'l ()>,
layouts: HashMap<*const u8, Arc<dyn Any>, FxBuildHasher>,
}
pub trait GetLayout<T> {
type LayoutT;
fn get_layout(&self, value: T) -> Arc<Self::LayoutT>;
}
impl<'l> GetLayout<Type<'_>> for LayoutCache<'l> {
type LayoutT = TypeLayout;
fn get_layout(&self, ty: Type<'_>) -> Arc<Self::LayoutT> {
match ty {
Type::Void(_) => {
static LAYOUT: OnceLock<Arc<TypeLayout>> = OnceLock::new();
LAYOUT
.get_or_init(|| {
Arc::new(TypeLayout {
layout: Layout::new::<()>(),
elements: Vec::new(),
})
})
.clone()
}
Type::Bool(_) => {
static LAYOUT: OnceLock<Arc<TypeLayout>> = OnceLock::new();
LAYOUT
.get_or_init(|| {
Arc::new(TypeLayout {
layout: Layout::new::<bool>(),
elements: Vec::new(),
})
})
.clone()
}
Type::Int(IntT {
signed: false,
precision: 32,
..
}) => {
static LAYOUT: OnceLock<Arc<TypeLayout>> = OnceLock::new();
LAYOUT
.get_or_init(|| {
Arc::new(TypeLayout {
layout: Layout::new::<u32>(),
elements: Vec::new(),
})
})
.clone()
}
Type::Ptr(PtrT { .. }) => {
static LAYOUT: OnceLock<Arc<TypeLayout>> = OnceLock::new();
LAYOUT
.get_or_init(|| {
Arc::new(TypeLayout {
layout: Layout::new::<*const u8>(),
elements: Vec::new(),
})
})
.clone()
}
_ => todo!("Unsupported type {ty}"),
}
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod interpreter;
pub mod instruction_cache;
pub mod layout_cache;
+108
View File
@@ -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);
}
}