From c0f2d1a20a7ce9e91f567f8c766a09f4d558c5d6 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Tue, 21 Jul 2026 21:13:46 +0800 Subject: [PATCH] vm, runtime: add slots! macro and port filter primop to typed slots --- Cargo.lock | 1 + fix-bytecode/src/lib.rs | 1 + fix-runtime/src/lib.rs | 2 + fix-runtime/src/slot.rs | 87 ++++++++++++++++ fix-vm/Cargo.toml | 1 + fix-vm/src/primops/list.rs | 201 ++++++++++++++++++++++--------------- fix-vm/src/primops/mod.rs | 27 ++++- 7 files changed, 236 insertions(+), 84 deletions(-) create mode 100644 fix-runtime/src/slot.rs diff --git a/Cargo.lock b/Cargo.lock index 9a8357b..71bbde7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,6 +546,7 @@ dependencies = [ "fix-bytecode", "fix-error", "fix-lang", + "fix-macros", "fix-runtime", "gc-arena", "hashbrown 0.16.1", diff --git a/fix-bytecode/src/lib.rs b/fix-bytecode/src/lib.rs index a04f37e..03d4d80 100644 --- a/fix-bytecode/src/lib.rs +++ b/fix-bytecode/src/lib.rs @@ -199,6 +199,7 @@ pub enum Continuation { PFetchUrl, PFilterForceList, + PFilterSetupStack, PFilterCallPred, PFilterCheck, diff --git a/fix-runtime/src/lib.rs b/fix-runtime/src/lib.rs index 8aef095..8240edb 100644 --- a/fix-runtime/src/lib.rs +++ b/fix-runtime/src/lib.rs @@ -4,6 +4,7 @@ mod host; mod machine; mod path_util; mod resolve; +mod slot; mod state; mod string_context; mod value; @@ -14,6 +15,7 @@ pub use host::*; pub use machine::*; pub use path_util::*; pub use resolve::*; +pub use slot::*; pub use state::*; pub use string_context::*; pub use value::*; diff --git a/fix-runtime/src/slot.rs b/fix-runtime/src/slot.rs new file mode 100644 index 0000000..2fcc2cd --- /dev/null +++ b/fix-runtime/src/slot.rs @@ -0,0 +1,87 @@ +use std::marker::PhantomData; +use std::ops::ControlFlow; + +use fix_bytecode::BytecodeReader; +use gc_arena::Mutation; + +use crate::{Break, Forced, Machine, NixType, Step, StrictValue, Value, ValueVariant}; + +pub trait SlotContent<'gc> { + type Ty: Into> + TryFrom> + 'gc; +} + +impl<'gc> SlotContent<'gc> for Value<'gc> { + type Ty = Value<'gc>; +} + +impl<'gc> SlotContent<'gc> for StrictValue<'gc> { + type Ty = StrictValue<'gc>; +} + +impl<'gc, T> SlotContent<'gc> for T +where + T: ValueVariant<'gc>, +{ + type Ty = T::Ty; +} + +pub struct Slot { + depth: u8, + _marker: PhantomData, +} + +impl Slot { + #[inline(always)] + pub const fn new(depth: u8) -> Self { + Self { + depth, + _marker: PhantomData, + } + } +} + +impl<'gc, T> Slot +where + T: SlotContent<'gc>, + >>::Error: std::fmt::Debug, +{ + #[inline(always)] + pub fn read>(&self, m: &M) -> T::Ty { + T::Ty::try_from(m.peek(self.depth as usize)).expect("slot held a value of the wrong type") + } + + #[inline(always)] + pub fn read_checked>(&self, m: &M) -> Result { + let val = m.peek(self.depth as usize); + T::Ty::try_from(val).map_err(|_err| val.ty()) + } + + #[inline(always)] + pub fn write>(&self, m: &mut M, val: T::Ty) { + m.replace(self.depth as usize, T::Ty::into(val)); + } +} + +impl<'gc> Slot> { + #[inline(always)] + pub fn force, M: Machine<'gc>>( + &self, + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> ControlFlow> { + T::force_and_check(m, reader, mc, self.depth as usize, reader.inst_start_pc())?; + ControlFlow::Continue(Slot::new(self.depth)) + } + + #[inline(always)] + pub fn force_to_pc>( + &self, + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + pc: usize, + ) -> Step { + m.force_slot_to_pc(self.depth as usize, reader, mc, pc) + } +} diff --git a/fix-vm/Cargo.toml b/fix-vm/Cargo.toml index 9958994..209e382 100644 --- a/fix-vm/Cargo.toml +++ b/fix-vm/Cargo.toml @@ -12,6 +12,7 @@ sysinfo = { version = "0.38", default-features = false, features = ["system"] } fix-bytecode = { path = "../fix-bytecode" } fix-error = { path = "../fix-error" } fix-lang = { path = "../fix-lang" } +fix-macros = { path = "../fix-macros" } fix-runtime = { path = "../fix-runtime" } [features] diff --git a/fix-vm/src/primops/list.rs b/fix-vm/src/primops/list.rs index 74e9cbf..883adfe 100644 --- a/fix-vm/src/primops/list.rs +++ b/fix-vm/src/primops/list.rs @@ -1,91 +1,130 @@ use fix_bytecode::Continuation; -use fix_runtime::{BytecodeReader, List, Machine, MachineExt, NixType, Step, StrictValue, Value}; +use fix_runtime::{ + BytecodeReader, List, Machine, MachineExt, NixType, Slot, Step, StrictValue, Value, +}; use gc_arena::Mutation; -pub fn filter_force_list<'gc, M: Machine<'gc>>( - m: &mut M, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, -) -> Step { - m.force_slot(0, reader, mc)?; - let list = match m.peek_forced(0).expect::() { - Ok(list) => list, - Err(got) => return m.finish_type_err(NixType::List, got), - }; - if list.inner.borrow().is_empty() { - let val = m.pop(); - let _pred = m.pop(); - return m.return_from_primop(val, reader); - } - // prepare stack layout: [ pred list idx acc ] - m.push(Value::new(0)); - m.push(Value::new(List::new_gc(mc))); - reader.set_pc(Continuation::PFilterCallPred.ip() as usize); - Step::Continue(()) -} +use crate::slots; -#[expect( - clippy::indexing_slicing, - clippy::cast_sign_loss, - reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds" -)] -pub fn filter_call_pred<'gc, M: Machine<'gc>>( - m: &mut M, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, -) -> Step { - m.force_slot(3, reader, mc)?; - let pred = m.peek_forced(3); - let idx = m - .peek(1) - .downcast::() - .expect("stack slot must be an integer"); - let elem = m - .peek_forced(2) - .downcast::() - .expect("stack slot must be a list") - .inner - .borrow()[idx as usize]; - m.push(pred.relax()); - m.call(reader, mc, elem, Continuation::PFilterCheck.ip() as usize) -} +pub mod filter { + use super::*; -#[expect( - clippy::indexing_slicing, - clippy::cast_sign_loss, - reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds" -)] -pub fn filter_check<'gc, M: Machine<'gc>>( - m: &mut M, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, -) -> Step { - let ret = m.force_and_retry::(reader, mc)?; - let idx = m - .peek(1) - .downcast::() - .expect("stack slot must be an integer"); - let list = m - .peek_forced(2) - .downcast::() - .expect("stack slot must be a list"); - let list = list.inner.borrow(); - let acc = m - .peek_forced(0) - .downcast::() - .expect("stack slot must be a list"); - if ret { - let mut acc = acc.unlock(mc).borrow_mut(); - acc.push(list[idx as usize]); + #[expect( + clippy::unreachable, + reason = "dispatch_cont routes only the PFilter* continuations to this function, so the fallback arm is unreachable" + )] + pub fn dispatch<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + cont: Continuation, + ) -> Step { + use Continuation::*; + + match cont { + PFilterForceList => force_list(m, reader, mc), + PFilterSetupStack => setup_stack(m, reader, mc), + PFilterCallPred => call_pred(m, reader, mc), + PFilterCheck => check(m, reader, mc), + _ => unreachable!(), + } } - if idx as usize == list.len() - 1 { - let acc = m.pop(); - m.drop_n(3); - return m.return_from_primop(acc, reader); + + fn force_list<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + slots! { + list: Value; + _pred: Value; + }; + list.force_to_pc(m, reader, mc, Continuation::PFilterSetupStack.ip() as usize)?; + setup_stack(m, reader, mc) + } + + fn setup_stack<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + slots! { + list: List; + _pred: Value; + }; + let list = match list.read_checked(m) { + Ok(list) => list, + Err(got) => return m.finish_type_err(NixType::List, got), + }; + if list.inner.borrow().is_empty() { + let val = m.pop(); + let _pred = m.pop(); + return m.return_from_primop(val, reader); + } + // prepare stack layout: [ pred list idx acc ] + m.push(Value::new(0)); + m.push(Value::new(List::new_gc(mc))); + reader.set_pc(Continuation::PFilterCallPred.ip() as usize); + Step::Continue(()) + } + + #[expect( + clippy::indexing_slicing, + clippy::cast_sign_loss, + reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds" + )] + fn call_pred<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + slots! { + _acc: List; + idx: i32; + list: List; + pred: Value; + }; + + let pred: Slot = pred.force(m, reader, mc)?; + let elem = list.read(m).inner.borrow()[idx.read(m) as usize]; + m.push(pred.read(m).relax()); + m.call(reader, mc, elem, Continuation::PFilterCheck.ip() as usize) + } + + #[expect( + clippy::indexing_slicing, + clippy::cast_sign_loss, + reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds" + )] + pub fn check<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + slots! { + acc: List; + idx: i32; + list: List; + _pred: StrictValue; + }; + + let ret = m.force_and_retry::(reader, mc)?; + let list = list.read(m).as_ref().inner.borrow(); + let acc = acc.read(m); + let old_idx = idx.read(m); + if ret { + let mut acc = acc.unlock(mc).borrow_mut(); + acc.push(list[old_idx as usize]); + } + if old_idx as usize == list.len() - 1 { + let acc = m.pop(); + m.drop_n(3); + return m.return_from_primop(acc, reader); + } + idx.write(m, old_idx + 1); + reader.set_pc(Continuation::PFilterCallPred.ip() as usize); + Step::Continue(()) } - m.replace(1, Value::new(idx + 1)); - reader.set_pc(Continuation::PFilterCallPred.ip() as usize); - Step::Continue(()) } // foldl' op nul list diff --git a/fix-vm/src/primops/mod.rs b/fix-vm/src/primops/mod.rs index 7abdcbd..6a7457b 100644 --- a/fix-vm/src/primops/mod.rs +++ b/fix-vm/src/primops/mod.rs @@ -18,6 +18,27 @@ pub use io::*; pub use list::*; pub use path::*; +#[macro_export] +macro_rules! slots { + { $($ident:ident : $ty:ty);* $(;)? } => { + slots! { @acc [ 0 ] $($ident : $ty;)* } + }; + /* { $($ident:ident : $ty:ty);* $(;)? } => { + slots! { @reverse [ $($ident : $ty;)* ] [] } + }; + { @reverse [ $ident:ident : $ty:ty; $($remain:tt)* ] [ $($acc:tt)* ] } => { + slots! { @reverse [ $($remain)* ] [ $ident : $ty; $($acc)* ] } + }; + { @reverse [] [ $($rev_id:ident : $rev_ty:ty;)* ] } => { + slots! { @acc [ 0 ] $($rev_id : $rev_ty;)* } + }; */ + { @acc [ $($acc:tt)* ] $ident:ident : $ty:ty; $($remain:tt)* } => { + let $ident : Slot<$ty> = Slot::new($($acc)*); + slots! { @acc [ $($acc)* + 1 ] $($remain)* } + }; + { @acc [ $($acc:tt)* ] } => {}; +} + pub fn dispatch_cont<'gc, M: Machine<'gc>>( m: &mut M, ctx: &mut impl VmRuntimeCtx, @@ -45,9 +66,9 @@ pub fn dispatch_cont<'gc, M: Machine<'gc>>( PDeepSeqLoop => deep_seq_loop(m, reader, mc), PSeq => seq(m, reader, mc), - PFilterForceList => filter_force_list(m, reader, mc), - PFilterCallPred => filter_call_pred(m, reader, mc), - PFilterCheck => filter_check(m, reader, mc), + PFilterForceList | PFilterSetupStack | PFilterCallPred | PFilterCheck => { + filter::dispatch(m, reader, mc, cont) + } PFoldlStrict => foldl_strict_entry(m, reader, mc), PFoldlStrictEmpty => foldl_strict_empty(m, reader, mc),