diff --git a/Cargo.lock b/Cargo.lock index 9c0920b..8fa38a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -504,6 +504,7 @@ dependencies = [ "bumpalo", "color-eyre", "either", + "hashbrown 0.17.1", "indexmap", "memchr", "parser", diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml index 740131c..f2ba5fc 100644 --- a/interpreter/Cargo.toml +++ b/interpreter/Cargo.toml @@ -13,6 +13,7 @@ tracing.workspace = true parser.workspace = true bumpalo.workspace = true allocator-api2.workspace = true +hashbrown.workspace = true indexmap = "2.14.0" [lints] diff --git a/interpreter/src/ir.rs b/interpreter/src/ir.rs index 42676e9..b594c16 100644 --- a/interpreter/src/ir.rs +++ b/interpreter/src/ir.rs @@ -10,9 +10,7 @@ //! emitting bytecode without an intermediate AST because AWK contextual //! shenanigans; _even_ if it was possible, good luck maintaining that. -#![allow(dead_code)] - -mod lower; +pub mod lower; pub use lower::test_interpreter; @@ -20,23 +18,23 @@ use std::fmt::{Debug, Display}; #[derive(Clone, Copy, Debug)] #[repr(transparent)] -struct NonLocal(u16); +pub struct NonLocal(pub u16); #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] -struct Reg(u16); +pub struct Reg(pub u16); #[derive(Clone, Copy, Debug)] #[repr(transparent)] -struct Label(u16); +pub struct Label(u16); #[derive(Clone, Copy, Debug)] #[repr(transparent)] -struct ArgCount(u16); +pub struct ArgCount(u16); #[repr(u8, align(1))] #[derive(Clone, Copy, Debug)] -enum OpCode { +pub enum OpCode { // Unary operations Record, Negation, @@ -63,10 +61,12 @@ enum OpCode { Modulo, // Intrinsic operations - Load, + LoadUser, + LoadBultin, LoadConst, Copy, - Store, + StoreUser, + StoreBuiltin, IntrinsicCall, UserCall, IndirectCall, @@ -80,26 +80,36 @@ const _: () = const { assert!(size_of::() <= 8) }; #[derive(Clone, Copy)] #[repr(C, align(8))] -struct Instruction { - opcode: OpCode, - hint: Hint, - args: Arguments, +pub struct Instruction { + pub opcode: OpCode, + pub hint: Hint, + pub args: Arguments, } #[derive(Clone, Copy)] #[repr(C, align(2))] -union Arguments { - unary_local: (Reg, Reg), - binary_local: (Reg, Reg, Reg), - load_store: (Reg, NonLocal), - jump: Label, - ret: Reg, - branch: (Reg, Label, Label), - br_if: (Reg, Label), - call: (Reg, NonLocal, ArgCount), - ind_call: (Reg, Reg, ArgCount), +pub union Arguments { + unary_local: UnaryArg, + binary_local: BinaryArg, + load_store: LoadStoreArg, + jump: JumpArg, + ret: RetArg, + branch: BranchArg, + br_if: BrIfArg, + call: CallArgs, + ind_call: IndCallArgs, } +pub type UnaryArg = (Reg, Reg); +pub type BinaryArg = (Reg, Reg, Reg); +pub type LoadStoreArg = (Reg, NonLocal); +pub type JumpArg = Label; +pub type RetArg = Reg; +pub type BranchArg = (Reg, Label, Label); +pub type BrIfArg = (Reg, Label); +pub type CallArgs = (Reg, NonLocal, ArgCount); +pub type IndCallArgs = (Reg, Reg, ArgCount); + impl Instruction { fn unary(opcode: impl Into, dest: Reg, src: &impl HintedReg) -> Self { let opcode = opcode.into(); @@ -180,6 +190,24 @@ impl Instruction { hint: Hint::None, } } + + pub fn get_unary(&self) -> Option<&UnaryArg> { + self.opcode + .is_unary() + .then(|| unsafe { &self.args.unary_local }) + } + + pub fn get_binary(&self) -> Option<&BinaryArg> { + self.opcode + .is_binary() + .then(|| unsafe { &self.args.binary_local }) + } + + pub fn get_load_store(&self) -> Option<&LoadStoreArg> { + self.opcode + .is_load_store() + .then(|| unsafe { &self.args.load_store }) + } } impl OpCode { @@ -213,7 +241,14 @@ impl OpCode { } fn is_load_store(self) -> bool { - matches!(self, Self::Load | Self::Store | Self::LoadConst) + matches!( + self, + Self::LoadUser + | Self::LoadBultin + | Self::LoadConst + | Self::StoreUser + | Self::StoreBuiltin + ) } fn is_jump(self) -> bool { @@ -231,7 +266,7 @@ impl OpCode { #[repr(u8, align(1))] #[derive(Clone, Copy, Debug)] -enum Hint { +pub enum Hint { None = 0, UnboxedFloat64, UnboxedLhsFloat64, @@ -247,35 +282,15 @@ impl Debug for Instruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Instruction::{:?}", self.opcode)?; match self.opcode { - OpCode::Record - | OpCode::Negation - | OpCode::ToInt - | OpCode::Negative - | OpCode::Concat - | OpCode::Copy => { + op if op.is_unary() => { let (dest, data) = unsafe { &self.args.unary_local }; write!(f, "({dest:?}, {data:?})") } - OpCode::Eq - | OpCode::NEq - | OpCode::Gt - | OpCode::Lt - | OpCode::LtE - | OpCode::GtE - | OpCode::And - | OpCode::Or - | OpCode::Matches - | OpCode::MatchesNot - | OpCode::Add - | OpCode::Subtract - | OpCode::Multiply - | OpCode::Divide - | OpCode::Raise - | OpCode::Modulo => { + op if op.is_binary() => { let (dest, lhs, rhs) = unsafe { &self.args.binary_local }; write!(f, "({dest:?}, {lhs:?}, {rhs:?})") } - OpCode::Load | OpCode::LoadConst | OpCode::Store => { + op if op.is_load_store() => { let (dest, src) = unsafe { &self.args.load_store }; write!(f, "({dest:?}, {src:?})") } @@ -323,14 +338,18 @@ impl Display for Instruction { let (dest, lhs, rhs) = unsafe { &self.args.binary_local }; write!(f, "{dest} <- {op} {lhs}, {rhs}") } - op @ (OpCode::Load | OpCode::Store) => { + op @ (OpCode::LoadUser | OpCode::StoreUser) => { let (dest, src) = unsafe { &self.args.load_store }; - write!(f, "{dest} <- {op} global[{src}]") + write!(f, "{dest} <- {op} user[{src}]") } op @ OpCode::LoadConst => { let (dest, src) = unsafe { &self.args.load_store }; write!(f, "{dest} <- {op} mem[{src}]") } + op @ (OpCode::StoreBuiltin | OpCode::LoadBultin) => { + let (dest, src) = unsafe { &self.args.load_store }; + write!(f, "{dest} <- {op} intrinsic[{src}]") + } op @ OpCode::BrIf => { let (cond, label) = unsafe { self.args.br_if }; write!(f, "{op} {cond}, {label}") @@ -375,9 +394,11 @@ impl Display for OpCode { Self::Divide => "div", Self::Raise => "pow", Self::Modulo => "mod", - Self::Load => "vload", + Self::LoadUser => "vload", + Self::LoadBultin => "iload", Self::LoadConst => "cload", - Self::Store => "vstore", + Self::StoreUser => "vstore", + Self::StoreBuiltin => "istore", Self::Copy => "cpy", Self::IntrinsicCall => "icall", Self::UserCall => "ucall", diff --git a/interpreter/src/ir/lower.rs b/interpreter/src/ir/lower.rs index abd9176..d79a7e5 100644 --- a/interpreter/src/ir/lower.rs +++ b/interpreter/src/ir/lower.rs @@ -9,20 +9,21 @@ use std::mem::forget; use bumpalo::{Bump, collections::Vec}; use indexmap::IndexSet; -use parser::{Atom, BinaryOperator, Expr, ExprNode, UnaryOperator}; +use parser::{Atom, BinaryOperator, Expr, ExprNode, UnaryOperator, Variable}; -use crate::ir::{Hint, HintedReg, Instruction, Label, NonLocal, OpCode, Reg}; - -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] -pub struct Value(f64); // TODO: use NaN-boxing. +use crate::{ + ir::{Hint, HintedReg, Instruction, Label, NonLocal, OpCode, Reg}, + vm::{ExecMode, Interpreter, SymbolTable, Value}, +}; #[derive(Debug)] -struct Code<'a> { - arena: &'a Bump, - bc: Bytecode<'a>, - consts: &'a mut IndexSet, - free_regs: Vec<'a, Reg>, - reg_pointer: u16, +pub struct Code<'arena> { + pub arena: &'arena Bump, + pub bc: Bytecode<'arena>, + pub consts: IndexSet, + pub symbols: SymbolTable<'arena>, + free_regs: Vec<'arena, Reg>, + pub reg_pointer: u16, } #[must_use] @@ -38,10 +39,10 @@ impl Code<'_> { fn lower_expr_into(&mut self, expr: &Expr, dest: Reg) -> Hint { match expr { Expr::Leaf(atom) => match atom { - Atom::Variable(var) => { - let src = NonLocal(crate::index_for_global(var)); + Atom::Variable(Variable::User(ident)) => { + let src = self.symbols.register_user_var(ident, self.arena); self.bc - .emit(Instruction::load_store(OpCode::Load, dest, src)); + .emit(Instruction::load_store(OpCode::LoadUser, dest, src)); } &Atom::Number(n) => { let src = self.register_const(Value(n)); @@ -106,8 +107,8 @@ impl Code<'_> { } #[derive(Debug, Clone)] -struct Bytecode<'a> { - code: Vec<'a, Instruction>, +pub struct Bytecode<'a> { + pub code: Vec<'a, Instruction>, } #[derive(Clone, Debug)] @@ -167,13 +168,17 @@ pub fn test_interpreter(expr: &Expr<'_>) -> impl Display { let mut c = Code { arena: &bump, bc: Bytecode::new_in(&bump), - consts: &mut IndexSet::new(), + consts: IndexSet::new(), + symbols: SymbolTable::new_in(&bump), reg_pointer: 0, free_regs: Vec::new_in(&bump), }; let result = c.lower_expr(expr); forget(result); - c.to_string() + let code = c.to_string(); + let mut vm = Interpreter::new(ExecMode::Uu, c); + vm.run(); + format!("{code}---\n{vm:#?}") } impl From for OpCode { diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index 6c11358..dbbdb46 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -3,34 +3,12 @@ // For the full copyright and license information, please view the LICENSE // files that was distributed with this source code. -mod ir; +#![allow(dead_code)] -use color_eyre::eyre::Result; -use either::Either; +pub(crate) mod ir; +mod vm; pub use ir::test_interpreter; -use parser::Variable; - -pub enum BuiltinCommand {} -pub enum BuiltinVar {} - -pub type Command<'a> = Either; -// pub type Variable<'a> = Either; - -#[derive(Debug)] -pub struct Interpreter; - -impl Interpreter { - #[tracing::instrument] - pub fn run(self) -> Result> { - todo!() - } - pub fn eval_expression(&mut self) {} -} - -fn index_for_global(_var: &Variable) -> u16 { - 1 // TODO -} #[derive(Debug, thiserror::Error)] #[allow(dead_code)] diff --git a/interpreter/src/types.rs b/interpreter/src/types.rs new file mode 100644 index 0000000..3791a39 --- /dev/null +++ b/interpreter/src/types.rs @@ -0,0 +1,14 @@ +use std::ptr::NonNull; + +// / NaN-boxed value +union AValue { + float: f64, + ptr: NonNull<()>, +} + +impl AValue { + fn get_float(&self) -> f64 { + let float = unsafe { self.float }; + if !float.is_nan() { float } else { todo!() } + } +} diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs new file mode 100644 index 0000000..79354e9 --- /dev/null +++ b/interpreter/src/vm.rs @@ -0,0 +1,130 @@ +use bumpalo::{Bump, collections::Vec}; +use hashbrown::{DefaultHashBuilder, HashMap}; +use indexmap::{IndexMap, IndexSet}; +use parser::Identifier; + +use crate::ir::{ + NonLocal, OpCode, Reg, + lower::{Bytecode, Code}, +}; + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct Value(pub f64); // TODO: use NaN-boxing. + +#[derive(Debug)] +pub enum ExecMode { + Uu, + Gnu, + Posix, +} + +#[derive(Debug)] +pub struct Interpreter<'a> { + arena: &'a Bump, + bc: Bytecode<'a>, + program_counter: usize, + registers: Registers<'a>, + symbols: SymbolTable<'a>, + consts: IndexSet, + compat: ExecMode, +} + +#[derive(Debug)] +pub struct Registers<'a>(Vec<'a, Value>); + +#[derive(Debug)] +pub struct SymbolTable<'a> { + user: IndexMap, Value>, + // separate table for cheap invalidation. It's an arena _visibly shrugs_. + records: HashMap, + // etc +} + +impl<'a> Interpreter<'a> { + pub fn new(compat: ExecMode, code: Code<'a>) -> Self { + Self { + arena: code.arena, + bc: code.bc, + program_counter: 0, + registers: Registers(bumpalo::vec![in code.arena; Value(0.); 8]), + symbols: code.symbols, + consts: code.consts, + compat, + } + } +} + +impl<'a> SymbolTable<'a> { + pub fn new_in(arena: &'a Bump) -> Self { + Self { + user: IndexMap::new(), + records: HashMap::new_in(arena), + } + } + fn lookup_user_var(&self, var: NonLocal) -> &Value { + self.user.get_index(var.0 as _).unwrap().1 + } + + fn write_user_val(&mut self, var: NonLocal, value: &Value) { + *self.user.get_index_mut(var.0 as _).unwrap().1 = Value::clone(value); + } + + pub fn register_user_var(&mut self, var: &Identifier, bump: &'a Bump) -> NonLocal { + if let Some(index) = self.user.get_index_of(var) { + NonLocal(index as _) + } else { + let ident = Identifier { + namespace: bump.alloc_str(var.namespace), + literal: bump.alloc_str(var.literal), + }; + NonLocal(self.user.insert_full(ident, Value(0.)).0 as _) + } + } +} + +impl Interpreter<'_> { + pub fn run(&mut self) { + while let Some(instr) = self.bc.code.get(self.program_counter) { + match instr { + ix if let Some(&(dest, src)) = ix.get_unary() => {} + ix if let Some(&(dest, lhs, rhs)) = ix.get_binary() => { + let lhs = self.registers.read(lhs); + let rhs = self.registers.read(rhs); + let val = match ix.opcode { + OpCode::Add => Value(lhs.0 + rhs.0), + OpCode::Subtract => Value(lhs.0 - rhs.0), + OpCode::Multiply => Value(lhs.0 * rhs.0), + OpCode::Divide => Value(lhs.0 / rhs.0), + _ => todo!(), + }; + self.registers.write(dest, &val); + } + ix if let Some(&(dest, src)) = ix.get_load_store() => match ix.opcode { + OpCode::LoadConst => self + .registers + .write(dest, self.consts.get_index(src.0 as _).unwrap()), + OpCode::LoadUser => { + self.registers + .write(dest, self.symbols.lookup_user_var(src)); + } + OpCode::StoreUser => { + self.symbols.write_user_val(src, self.registers.read(dest)); + } + _ => todo!(), + }, + _ => todo!(), + } + self.program_counter += 1; + } + } +} + +impl Registers<'_> { + fn read(&self, src: Reg) -> &Value { + &self.0[src.0 as usize] + } + fn write(&mut self, dest: Reg, src: &Value) { + self.0[dest.0 as usize] = Value::clone(src); + } +}