vm, runtime: add slots! macro and port filter primop to typed slots

This commit is contained in:
2026-08-22 20:59:43 +08:00
parent 5a06fb4c1e
commit c0f2d1a20a
7 changed files with 236 additions and 84 deletions
+1
View File
@@ -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]
+120 -81
View File
@@ -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::<List>() {
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::<i32>()
.expect("stack slot must be an integer");
let elem = m
.peek_forced(2)
.downcast::<List>()
.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::<bool>(reader, mc)?;
let idx = m
.peek(1)
.downcast::<i32>()
.expect("stack slot must be an integer");
let list = m
.peek_forced(2)
.downcast::<List>()
.expect("stack slot must be a list");
let list = list.inner.borrow();
let acc = m
.peek_forced(0)
.downcast::<List>()
.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<StrictValue> = 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::<bool>(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
+24 -3
View File
@@ -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),