vm, runtime: add slots! macro and port filter primop to typed slots
This commit is contained in:
Generated
+1
@@ -546,6 +546,7 @@ dependencies = [
|
|||||||
"fix-bytecode",
|
"fix-bytecode",
|
||||||
"fix-error",
|
"fix-error",
|
||||||
"fix-lang",
|
"fix-lang",
|
||||||
|
"fix-macros",
|
||||||
"fix-runtime",
|
"fix-runtime",
|
||||||
"gc-arena",
|
"gc-arena",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ pub enum Continuation {
|
|||||||
PFetchUrl,
|
PFetchUrl,
|
||||||
|
|
||||||
PFilterForceList,
|
PFilterForceList,
|
||||||
|
PFilterSetupStack,
|
||||||
PFilterCallPred,
|
PFilterCallPred,
|
||||||
PFilterCheck,
|
PFilterCheck,
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod host;
|
|||||||
mod machine;
|
mod machine;
|
||||||
mod path_util;
|
mod path_util;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
|
mod slot;
|
||||||
mod state;
|
mod state;
|
||||||
mod string_context;
|
mod string_context;
|
||||||
mod value;
|
mod value;
|
||||||
@@ -14,6 +15,7 @@ pub use host::*;
|
|||||||
pub use machine::*;
|
pub use machine::*;
|
||||||
pub use path_util::*;
|
pub use path_util::*;
|
||||||
pub use resolve::*;
|
pub use resolve::*;
|
||||||
|
pub use slot::*;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
pub use string_context::*;
|
pub use string_context::*;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
|||||||
@@ -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<Value<'gc>> + TryFrom<Value<'gc>> + '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<T> {
|
||||||
|
depth: u8,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Slot<T> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn new(depth: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
depth,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc, T> Slot<T>
|
||||||
|
where
|
||||||
|
T: SlotContent<'gc>,
|
||||||
|
<T::Ty as TryFrom<Value<'gc>>>::Error: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn read<M: Machine<'gc>>(&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<M: Machine<'gc>>(&self, m: &M) -> Result<T::Ty, NixType> {
|
||||||
|
let val = m.peek(self.depth as usize);
|
||||||
|
T::Ty::try_from(val).map_err(|_err| val.ty())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn write<M: Machine<'gc>>(&self, m: &mut M, val: T::Ty) {
|
||||||
|
m.replace(self.depth as usize, T::Ty::into(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Slot<Value<'gc>> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn force<T: Forced<'gc>, M: Machine<'gc>>(
|
||||||
|
&self,
|
||||||
|
m: &mut M,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> ControlFlow<Break, Slot<T>> {
|
||||||
|
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<M: Machine<'gc>>(
|
||||||
|
&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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ sysinfo = { version = "0.38", default-features = false, features = ["system"] }
|
|||||||
fix-bytecode = { path = "../fix-bytecode" }
|
fix-bytecode = { path = "../fix-bytecode" }
|
||||||
fix-error = { path = "../fix-error" }
|
fix-error = { path = "../fix-error" }
|
||||||
fix-lang = { path = "../fix-lang" }
|
fix-lang = { path = "../fix-lang" }
|
||||||
|
fix-macros = { path = "../fix-macros" }
|
||||||
fix-runtime = { path = "../fix-runtime" }
|
fix-runtime = { path = "../fix-runtime" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
+120
-81
@@ -1,91 +1,130 @@
|
|||||||
use fix_bytecode::Continuation;
|
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;
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
pub fn filter_force_list<'gc, M: Machine<'gc>>(
|
use crate::slots;
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(
|
pub mod filter {
|
||||||
clippy::indexing_slicing,
|
use super::*;
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::indexing_slicing,
|
clippy::unreachable,
|
||||||
clippy::cast_sign_loss,
|
reason = "dispatch_cont routes only the PFilter* continuations to this function, so the fallback arm is unreachable"
|
||||||
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
)]
|
||||||
)]
|
pub fn dispatch<'gc, M: Machine<'gc>>(
|
||||||
pub fn filter_check<'gc, M: Machine<'gc>>(
|
m: &mut M,
|
||||||
m: &mut M,
|
reader: &mut BytecodeReader<'_>,
|
||||||
reader: &mut BytecodeReader<'_>,
|
mc: &Mutation<'gc>,
|
||||||
mc: &Mutation<'gc>,
|
cont: Continuation,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let ret = m.force_and_retry::<bool>(reader, mc)?;
|
use Continuation::*;
|
||||||
let idx = m
|
|
||||||
.peek(1)
|
match cont {
|
||||||
.downcast::<i32>()
|
PFilterForceList => force_list(m, reader, mc),
|
||||||
.expect("stack slot must be an integer");
|
PFilterSetupStack => setup_stack(m, reader, mc),
|
||||||
let list = m
|
PFilterCallPred => call_pred(m, reader, mc),
|
||||||
.peek_forced(2)
|
PFilterCheck => check(m, reader, mc),
|
||||||
.downcast::<List>()
|
_ => unreachable!(),
|
||||||
.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]);
|
|
||||||
}
|
}
|
||||||
if idx as usize == list.len() - 1 {
|
|
||||||
let acc = m.pop();
|
fn force_list<'gc, M: Machine<'gc>>(
|
||||||
m.drop_n(3);
|
m: &mut M,
|
||||||
return m.return_from_primop(acc, reader);
|
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
|
// foldl' op nul list
|
||||||
|
|||||||
@@ -18,6 +18,27 @@ pub use io::*;
|
|||||||
pub use list::*;
|
pub use list::*;
|
||||||
pub use path::*;
|
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>>(
|
pub fn dispatch_cont<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
@@ -45,9 +66,9 @@ pub fn dispatch_cont<'gc, M: Machine<'gc>>(
|
|||||||
PDeepSeqLoop => deep_seq_loop(m, reader, mc),
|
PDeepSeqLoop => deep_seq_loop(m, reader, mc),
|
||||||
PSeq => seq(m, reader, mc),
|
PSeq => seq(m, reader, mc),
|
||||||
|
|
||||||
PFilterForceList => filter_force_list(m, reader, mc),
|
PFilterForceList | PFilterSetupStack | PFilterCallPred | PFilterCheck => {
|
||||||
PFilterCallPred => filter_call_pred(m, reader, mc),
|
filter::dispatch(m, reader, mc, cont)
|
||||||
PFilterCheck => filter_check(m, reader, mc),
|
}
|
||||||
|
|
||||||
PFoldlStrict => foldl_strict_entry(m, reader, mc),
|
PFoldlStrict => foldl_strict_entry(m, reader, mc),
|
||||||
PFoldlStrictEmpty => foldl_strict_empty(m, reader, mc),
|
PFoldlStrictEmpty => foldl_strict_empty(m, reader, mc),
|
||||||
|
|||||||
Reference in New Issue
Block a user