macros, vm, bytecode: add #[primop] await-style primop macro
This commit is contained in:
+33
-25
@@ -152,13 +152,15 @@ pub enum Continuation {
|
||||
PAdd,
|
||||
PAddErrorContext,
|
||||
|
||||
PAll,
|
||||
PAllCallPred,
|
||||
PAllCheck,
|
||||
PAll0,
|
||||
PAll1,
|
||||
PAll2,
|
||||
PAll3,
|
||||
|
||||
PAny,
|
||||
PAnyCallPred,
|
||||
PAnyCheck,
|
||||
PAny0,
|
||||
PAny1,
|
||||
PAny2,
|
||||
PAny3,
|
||||
|
||||
PAppendContext,
|
||||
PAppendContextLoop,
|
||||
@@ -182,9 +184,12 @@ pub enum Continuation {
|
||||
PConcatStringsSep,
|
||||
PConvertHash,
|
||||
|
||||
PDeepSeq,
|
||||
PDeepSeqPush,
|
||||
PDeepSeqLoop,
|
||||
PDeepSeq0,
|
||||
PDeepSeq1,
|
||||
PDeepSeq2,
|
||||
PDeepSeq3,
|
||||
PDeepSeq4,
|
||||
PDeepSeq5,
|
||||
|
||||
PDerivation,
|
||||
PDerivationStrict,
|
||||
@@ -198,19 +203,21 @@ pub enum Continuation {
|
||||
PFetchTree,
|
||||
PFetchUrl,
|
||||
|
||||
PFilterForceList,
|
||||
PFilterSetupStack,
|
||||
PFilterCallPred,
|
||||
PFilterCheck,
|
||||
PFilter0,
|
||||
PFilter1,
|
||||
PFilter2,
|
||||
PFilter3,
|
||||
PFilter4,
|
||||
|
||||
PFilterSource,
|
||||
PFindFile,
|
||||
PFloor,
|
||||
PFoldlStrict,
|
||||
PFoldlStrictEmpty,
|
||||
PFoldlStrictCall1,
|
||||
PFoldlStrictCall2,
|
||||
PFoldlStrictUpdate,
|
||||
PFoldlStrict0,
|
||||
PFoldlStrict1,
|
||||
PFoldlStrict2,
|
||||
PFoldlStrict3,
|
||||
PFoldlStrict4,
|
||||
PFoldlStrict5,
|
||||
PFromJSON,
|
||||
PFromTOML,
|
||||
PFunctionArgs,
|
||||
@@ -258,7 +265,8 @@ pub enum Continuation {
|
||||
PReadFileType,
|
||||
PRemoveAttrs,
|
||||
PReplaceStrings,
|
||||
PSeq,
|
||||
PSeq0,
|
||||
PSeq1,
|
||||
PSort,
|
||||
PSplit,
|
||||
PSplitVersion,
|
||||
@@ -319,8 +327,8 @@ impl Continuation {
|
||||
Abort => Self::PAbort,
|
||||
Add => Self::PAdd,
|
||||
AddErrorContext => Self::PAddErrorContext,
|
||||
All => Self::PAll,
|
||||
Any => Self::PAny,
|
||||
All => Self::PAll0,
|
||||
Any => Self::PAny0,
|
||||
AppendContext => Self::PAppendContext,
|
||||
AttrNames => Self::PAttrNames,
|
||||
AttrValues => Self::PAttrValues,
|
||||
@@ -336,7 +344,7 @@ impl Continuation {
|
||||
ConcatMap => Self::PConcatMap,
|
||||
ConcatStringsSep => Self::PConcatStringsSep,
|
||||
ConvertHash => Self::PConvertHash,
|
||||
DeepSeq => Self::PDeepSeq,
|
||||
DeepSeq => Self::PDeepSeq0,
|
||||
Derivation => Self::PDerivation,
|
||||
DerivationStrict => Self::PDerivationStrict,
|
||||
DirOf => Self::PDirOf,
|
||||
@@ -348,11 +356,11 @@ impl Continuation {
|
||||
FetchTarball => Self::PFetchTarball,
|
||||
FetchTree => Self::PFetchTree,
|
||||
FetchUrl => Self::PFetchUrl,
|
||||
Filter => Self::PFilterForceList,
|
||||
Filter => Self::PFilter0,
|
||||
FilterSource => Self::PFilterSource,
|
||||
FindFile => Self::PFindFile,
|
||||
Floor => Self::PFloor,
|
||||
FoldlStrict => Self::PFoldlStrict,
|
||||
FoldlStrict => Self::PFoldlStrict0,
|
||||
FromJSON => Self::PFromJSON,
|
||||
FromTOML => Self::PFromTOML,
|
||||
FunctionArgs => Self::PFunctionArgs,
|
||||
@@ -396,7 +404,7 @@ impl Continuation {
|
||||
RemoveAttrs => Self::PRemoveAttrs,
|
||||
ReplaceStrings => Self::PReplaceStrings,
|
||||
ScopedImport => Self::PScopedImport,
|
||||
Seq => Self::PSeq,
|
||||
Seq => Self::PSeq0,
|
||||
Sort => Self::PSort,
|
||||
Split => Self::PSplit,
|
||||
SplitVersion => Self::PSplitVersion,
|
||||
|
||||
@@ -466,7 +466,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
body: GhostRoIrRef<'id, 'ir>,
|
||||
}
|
||||
|
||||
let (ret, thunks) = ctx.with_thunk_scope(|ctx| {
|
||||
let (ret, thunks) = ctx.with_thunk_scope(|ctx| -> Result<Ret> {
|
||||
let (param, body) = match raw_param {
|
||||
ast::Param::IdentParam(id) => {
|
||||
let param_sym = ctx.intern_string(id.to_string());
|
||||
@@ -506,7 +506,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
};
|
||||
|
||||
Result::Ok(Ret { param, body })
|
||||
Ok(Ret { param, body })
|
||||
});
|
||||
let Ret { param, body } = ret?;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Box<Error>>;
|
||||
pub type Result<T, E = Box<Error>> = core::result::Result<T, E>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SourceType {
|
||||
|
||||
@@ -9,7 +9,7 @@ proc-macro = true
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "3.0", features = ["full", "visit-mut"] }
|
||||
syn = { version = "3.0", features = ["full", "visit", "visit-mut"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,15 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
mod handler;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn handler(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
handler::handler(attr.into(), item.into()).into()
|
||||
}
|
||||
|
||||
// Adapted from `__unelide_lifetimes` in `gc-arena-derive`.
|
||||
// Licensed under the MIT license.
|
||||
// See: https://github.com/kyren/gc-arena
|
||||
|
||||
@@ -2,6 +2,7 @@ mod boxing;
|
||||
mod forced;
|
||||
mod host;
|
||||
mod machine;
|
||||
mod macro_support;
|
||||
mod path_util;
|
||||
mod resolve;
|
||||
mod slot;
|
||||
@@ -13,6 +14,7 @@ pub use fix_bytecode::{BytecodeReader, OperandData};
|
||||
pub use forced::*;
|
||||
pub use host::*;
|
||||
pub use machine::*;
|
||||
pub use macro_support::*;
|
||||
pub use path_util::*;
|
||||
pub use resolve::*;
|
||||
pub use slot::*;
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use gc_arena::Mutation;
|
||||
|
||||
use crate::{
|
||||
Break, BytecodeReader, Forced, Machine, MachineExt, Slot, SlotContent, Step, StrictValue,
|
||||
Value, ValueVariant,
|
||||
};
|
||||
|
||||
pub trait UnwrapSlot {
|
||||
type Unwrapped;
|
||||
}
|
||||
|
||||
impl<T> UnwrapSlot for Slot<T> {
|
||||
type Unwrapped = T;
|
||||
}
|
||||
|
||||
/// Force-target protocol behind the `#[primop]` `force` form: `Value` only
|
||||
/// needs WHNF, `StrictValue` *is* WHNF, and content types are checked through
|
||||
/// [`Forced`] on their stored representation.
|
||||
pub trait ForceTarget<'gc>: SlotContent<'gc> {
|
||||
/// Type-check the stack slot refined by the previous `force(slot)`; the
|
||||
/// slot is already WHNF when this runs.
|
||||
fn refine_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
depth: usize,
|
||||
) -> Step;
|
||||
|
||||
/// Pop and convert the previous suspension's result into the annotated
|
||||
/// binding's stored representation.
|
||||
fn conv_force<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break, <Self as SlotContent<'gc>>::Ty>;
|
||||
}
|
||||
|
||||
impl<'gc> ForceTarget<'gc> for Value<'gc> {
|
||||
#[inline(always)]
|
||||
fn refine_check<M: Machine<'gc>>(
|
||||
_m: &mut M,
|
||||
_reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
_depth: usize,
|
||||
) -> Step {
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn conv_force<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break, Value<'gc>> {
|
||||
match m.force_and_retry::<StrictValue<'gc>>(reader, mc) {
|
||||
ControlFlow::Continue(v) => ControlFlow::Continue(v.relax()),
|
||||
ControlFlow::Break(b) => ControlFlow::Break(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> ForceTarget<'gc> for StrictValue<'gc> {
|
||||
#[inline(always)]
|
||||
fn refine_check<M: Machine<'gc>>(
|
||||
_m: &mut M,
|
||||
_reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
_depth: usize,
|
||||
) -> Step {
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn conv_force<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break, StrictValue<'gc>> {
|
||||
m.force_and_retry::<StrictValue<'gc>>(reader, mc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc, T> ForceTarget<'gc> for T
|
||||
where
|
||||
T: ValueVariant<'gc>,
|
||||
<T as ValueVariant<'gc>>::Ty: Forced<'gc>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn refine_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
depth: usize,
|
||||
) -> Step {
|
||||
<<T as ValueVariant<'gc>>::Ty as Forced<'gc>>::force_and_check(
|
||||
m,
|
||||
reader,
|
||||
mc,
|
||||
depth,
|
||||
reader.inst_start_pc(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn conv_force<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break, <T as SlotContent<'gc>>::Ty> {
|
||||
m.force_and_retry::<<T as ValueVariant<'gc>>::Ty>(reader, mc)
|
||||
}
|
||||
}
|
||||
|
||||
/// Callee protocol behind `call(&slot, ..)`: an unforced `Slot<Value>` is
|
||||
/// WHNF'd before the call; refined slots already hold strict values.
|
||||
pub trait CalleeReady<'gc> {
|
||||
fn callee_ready<M: Machine<'gc>>(
|
||||
&self,
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break>;
|
||||
}
|
||||
|
||||
impl<'gc> CalleeReady<'gc> for Slot<Value<'gc>> {
|
||||
#[inline(always)]
|
||||
fn callee_ready<M: Machine<'gc>>(
|
||||
&self,
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break> {
|
||||
self.force::<StrictValue<'gc>, M>(m, reader, mc)?;
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> CalleeReady<'gc> for Slot<StrictValue<'gc>> {
|
||||
#[inline(always)]
|
||||
fn callee_ready<M: Machine<'gc>>(
|
||||
&self,
|
||||
_m: &mut M,
|
||||
_reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break> {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc, T> CalleeReady<'gc> for Slot<T>
|
||||
where
|
||||
T: ValueVariant<'gc>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn callee_ready<M: Machine<'gc>>(
|
||||
&self,
|
||||
_m: &mut M,
|
||||
_reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break> {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,11 @@ use gc_arena::Mutation;
|
||||
|
||||
use crate::{Break, Forced, Machine, NixType, Step, StrictValue, Value, ValueVariant};
|
||||
|
||||
pub struct TypeError {
|
||||
pub expected: NixType,
|
||||
pub got: NixType,
|
||||
}
|
||||
|
||||
pub trait SlotContent<'gc> {
|
||||
type Ty: Into<Value<'gc>> + TryFrom<Value<'gc>> + 'gc;
|
||||
}
|
||||
@@ -50,12 +55,6 @@ where
|
||||
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));
|
||||
|
||||
@@ -165,7 +165,7 @@ pub(crate) fn op_return<'gc, M: Machine<'gc>>(
|
||||
depth: None,
|
||||
});
|
||||
m.inc_call_depth();
|
||||
reader.set_pc(Continuation::PDeepSeq.ip() as usize);
|
||||
reader.set_pc(Continuation::PDeepSeq0.ip() as usize);
|
||||
return Step::Continue(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,12 @@ use smallvec::SmallVec;
|
||||
#[cfg(feature = "tailcall")]
|
||||
mod dispatch_tailcall;
|
||||
pub use fix_runtime::*;
|
||||
#[doc(hidden)]
|
||||
#[path = "macro_support.rs"]
|
||||
pub mod __macro_support;
|
||||
mod instructions;
|
||||
mod primops;
|
||||
extern crate self as fix_vm;
|
||||
|
||||
type VmResult<T> = std::result::Result<T, VmError>;
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//! Single macro-facing path for the `#[primop]` support surface: the traits
|
||||
//! generated code type-checks through (defined in `fix_runtime`, where the
|
||||
//! impl sets are coherent) and the stub trio that keeps un-expanded primop
|
||||
//! sources resolving in tooling.
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
pub use fix_runtime::{CalleeReady, ForceTarget, UnwrapSlot};
|
||||
use fix_runtime::{Slot, Value};
|
||||
|
||||
macro_rules! type_hint {
|
||||
() => {
|
||||
if false {
|
||||
return async {
|
||||
unreachable!();
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[expect(clippy::panic, reason = "deliberately panic when the stub is called")]
|
||||
pub fn force<T>(_: Slot<Value<'_>>) -> impl Future<Output = Slot<T>> {
|
||||
type_hint!();
|
||||
panic!("stub `force` called at runtime")
|
||||
}
|
||||
|
||||
#[expect(clippy::panic, reason = "deliberately panic when the stub is called")]
|
||||
pub fn call<T, A, R>(_: &T, _: A) -> impl Future<Output = R> {
|
||||
type_hint!();
|
||||
panic!("stub `call` called at runtime")
|
||||
}
|
||||
|
||||
#[expect(clippy::panic, reason = "deliberately panic when the stub is called")]
|
||||
pub fn spill<T>(_: T) -> Slot<T> {
|
||||
panic!("stub `spill` called at runtime")
|
||||
}
|
||||
+79
-154
@@ -1,22 +1,89 @@
|
||||
use fix_bytecode::Continuation;
|
||||
use fix_error::Error;
|
||||
use fix_error::{Error, Result};
|
||||
use fix_macros::handler;
|
||||
use fix_runtime::{
|
||||
AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Step, StrictValue, Value,
|
||||
VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Slot, Step, StrictValue,
|
||||
Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
};
|
||||
use gc_arena::{Gc, Mutation, RefLock};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub fn seq<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
use crate::primops::stubs::*;
|
||||
|
||||
#[handler(name = PSeq)]
|
||||
fn seq<'gc>(mc: &Mutation<'gc>, e1: Slot<Value<'gc>>, e2: Slot<Value<'gc>>) -> Result<Value<'gc>> {
|
||||
let _e1: Slot<StrictValue<'gc>> = force(e1).await?;
|
||||
Ok(e2.get())
|
||||
}
|
||||
|
||||
#[handler(name = PDeepSeq)]
|
||||
fn deep_seq<'gc>(
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
m.force_slot(1, reader, mc)?;
|
||||
let e2 = m.pop();
|
||||
m.drop_n(1);
|
||||
m.return_from_primop(e2, reader)
|
||||
e1: Slot<Value<'gc>>,
|
||||
e2: Slot<Value<'gc>>,
|
||||
) -> Result<Value<'gc>> {
|
||||
let e1: Slot<StrictValue<'gc>> = force(e1).await?;
|
||||
if collect_children(e1.get()).is_empty() {
|
||||
return Ok(e2.get());
|
||||
}
|
||||
let seen: Slot<List<'gc>> = spill(Gc::new(mc, List::default()));
|
||||
let worklist: Slot<List<'gc>> = spill(List::new(mc, collect_children(e1.get())));
|
||||
let count: Slot<i32> = spill(worklist.get().inner.borrow().len() as i32);
|
||||
loop {
|
||||
if count.get() == 0 {
|
||||
return Ok(e2.get());
|
||||
}
|
||||
let item = worklist
|
||||
.get()
|
||||
.unlock(mc)
|
||||
.borrow_mut()
|
||||
.pop()
|
||||
.expect("worklist is non-empty while count > 0");
|
||||
count.set(count.get() - 1);
|
||||
let item: Slot<StrictValue<'gc>> = force(item).await?;
|
||||
let added = push_children(mc, seen.get(), worklist.get(), item);
|
||||
count.set(count.get() + added);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_children<'gc>(val: StrictValue<'gc>) -> SmallVec<[Value<'gc>; 4]> {
|
||||
if let Some(attrs) = val.downcast::<AttrSet<'gc>>() {
|
||||
attrs.entries.iter().map(|&(_, v)| v).collect()
|
||||
} else if let Some(list) = val.downcast::<List<'gc>>() {
|
||||
list.inner.borrow().iter().copied().collect()
|
||||
} else {
|
||||
SmallVec::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn push_children<'gc>(
|
||||
mc: &Mutation<'gc>,
|
||||
seen: Gc<'gc, List<'gc>>,
|
||||
worklist: Gc<'gc, List<'gc>>,
|
||||
item: StrictValue<'gc>,
|
||||
) -> i32 {
|
||||
let mut added = 0i32;
|
||||
if let Some(attrs) = item.downcast::<AttrSet<'gc>>()
|
||||
&& !is_value_in_seen(seen, item.relax())
|
||||
{
|
||||
add_value_to_seen(seen, mc, item.relax());
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &(_, v) in attrs.entries.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = attrs.entries.len() as i32;
|
||||
} else if let Some(list) = item.downcast::<List<'gc>>()
|
||||
&& !is_value_in_seen(seen, item.relax())
|
||||
{
|
||||
add_value_to_seen(seen, mc, item.relax());
|
||||
let inner = list.inner.borrow();
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &v in inner.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = inner.len() as i32;
|
||||
}
|
||||
added
|
||||
}
|
||||
|
||||
pub fn abort<'gc, M: Machine<'gc>>(
|
||||
@@ -34,148 +101,6 @@ pub fn abort<'gc, M: Machine<'gc>>(
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn deep_seq_force_top<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
m.force_slot(1, reader, mc)?;
|
||||
|
||||
let e1 = m.peek_forced(1);
|
||||
|
||||
let children: SmallVec<_> = if let Some(attrs) = e1.downcast::<AttrSet>() {
|
||||
let attrs = &attrs.entries;
|
||||
if attrs.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
attrs.iter().map(|&(_, v)| v).collect()
|
||||
}
|
||||
} else if let Some(list) = e1.downcast::<List>() {
|
||||
let inner = list.inner.borrow();
|
||||
if inner.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
inner.iter().copied().collect()
|
||||
}
|
||||
} else {
|
||||
SmallVec::new()
|
||||
};
|
||||
|
||||
if children.is_empty() {
|
||||
let e2 = m.pop();
|
||||
m.drop_n(1);
|
||||
return m.return_from_primop(e2, reader);
|
||||
}
|
||||
|
||||
let count = children.len() as i32;
|
||||
let seen = Gc::new(mc, List::default());
|
||||
let worklist = List::new(mc, children);
|
||||
|
||||
let e2 = m.pop();
|
||||
m.drop_n(1);
|
||||
m.push(e2);
|
||||
m.push(Value::new(seen));
|
||||
m.push(Value::new(worklist));
|
||||
m.push(Value::new(count));
|
||||
reader.set_pc(Continuation::PDeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn deep_seq_push<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e2, seen, worklist, counter]
|
||||
let counter = m
|
||||
.peek(0)
|
||||
.downcast::<i32>()
|
||||
.expect("stack slot must be an integer");
|
||||
if counter == 0 {
|
||||
m.drop_n(3);
|
||||
let val = m.pop();
|
||||
return m.return_from_primop(val, reader);
|
||||
}
|
||||
|
||||
let worklist = m
|
||||
.peek_forced(1)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list");
|
||||
let item = worklist
|
||||
.unlock(mc)
|
||||
.borrow_mut()
|
||||
.pop()
|
||||
.expect("worklist is non-empty while counter > 0");
|
||||
m.replace(0, Value::new(counter - 1));
|
||||
m.push(item);
|
||||
|
||||
// force item at TOS, resume at DeepSeqLoop after force
|
||||
m.force_slot_to_pc(0, reader, mc, Continuation::PDeepSeqLoop.ip() as usize)?;
|
||||
reader.set_pc(Continuation::PDeepSeqLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack after pop: [e2, seen, worklist, counter]
|
||||
let item = m.pop();
|
||||
let counter = m
|
||||
.peek(0)
|
||||
.downcast::<i32>()
|
||||
.expect("stack slot must be an integer");
|
||||
|
||||
let mut added: usize = 0;
|
||||
if let Some(attrs) = item.downcast::<AttrSet>() {
|
||||
let attrs = &attrs.entries;
|
||||
let seen = m
|
||||
.peek_forced(2)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list");
|
||||
if !is_value_in_seen(seen, item) {
|
||||
add_value_to_seen(seen, mc, item);
|
||||
let worklist = m
|
||||
.peek_forced(1)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list");
|
||||
{
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &(_, v) in attrs.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = attrs.len();
|
||||
}
|
||||
}
|
||||
} else if let Some(list) = item.downcast::<List>() {
|
||||
let seen = m
|
||||
.peek_forced(2)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list");
|
||||
if !is_value_in_seen(seen, item) {
|
||||
add_value_to_seen(seen, mc, item);
|
||||
let worklist = m
|
||||
.peek_forced(1)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list");
|
||||
{
|
||||
let inner = list.inner.borrow();
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &v in inner.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = inner.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.replace(0, Value::new(counter + added as i32));
|
||||
reader.set_pc(Continuation::PDeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn force_result_shallow<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
|
||||
+99
-379
@@ -1,396 +1,116 @@
|
||||
use fix_bytecode::Continuation;
|
||||
use fix_runtime::{
|
||||
BytecodeReader, List, Machine, MachineExt, NixType, Slot, Step, StrictValue, Value,
|
||||
};
|
||||
use fix_error::Result;
|
||||
use fix_macros::handler;
|
||||
use fix_runtime::{List, Slot, StrictValue, Value};
|
||||
use gc_arena::Mutation;
|
||||
|
||||
use crate::slots;
|
||||
use crate::primops::stubs::*;
|
||||
|
||||
pub mod filter {
|
||||
use super::*;
|
||||
|
||||
#[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!(),
|
||||
#[handler(name = PFilter)]
|
||||
fn filter<'gc>(
|
||||
mc: &Mutation<'gc>,
|
||||
pred: Slot<Value<'gc>>,
|
||||
list: Slot<Value<'gc>>,
|
||||
) -> Result<Value<'gc>> {
|
||||
let list: Slot<List<'gc>> = force(list).await?;
|
||||
if list.get().inner.borrow().is_empty() {
|
||||
return Ok(Value::new(list.get()));
|
||||
}
|
||||
let idx: Slot<i32> = spill(0i32);
|
||||
let acc: Slot<List<'gc>> = spill(List::new_gc(mc));
|
||||
loop {
|
||||
#[expect(
|
||||
clippy::indexing_slicing,
|
||||
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||
)]
|
||||
let keep: bool = call(&pred, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||
if keep {
|
||||
#[expect(
|
||||
clippy::indexing_slicing,
|
||||
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||
)]
|
||||
let elem = list.get().inner.borrow()[idx.get() as usize];
|
||||
acc.get().unlock(mc).borrow_mut().push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if idx.get() as usize == list.get().inner.borrow().len() - 1 {
|
||||
return Ok(Value::new(acc.get()));
|
||||
}
|
||||
// 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(())
|
||||
idx.set(idx.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
#[handler(name = PAll)]
|
||||
fn all<'gc>(
|
||||
mc: &Mutation<'gc>,
|
||||
pred: Slot<Value<'gc>>,
|
||||
list: Slot<Value<'gc>>,
|
||||
) -> Result<Value<'gc>> {
|
||||
let list: Slot<List<'gc>> = force(list).await?;
|
||||
if list.get().inner.borrow().is_empty() {
|
||||
return Ok(Value::new(true));
|
||||
}
|
||||
|
||||
#[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]);
|
||||
let idx: Slot<i32> = spill(0i32);
|
||||
loop {
|
||||
#[expect(
|
||||
clippy::indexing_slicing,
|
||||
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||
)]
|
||||
let keep: bool = call(&pred, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||
if !keep || idx.get() as usize + 1 == list.get().inner.borrow().len() {
|
||||
return Ok(Value::new(keep));
|
||||
}
|
||||
if old_idx as usize == list.len() - 1 {
|
||||
let acc = m.pop();
|
||||
m.drop_n(3);
|
||||
return m.return_from_primop(acc, reader);
|
||||
idx.set(idx.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[handler(name = PAny)]
|
||||
fn any<'gc>(
|
||||
mc: &Mutation<'gc>,
|
||||
pred: Slot<Value<'gc>>,
|
||||
list: Slot<Value<'gc>>,
|
||||
) -> Result<Value<'gc>> {
|
||||
let list: Slot<List<'gc>> = force(list).await?;
|
||||
if list.get().inner.borrow().is_empty() {
|
||||
return Ok(Value::new(false));
|
||||
}
|
||||
let idx: Slot<i32> = spill(0i32);
|
||||
loop {
|
||||
#[expect(
|
||||
clippy::indexing_slicing,
|
||||
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||
)]
|
||||
let keep: bool = call(&pred, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||
if keep || idx.get() as usize + 1 == list.get().inner.borrow().len() {
|
||||
return Ok(Value::new(keep));
|
||||
}
|
||||
idx.write(m, old_idx + 1);
|
||||
reader.set_pc(Continuation::PFilterCallPred.ip() as usize);
|
||||
Step::Continue(())
|
||||
idx.set(idx.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// foldl' op nul list
|
||||
//
|
||||
// Stack layouts across phases:
|
||||
// Entry: [op, nul, list]
|
||||
// Empty: [op, nul]
|
||||
// Call1: [op, list, idx, acc]
|
||||
// Call2: [op, list, idx, acc, intermediate]
|
||||
// Update: [op, list, idx, acc, result]
|
||||
pub fn foldl_strict_entry<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
#[handler(name = PFoldlStrict)]
|
||||
fn foldl_strict<'gc>(
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
m.force_slot(0, reader, mc)?;
|
||||
let list_val = m.peek_forced(0);
|
||||
let Some(list) = list_val.downcast::<List>() else {
|
||||
return m.finish_type_err(NixType::List, list_val.ty());
|
||||
};
|
||||
if list.inner.borrow().is_empty() {
|
||||
m.drop_n(1);
|
||||
reader.set_pc(Continuation::PFoldlStrictEmpty.ip() as usize);
|
||||
return Step::Continue(());
|
||||
op: Slot<Value<'gc>>,
|
||||
nul: Slot<Value<'gc>>,
|
||||
list: Slot<Value<'gc>>,
|
||||
) -> Result<Value<'gc>> {
|
||||
let list: Slot<List<'gc>> = force(list).await?;
|
||||
if list.get().inner.borrow().is_empty() {
|
||||
return Ok(nul.get());
|
||||
}
|
||||
let list_val = m.pop();
|
||||
let nul_val = m.pop();
|
||||
m.push(list_val);
|
||||
m.push(Value::new(0i32));
|
||||
m.push(nul_val);
|
||||
reader.set_pc(Continuation::PFoldlStrictCall1.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn foldl_strict_empty<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let nul = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
m.drop_n(1);
|
||||
m.return_from_primop(nul.relax(), reader)
|
||||
}
|
||||
|
||||
pub fn foldl_strict_call1<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
m.force_slot(3, reader, mc)?;
|
||||
let op = m.peek_forced(3);
|
||||
let acc = m.peek(0);
|
||||
m.push(op.relax());
|
||||
m.call(
|
||||
reader,
|
||||
mc,
|
||||
acc,
|
||||
Continuation::PFoldlStrictCall2.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 foldl_strict_call2<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let idx = m
|
||||
.peek(2)
|
||||
.downcast::<i32>()
|
||||
.expect("stack slot must be an integer");
|
||||
let list = m
|
||||
.peek_forced(3)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list");
|
||||
let elem = list.inner.borrow()[idx as usize];
|
||||
m.call(
|
||||
reader,
|
||||
mc,
|
||||
elem,
|
||||
Continuation::PFoldlStrictUpdate.ip() as usize,
|
||||
)
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::cast_sign_loss,
|
||||
reason = "idx is a non-negative loop counter in 0..list.len()"
|
||||
)]
|
||||
pub fn foldl_strict_update<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let result = m.pop();
|
||||
m.replace(0, result);
|
||||
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 len = list.inner.borrow().len();
|
||||
if (idx as usize) + 1 == len {
|
||||
let acc = m.pop();
|
||||
m.drop_n(3);
|
||||
return m.return_from_primop(acc, reader);
|
||||
let idx: Slot<i32> = spill(0i32);
|
||||
let acc = spill(nul.get());
|
||||
loop {
|
||||
let f = call(&op, acc.get()).await?;
|
||||
#[expect(
|
||||
clippy::indexing_slicing,
|
||||
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||
)]
|
||||
let new_acc: StrictValue<'gc> =
|
||||
call(&f, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||
acc.set(new_acc.relax());
|
||||
if idx.get() as usize + 1 == list.get().inner.borrow().len() {
|
||||
return Ok(acc.get());
|
||||
}
|
||||
idx.set(idx.get() + 1);
|
||||
}
|
||||
m.replace(1, Value::new(idx + 1));
|
||||
reader.set_pc(Continuation::PFoldlStrictCall1.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn all_entry<'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),
|
||||
};
|
||||
// FIXME: force callable
|
||||
m.force_slot(1, reader, mc)?;
|
||||
if list.inner.borrow().is_empty() {
|
||||
let _list = m.pop();
|
||||
let _pred = m.pop();
|
||||
return m.return_from_primop(Value::new(true), reader);
|
||||
}
|
||||
// prepare stack layout: [ pred list idx ]
|
||||
m.push(Value::new(0));
|
||||
reader.set_pc(Continuation::PAllCallPred.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"
|
||||
)]
|
||||
pub fn all_call_pred<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let pred = m.peek_forced(2);
|
||||
let idx = m
|
||||
.peek(0)
|
||||
.downcast::<i32>()
|
||||
.expect("stack slot must be an integer");
|
||||
let elem = m
|
||||
.peek_forced(1)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list")
|
||||
.inner
|
||||
.borrow()[idx as usize];
|
||||
m.push(pred.relax());
|
||||
m.call(reader, mc, elem, Continuation::PAllCheck.ip() as usize)
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::cast_sign_loss,
|
||||
reason = "idx is a non-negative loop counter in 0..list.len()"
|
||||
)]
|
||||
pub fn all_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(0)
|
||||
.downcast::<i32>()
|
||||
.expect("stack slot must be an integer");
|
||||
let list = m
|
||||
.peek_forced(1)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list");
|
||||
let list = list.inner.borrow();
|
||||
if idx as usize == list.len() - 1 || !ret {
|
||||
m.drop_n(3);
|
||||
return m.return_from_primop(Value::new(ret), reader);
|
||||
}
|
||||
m.replace(0, Value::new(idx + 1));
|
||||
reader.set_pc(Continuation::PAllCallPred.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn any_entry<'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),
|
||||
};
|
||||
// FIXME: force callable
|
||||
m.force_slot(1, reader, mc)?;
|
||||
if list.inner.borrow().is_empty() {
|
||||
let _list = m.pop();
|
||||
let _pred = m.pop();
|
||||
return m.return_from_primop(Value::new(false), reader);
|
||||
}
|
||||
// prepare stack layout: [ pred list idx ]
|
||||
m.push(Value::new(0));
|
||||
reader.set_pc(Continuation::PAnyCallPred.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"
|
||||
)]
|
||||
pub fn any_call_pred<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let pred = m.peek_forced(2);
|
||||
let idx = m
|
||||
.peek(0)
|
||||
.downcast::<i32>()
|
||||
.expect("stack slot must be an integer");
|
||||
let elem = m
|
||||
.peek_forced(1)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list")
|
||||
.inner
|
||||
.borrow()[idx as usize];
|
||||
m.push(pred.relax());
|
||||
m.call(reader, mc, elem, Continuation::PAnyCheck.ip() as usize)
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::cast_sign_loss,
|
||||
reason = "idx is a non-negative loop counter in 0..list.len()"
|
||||
)]
|
||||
pub fn any_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(0)
|
||||
.downcast::<i32>()
|
||||
.expect("stack slot must be an integer");
|
||||
let list = m
|
||||
.peek_forced(1)
|
||||
.downcast::<List>()
|
||||
.expect("stack slot must be a list");
|
||||
let list = list.inner.borrow();
|
||||
if idx as usize == list.len() - 1 || ret {
|
||||
m.drop_n(3);
|
||||
return m.return_from_primop(Value::new(ret), reader);
|
||||
}
|
||||
m.replace(0, Value::new(idx + 1));
|
||||
reader.set_pc(Continuation::PAnyCallPred.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
+11
-41
@@ -5,6 +5,7 @@ mod eq;
|
||||
mod io;
|
||||
mod list;
|
||||
mod path;
|
||||
mod stubs;
|
||||
|
||||
pub use context::*;
|
||||
pub use control::*;
|
||||
@@ -18,27 +19,6 @@ 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,
|
||||
@@ -53,28 +33,18 @@ pub fn dispatch_cont<'gc, M: Machine<'gc>>(
|
||||
match cont {
|
||||
PAbort => abort(m, ctx, reader, mc),
|
||||
|
||||
PAll => all_entry(m, reader, mc),
|
||||
PAllCallPred => all_call_pred(m, reader, mc),
|
||||
PAllCheck => all_check(m, reader, mc),
|
||||
|
||||
PAny => any_entry(m, reader, mc),
|
||||
PAnyCallPred => any_call_pred(m, reader, mc),
|
||||
PAnyCheck => any_check(m, reader, mc),
|
||||
|
||||
PDeepSeq => deep_seq_force_top(m, reader, mc),
|
||||
PDeepSeqPush => deep_seq_push(m, reader, mc),
|
||||
PDeepSeqLoop => deep_seq_loop(m, reader, mc),
|
||||
PSeq => seq(m, reader, mc),
|
||||
|
||||
PFilterForceList | PFilterSetupStack | PFilterCallPred | PFilterCheck => {
|
||||
filter::dispatch(m, reader, mc, cont)
|
||||
PAll0 | PAll1 | PAll2 | PAll3 => all(m, reader, mc, cont),
|
||||
PAny0 | PAny1 | PAny2 | PAny3 => any(m, reader, mc, cont),
|
||||
PDeepSeq0 | PDeepSeq1 | PDeepSeq2 | PDeepSeq3 | PDeepSeq4 | PDeepSeq5 => {
|
||||
deep_seq(m, reader, mc, cont)
|
||||
}
|
||||
PSeq0 | PSeq1 => seq(m, reader, mc, cont),
|
||||
|
||||
PFoldlStrict => foldl_strict_entry(m, reader, mc),
|
||||
PFoldlStrictEmpty => foldl_strict_empty(m, reader, mc),
|
||||
PFoldlStrictCall1 => foldl_strict_call1(m, reader, mc),
|
||||
PFoldlStrictCall2 => foldl_strict_call2(m, reader, mc),
|
||||
PFoldlStrictUpdate => foldl_strict_update(m, reader, mc),
|
||||
PFilter0 | PFilter1 | PFilter2 | PFilter3 | PFilter4 => filter(m, reader, mc, cont),
|
||||
|
||||
PFoldlStrict0 | PFoldlStrict1 | PFoldlStrict2 | PFoldlStrict3 | PFoldlStrict4 | PFoldlStrict5 => {
|
||||
foldl_strict(m, reader, mc, cont)
|
||||
}
|
||||
|
||||
ForceResultShallow => force_result_shallow(m, ctx, reader, mc),
|
||||
ForceResultShallowPush => force_result_shallow_push(m, ctx, reader, mc),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//! Fallback definitions for the `#[primop]` surface syntax.
|
||||
//!
|
||||
//! `force`, `call`, and `spill` are recognized and rewritten by the
|
||||
//! `#[primop]` proc macro, so compiled code never calls them. They exist only
|
||||
//! so the un-expanded source name-resolves in tooling that does not run the
|
||||
//! macro (e.g. rust-analyzer while the macro crate is being rebuilt). Calling
|
||||
//! one for real is impossible: they never return.
|
||||
|
||||
pub use crate::__macro_support::{call, force, spill};
|
||||
Reference in New Issue
Block a user