diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index eedb1bb..fba7ada 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -46,6 +46,7 @@ pub mod module; pub mod prelude; pub mod reg; pub mod reset; +pub mod sim; pub mod source_location; pub mod testing; pub mod ty; diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs new file mode 100644 index 0000000..3aa69a6 --- /dev/null +++ b/crates/fayalite/src/sim.rs @@ -0,0 +1,206 @@ +//! Fayalite Simulation + +use crate::{ + bundle::{Bundle, BundleType}, + enum_::Enum, + expr::Expr, + int::Bool, + intern::{Intern, Interned}, + module::{ + Block, Module, ModuleBody, NormalModuleBody, Stmt, StmtConnect, StmtFormal, StmtIf, + StmtMatch, + }, + sim::interpreter::{Insn, Insns, InsnsBuilding}, + source_location::SourceLocation, +}; +use hashbrown::HashMap; +use std::{cell::RefCell, rc::Rc}; + +mod interpreter; + +#[derive(Debug)] +struct CompilingModule { + compiled_module: Option, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +enum CondStack { + Always, + IfTrue { + parent: Interned, + cond: Expr, + source_location: SourceLocation, + }, + IfFalse { + parent: Interned, + cond: Expr, + source_location: SourceLocation, + }, + MatchArm { + parent: Interned, + enum_expr: Expr, + variant_index: usize, + source_location: SourceLocation, + }, +} + +#[derive(Debug)] +pub struct Compiler { + insns: Insns, + modules: HashMap>, Rc>>, + module_queue: Vec>>, +} + +impl Compiler { + pub fn new() -> Self { + Self { + insns: Insns::new(), + modules: HashMap::new(), + module_queue: Vec::new(), + } + } + pub fn add_module(&mut self, module: Interned>) { + self.modules.entry(module).or_insert_with(|| { + self.module_queue.push(module); + Rc::new(RefCell::new(CompilingModule { + compiled_module: None, + })) + }); + } + fn compile_block( + &mut self, + module: Interned>, + block: Block, + cond_stack: Interned, + ) { + let Block { memories, stmts } = block; + for memory in memories { + todo!("implement memory"); + } + for stmt in stmts { + match stmt { + Stmt::Connect(StmtConnect { + lhs, + rhs, + source_location, + }) => todo!(), + Stmt::Formal(StmtFormal { + kind, + clk, + pred, + en, + text, + source_location, + }) => todo!("implement simulating formal statements"), + Stmt::If(StmtIf { + cond, + source_location, + blocks: [then_block, else_block], + }) => { + self.compile_block( + module, + then_block, + CondStack::IfTrue { + parent: cond_stack, + cond, + source_location, + } + .intern_sized(), + ); + self.compile_block( + module, + else_block, + CondStack::IfFalse { + parent: cond_stack, + cond, + source_location, + } + .intern_sized(), + ); + } + Stmt::Match(StmtMatch { + expr, + source_location, + blocks, + }) => { + for (variant_index, block) in blocks.into_iter().enumerate() { + self.compile_block( + module, + block, + CondStack::MatchArm { + parent: cond_stack, + enum_expr: expr, + variant_index, + source_location, + } + .intern_sized(), + ); + } + } + Stmt::Declaration(stmt_declaration) => todo!(), + } + } + } + fn compile_module(&mut self, module: Interned>) -> CompiledModule { + let step_entry_pc = self.insns.next_insn_pc(); + match module.body() { + ModuleBody::Normal(NormalModuleBody { body }) => { + self.compile_block(module, body, CondStack::Always.intern_sized()) + } + ModuleBody::Extern(_extern_module_body) => { + todo!("simulating extern module: {}", module.name_id()); + } + } + self.insns.push(Insn::Return, module.source_location()); + CompiledModule { step_entry_pc } + } + pub fn run(mut self) -> Compiled { + while let Some(module) = self.module_queue.pop() { + let compiling_module = self.modules[&module].clone(); + let mut compiling_module = compiling_module.borrow_mut(); + let CompilingModule { compiled_module } = &mut *compiling_module; + assert!(compiled_module.is_none()); + *compiled_module = Some(self.compile_module(module)); + } + Compiled { + insns: Insns::from(self.insns).intern_sized(), + modules: self + .modules + .into_iter() + .map(|(module, compiling_module)| { + ( + module, + Rc::into_inner(compiling_module) + .expect("only reference is Compiler::modules") + .into_inner() + .compiled_module + .expect("module is compiled by Compiler::run"), + ) + }) + .collect(), + } + } +} + +#[derive(Debug)] +struct CompiledModule { + step_entry_pc: usize, +} + +#[derive(Debug)] +pub struct Compiled { + insns: Interned, + modules: HashMap>, CompiledModule>, +} + +impl Compiled { + pub fn new(module: Module) -> Self { + let mut compiler = Compiler::new(); + compiler.add_module(module.canonical().intern()); + compiler.run() + } +} + +pub struct Simulation { + state: interpreter::State, +} diff --git a/crates/fayalite/src/sim/interpreter.rs b/crates/fayalite/src/sim/interpreter.rs new file mode 100644 index 0000000..eed64e4 --- /dev/null +++ b/crates/fayalite/src/sim/interpreter.rs @@ -0,0 +1,464 @@ +use crate::{ + intern::{Intern, Interned}, + source_location::SourceLocation, +}; +use num_bigint::BigInt; +use std::{convert::Infallible, fmt, hash::Hash}; + +pub(crate) type SmallUInt = u64; +pub(crate) type SmallSInt = i64; + +macro_rules! impl_insns { + ( + #[next_macro = $next_macro:ident, branch_macro = $branch_macro:ident] + $vis:vis fn $State:ident::$run:ident(&mut $self:ident, $insns:ident: &[$Insn:ident]) -> $run_ret_ty:ty { + #[pc] + let mut $pc:ident: usize = $pc_init:expr; + setup! { + $($setup:tt)* + } + $(let mut $local:ident = $local_init:expr;)* + main_loop!(); + cleanup! { + $($cleanup:tt)* + } + } + $( + $(#[$insn_meta:meta])* + $insn_name:ident $({ + $( + $(#[$field_meta:meta])* + $field_name:ident: $field_ty:ty, + )* + })? => $block:block + )+ + ) => { + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] + $vis enum $Insn { + $( + $(#[$insn_meta])* + $insn_name $({ + $( + $(#[$field_meta])* + $field_name: $field_ty, + )* + })?, + )+ + } + + impl $State { + $vis fn $run(&mut $self, $insns: &[$Insn]) -> $run_ret_ty { + let mut $pc: usize = $pc_init; + $($setup)* + let mut insn = $insns[$pc]; + let retval = 'main_loop: loop { + macro_rules! $next_macro { + () => { + $pc += 1; + insn = $insns[$pc]; + continue 'main_loop; + }; + } + macro_rules! $branch_macro { + ($next_pc:expr) => { + $pc = $next_pc; + insn = $insns[$pc]; + continue 'main_loop; + }; + } + let _: Infallible = match insn { + $( + $Insn::$insn_name $({ + $( + $field_name, + )* + })? => { + $block + } + )+ + }; + }; + $($cleanup)* + retval + } + } + }; +} + +pub(crate) trait InsnsBuildingKind: Copy + Eq + fmt::Debug + Hash + Default { + type BoxedSlice: fmt::Debug + + Clone + + Eq + + std::ops::Deref; +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub(crate) struct InsnsBuildingDone; + +impl InsnsBuildingKind for InsnsBuildingDone { + type BoxedSlice = Interned<[T]>; +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub(crate) struct InsnsBuilding; + +impl InsnsBuildingKind for InsnsBuilding { + type BoxedSlice = Vec; +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub(crate) struct Insns { + insns: BK::BoxedSlice, + small_stack_size: usize, + big_stack_size: usize, + small_state_debug_names: BK::BoxedSlice>, + big_state_debug_names: BK::BoxedSlice>, + insn_source_locations: BK::BoxedSlice, +} + +struct InsnsDebug<'a> { + insns: &'a [Insn], + insn_source_locations: &'a [SourceLocation], +} + +impl<'a> fmt::Debug for InsnsDebug<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug_list = f.debug_list(); + let mut last_source_location = None; + for (insn, source_location) in self.insns.iter().zip(self.insn_source_locations) { + if Some(source_location) != last_source_location { + debug_list.entry(&format_args!("// at: {source_location}\n{insn:?}")); + } else { + debug_list.entry(insn); + } + last_source_location = Some(source_location); + } + debug_list.finish() + } +} + +impl fmt::Debug for Insns { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + insns, + small_stack_size, + big_stack_size, + small_state_debug_names, + big_state_debug_names, + insn_source_locations, + } = self; + f.debug_struct("Insns") + .field("small_stack_size", small_stack_size) + .field("big_stack_size", big_stack_size) + .field("small_state_debug_names", small_state_debug_names) + .field("big_state_debug_names", big_state_debug_names) + .field( + "insns", + &InsnsDebug { + insns, + insn_source_locations, + }, + ) + .finish_non_exhaustive() + } +} + +impl Insns { + pub(crate) fn new() -> Self { + Self { + insns: Vec::new(), + small_stack_size: 0, + big_stack_size: 0, + small_state_debug_names: Vec::new(), + big_state_debug_names: Vec::new(), + insn_source_locations: Vec::new(), + } + } + pub(crate) fn last_insn_pc(&self) -> usize { + self.insns + .len() + .checked_sub(1) + .expect("no last instruction") + } + pub(crate) fn next_insn_pc(&self) -> usize { + self.insns.len() + } + pub(crate) fn push(&mut self, insn: Insn, source_location: SourceLocation) { + self.insns.push(insn); + self.insn_source_locations.push(source_location); + } +} + +impl From> for Insns { + fn from(input: Insns) -> Self { + let Insns { + insns, + small_stack_size, + big_stack_size, + small_state_debug_names, + big_state_debug_names, + insn_source_locations, + } = input; + Insns { + insns: Intern::intern_owned(insns), + small_stack_size, + big_stack_size, + small_state_debug_names: Intern::intern_owned(small_state_debug_names), + big_state_debug_names: Intern::intern_owned(big_state_debug_names), + insn_source_locations: Intern::intern_owned(insn_source_locations), + } + } +} + +#[derive(Debug)] +pub(crate) struct State { + pub(crate) insns: Interned, + pub(crate) pc: usize, + pub(crate) small_stack: Box<[SmallUInt]>, + pub(crate) small_stack_top: usize, + pub(crate) small_states: Box<[SmallUInt]>, + pub(crate) big_stack: Box<[BigInt]>, + pub(crate) big_stack_top: usize, + pub(crate) big_states: Box<[BigInt]>, +} + +impl State { + pub(crate) fn setup_call(&mut self, entry_pc: usize) { + let Self { + insns: _, + pc, + small_stack: _, + small_stack_top, + small_states: _, + big_stack: _, + big_stack_top, + big_states: _, + } = self; + *pc = entry_pc; + *small_stack_top = 0; + *big_stack_top = 0; + } +} + +impl_insns! { + #[next_macro = next, branch_macro = branch] + pub(crate) fn State::run(&mut self, insns: &[Insn]) -> () { + #[pc] + let mut pc: usize = self.pc; + setup! { + let small_states = &mut *self.small_states; + let small_stack = &mut *self.small_stack; + let mut small_stack_top = self.small_stack_top; + macro_rules! small_stack_push { + ($v:expr) => { + { + small_stack[small_stack_top] = $v; + small_stack_top += 1; + } + }; + } + macro_rules! small_stack_pop { + () => { + { + small_stack_top -= 1; + small_stack[small_stack_top] + } + }; + } + let big_states = &mut *self.big_states; + let big_stack = &mut *self.big_stack; + let mut big_stack_top = self.big_stack_top; + macro_rules! big_stack_push { + ($v:expr) => { + { + big_stack[big_stack_top] = $v; + big_stack_top += 1; + } + }; + } + macro_rules! big_stack_pop { + () => { + { + big_stack_top -= 1; + big_stack[big_stack_top] + } + }; + } + } + main_loop!(); + cleanup! { + self.pc = pc; + self.small_stack_top = small_stack_top; + } + } + ReadSmall { + index: u32, + } => { + small_stack_push!(small_states[index as usize]); + next!(); + } + SExtSmall { + /// number of bits in a [`SmallSInt`] that aren't used + unused_bit_count: u8, + } => { + let mut value = small_stack_pop!() as SmallSInt; + value <<= unused_bit_count; + value >>= unused_bit_count; + small_stack_push!(value as SmallUInt); + next!(); + } + ZExtSmall { + /// number of bits in a [`SmallUInt`] that aren't used + unused_bit_count: u8, + } => { + let mut value = small_stack_pop!(); + value <<= unused_bit_count; + value >>= unused_bit_count; + small_stack_push!(value); + next!(); + } + AndSmall => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = lhs & rhs; + small_stack_push!(value); + next!(); + } + OrSmall => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = lhs | rhs; + small_stack_push!(value); + next!(); + } + XorSmall => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = lhs ^ rhs; + small_stack_push!(value); + next!(); + } + NotSmall => { + let value = small_stack_pop!(); + small_stack_push!(!value); + next!(); + } + CmpEqSmall => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = (lhs == rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpNeSmall => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = (lhs != rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpLTSmallUInt => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = (lhs < rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpLESmallUInt => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = (lhs <= rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpGTSmallUInt => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = (lhs > rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpGESmallUInt => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = (lhs >= rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpLTSmallSInt => { + let rhs = small_stack_pop!() as SmallSInt; + let lhs = small_stack_pop!() as SmallSInt; + let value = (lhs < rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpLESmallSInt => { + let rhs = small_stack_pop!() as SmallSInt; + let lhs = small_stack_pop!() as SmallSInt; + let value = (lhs <= rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpGTSmallSInt => { + let rhs = small_stack_pop!() as SmallSInt; + let lhs = small_stack_pop!() as SmallSInt; + let value = (lhs > rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + CmpGESmallSInt => { + let rhs = small_stack_pop!() as SmallSInt; + let lhs = small_stack_pop!() as SmallSInt; + let value = (lhs >= rhs) as SmallUInt; + small_stack_push!(value); + next!(); + } + AddSmall => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = lhs.wrapping_add(rhs); + small_stack_push!(value); + next!(); + } + SubSmall => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = lhs.wrapping_sub(rhs); + small_stack_push!(value); + next!(); + } + NegSmall => { + let value = small_stack_pop!(); + let value = value.wrapping_neg(); + small_stack_push!(value); + next!(); + } + MulSmall => { + let rhs = small_stack_pop!(); + let lhs = small_stack_pop!(); + let value = lhs.wrapping_mul(rhs); + small_stack_push!(value); + next!(); + } + ConstU32Small { + value: u32, + } => { + small_stack_push!(value as SmallUInt); + next!(); + } + ConstS32Small { + value: i32, + } => { + small_stack_push!(value as SmallUInt); + next!(); + } + WriteSmall { + index: u32, + } => { + small_states[index as usize] = small_stack_pop!(); + next!(); + } + Return => { + break; + } +}