macros, vm, bytecode: add #[primop] await-style primop macro
This commit is contained in:
+33
-25
@@ -152,13 +152,15 @@ pub enum Continuation {
|
|||||||
PAdd,
|
PAdd,
|
||||||
PAddErrorContext,
|
PAddErrorContext,
|
||||||
|
|
||||||
PAll,
|
PAll0,
|
||||||
PAllCallPred,
|
PAll1,
|
||||||
PAllCheck,
|
PAll2,
|
||||||
|
PAll3,
|
||||||
|
|
||||||
PAny,
|
PAny0,
|
||||||
PAnyCallPred,
|
PAny1,
|
||||||
PAnyCheck,
|
PAny2,
|
||||||
|
PAny3,
|
||||||
|
|
||||||
PAppendContext,
|
PAppendContext,
|
||||||
PAppendContextLoop,
|
PAppendContextLoop,
|
||||||
@@ -182,9 +184,12 @@ pub enum Continuation {
|
|||||||
PConcatStringsSep,
|
PConcatStringsSep,
|
||||||
PConvertHash,
|
PConvertHash,
|
||||||
|
|
||||||
PDeepSeq,
|
PDeepSeq0,
|
||||||
PDeepSeqPush,
|
PDeepSeq1,
|
||||||
PDeepSeqLoop,
|
PDeepSeq2,
|
||||||
|
PDeepSeq3,
|
||||||
|
PDeepSeq4,
|
||||||
|
PDeepSeq5,
|
||||||
|
|
||||||
PDerivation,
|
PDerivation,
|
||||||
PDerivationStrict,
|
PDerivationStrict,
|
||||||
@@ -198,19 +203,21 @@ pub enum Continuation {
|
|||||||
PFetchTree,
|
PFetchTree,
|
||||||
PFetchUrl,
|
PFetchUrl,
|
||||||
|
|
||||||
PFilterForceList,
|
PFilter0,
|
||||||
PFilterSetupStack,
|
PFilter1,
|
||||||
PFilterCallPred,
|
PFilter2,
|
||||||
PFilterCheck,
|
PFilter3,
|
||||||
|
PFilter4,
|
||||||
|
|
||||||
PFilterSource,
|
PFilterSource,
|
||||||
PFindFile,
|
PFindFile,
|
||||||
PFloor,
|
PFloor,
|
||||||
PFoldlStrict,
|
PFoldlStrict0,
|
||||||
PFoldlStrictEmpty,
|
PFoldlStrict1,
|
||||||
PFoldlStrictCall1,
|
PFoldlStrict2,
|
||||||
PFoldlStrictCall2,
|
PFoldlStrict3,
|
||||||
PFoldlStrictUpdate,
|
PFoldlStrict4,
|
||||||
|
PFoldlStrict5,
|
||||||
PFromJSON,
|
PFromJSON,
|
||||||
PFromTOML,
|
PFromTOML,
|
||||||
PFunctionArgs,
|
PFunctionArgs,
|
||||||
@@ -258,7 +265,8 @@ pub enum Continuation {
|
|||||||
PReadFileType,
|
PReadFileType,
|
||||||
PRemoveAttrs,
|
PRemoveAttrs,
|
||||||
PReplaceStrings,
|
PReplaceStrings,
|
||||||
PSeq,
|
PSeq0,
|
||||||
|
PSeq1,
|
||||||
PSort,
|
PSort,
|
||||||
PSplit,
|
PSplit,
|
||||||
PSplitVersion,
|
PSplitVersion,
|
||||||
@@ -319,8 +327,8 @@ impl Continuation {
|
|||||||
Abort => Self::PAbort,
|
Abort => Self::PAbort,
|
||||||
Add => Self::PAdd,
|
Add => Self::PAdd,
|
||||||
AddErrorContext => Self::PAddErrorContext,
|
AddErrorContext => Self::PAddErrorContext,
|
||||||
All => Self::PAll,
|
All => Self::PAll0,
|
||||||
Any => Self::PAny,
|
Any => Self::PAny0,
|
||||||
AppendContext => Self::PAppendContext,
|
AppendContext => Self::PAppendContext,
|
||||||
AttrNames => Self::PAttrNames,
|
AttrNames => Self::PAttrNames,
|
||||||
AttrValues => Self::PAttrValues,
|
AttrValues => Self::PAttrValues,
|
||||||
@@ -336,7 +344,7 @@ impl Continuation {
|
|||||||
ConcatMap => Self::PConcatMap,
|
ConcatMap => Self::PConcatMap,
|
||||||
ConcatStringsSep => Self::PConcatStringsSep,
|
ConcatStringsSep => Self::PConcatStringsSep,
|
||||||
ConvertHash => Self::PConvertHash,
|
ConvertHash => Self::PConvertHash,
|
||||||
DeepSeq => Self::PDeepSeq,
|
DeepSeq => Self::PDeepSeq0,
|
||||||
Derivation => Self::PDerivation,
|
Derivation => Self::PDerivation,
|
||||||
DerivationStrict => Self::PDerivationStrict,
|
DerivationStrict => Self::PDerivationStrict,
|
||||||
DirOf => Self::PDirOf,
|
DirOf => Self::PDirOf,
|
||||||
@@ -348,11 +356,11 @@ impl Continuation {
|
|||||||
FetchTarball => Self::PFetchTarball,
|
FetchTarball => Self::PFetchTarball,
|
||||||
FetchTree => Self::PFetchTree,
|
FetchTree => Self::PFetchTree,
|
||||||
FetchUrl => Self::PFetchUrl,
|
FetchUrl => Self::PFetchUrl,
|
||||||
Filter => Self::PFilterForceList,
|
Filter => Self::PFilter0,
|
||||||
FilterSource => Self::PFilterSource,
|
FilterSource => Self::PFilterSource,
|
||||||
FindFile => Self::PFindFile,
|
FindFile => Self::PFindFile,
|
||||||
Floor => Self::PFloor,
|
Floor => Self::PFloor,
|
||||||
FoldlStrict => Self::PFoldlStrict,
|
FoldlStrict => Self::PFoldlStrict0,
|
||||||
FromJSON => Self::PFromJSON,
|
FromJSON => Self::PFromJSON,
|
||||||
FromTOML => Self::PFromTOML,
|
FromTOML => Self::PFromTOML,
|
||||||
FunctionArgs => Self::PFunctionArgs,
|
FunctionArgs => Self::PFunctionArgs,
|
||||||
@@ -396,7 +404,7 @@ impl Continuation {
|
|||||||
RemoveAttrs => Self::PRemoveAttrs,
|
RemoveAttrs => Self::PRemoveAttrs,
|
||||||
ReplaceStrings => Self::PReplaceStrings,
|
ReplaceStrings => Self::PReplaceStrings,
|
||||||
ScopedImport => Self::PScopedImport,
|
ScopedImport => Self::PScopedImport,
|
||||||
Seq => Self::PSeq,
|
Seq => Self::PSeq0,
|
||||||
Sort => Self::PSort,
|
Sort => Self::PSort,
|
||||||
Split => Self::PSplit,
|
Split => Self::PSplit,
|
||||||
SplitVersion => Self::PSplitVersion,
|
SplitVersion => Self::PSplitVersion,
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
body: GhostRoIrRef<'id, 'ir>,
|
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 {
|
let (param, body) = match raw_param {
|
||||||
ast::Param::IdentParam(id) => {
|
ast::Param::IdentParam(id) => {
|
||||||
let param_sym = ctx.intern_string(id.to_string());
|
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?;
|
let Ret { param, body } = ret?;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
use miette::{Diagnostic, NamedSource, SourceSpan};
|
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||||
use thiserror::Error;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum SourceType {
|
pub enum SourceType {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ proc-macro = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "3.0", features = ["full", "visit-mut"] }
|
syn = { version = "3.0", features = ["full", "visit", "visit-mut"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,15 @@
|
|||||||
extern crate proc_macro;
|
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`.
|
// Adapted from `__unelide_lifetimes` in `gc-arena-derive`.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
// See: https://github.com/kyren/gc-arena
|
// See: https://github.com/kyren/gc-arena
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ mod boxing;
|
|||||||
mod forced;
|
mod forced;
|
||||||
mod host;
|
mod host;
|
||||||
mod machine;
|
mod machine;
|
||||||
|
mod macro_support;
|
||||||
mod path_util;
|
mod path_util;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
mod slot;
|
mod slot;
|
||||||
@@ -13,6 +14,7 @@ pub use fix_bytecode::{BytecodeReader, OperandData};
|
|||||||
pub use forced::*;
|
pub use forced::*;
|
||||||
pub use host::*;
|
pub use host::*;
|
||||||
pub use machine::*;
|
pub use machine::*;
|
||||||
|
pub use macro_support::*;
|
||||||
pub use path_util::*;
|
pub use path_util::*;
|
||||||
pub use resolve::*;
|
pub use resolve::*;
|
||||||
pub use slot::*;
|
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};
|
use crate::{Break, Forced, Machine, NixType, Step, StrictValue, Value, ValueVariant};
|
||||||
|
|
||||||
|
pub struct TypeError {
|
||||||
|
pub expected: NixType,
|
||||||
|
pub got: NixType,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait SlotContent<'gc> {
|
pub trait SlotContent<'gc> {
|
||||||
type Ty: Into<Value<'gc>> + TryFrom<Value<'gc>> + '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")
|
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)]
|
#[inline(always)]
|
||||||
pub fn write<M: Machine<'gc>>(&self, m: &mut M, val: T::Ty) {
|
pub fn write<M: Machine<'gc>>(&self, m: &mut M, val: T::Ty) {
|
||||||
m.replace(self.depth as usize, T::Ty::into(val));
|
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,
|
depth: None,
|
||||||
});
|
});
|
||||||
m.inc_call_depth();
|
m.inc_call_depth();
|
||||||
reader.set_pc(Continuation::PDeepSeq.ip() as usize);
|
reader.set_pc(Continuation::PDeepSeq0.ip() as usize);
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,12 @@ use smallvec::SmallVec;
|
|||||||
#[cfg(feature = "tailcall")]
|
#[cfg(feature = "tailcall")]
|
||||||
mod dispatch_tailcall;
|
mod dispatch_tailcall;
|
||||||
pub use fix_runtime::*;
|
pub use fix_runtime::*;
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[path = "macro_support.rs"]
|
||||||
|
pub mod __macro_support;
|
||||||
mod instructions;
|
mod instructions;
|
||||||
mod primops;
|
mod primops;
|
||||||
|
extern crate self as fix_vm;
|
||||||
|
|
||||||
type VmResult<T> = std::result::Result<T, VmError>;
|
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_bytecode::Continuation;
|
||||||
use fix_error::Error;
|
use fix_error::{Error, Result};
|
||||||
|
use fix_macros::handler;
|
||||||
use fix_runtime::{
|
use fix_runtime::{
|
||||||
AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Step, StrictValue, Value,
|
AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Slot, Step, StrictValue,
|
||||||
VmRuntimeCtx, VmRuntimeCtxExt,
|
Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||||
};
|
};
|
||||||
use gc_arena::{Gc, Mutation, RefLock};
|
use gc_arena::{Gc, Mutation, RefLock};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
pub fn seq<'gc, M: Machine<'gc>>(
|
use crate::primops::stubs::*;
|
||||||
m: &mut M,
|
|
||||||
reader: &mut BytecodeReader<'_>,
|
#[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>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
e1: Slot<Value<'gc>>,
|
||||||
// stack: [e1, e2] - force e1, return e2
|
e2: Slot<Value<'gc>>,
|
||||||
m.force_slot(1, reader, mc)?;
|
) -> Result<Value<'gc>> {
|
||||||
let e2 = m.pop();
|
let e1: Slot<StrictValue<'gc>> = force(e1).await?;
|
||||||
m.drop_n(1);
|
if collect_children(e1.get()).is_empty() {
|
||||||
m.return_from_primop(e2, reader)
|
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>>(
|
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>>(
|
pub fn force_result_shallow<'gc, M: Machine<'gc>>(
|
||||||
m: &mut M,
|
m: &mut M,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
|||||||
+99
-379
@@ -1,396 +1,116 @@
|
|||||||
use fix_bytecode::Continuation;
|
use fix_error::Result;
|
||||||
use fix_runtime::{
|
use fix_macros::handler;
|
||||||
BytecodeReader, List, Machine, MachineExt, NixType, Slot, Step, StrictValue, Value,
|
use fix_runtime::{List, Slot, StrictValue, Value};
|
||||||
};
|
|
||||||
use gc_arena::Mutation;
|
use gc_arena::Mutation;
|
||||||
|
|
||||||
use crate::slots;
|
use crate::primops::stubs::*;
|
||||||
|
|
||||||
pub mod filter {
|
#[handler(name = PFilter)]
|
||||||
use super::*;
|
fn filter<'gc>(
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
#[expect(
|
pred: Slot<Value<'gc>>,
|
||||||
clippy::unreachable,
|
list: Slot<Value<'gc>>,
|
||||||
reason = "dispatch_cont routes only the PFilter* continuations to this function, so the fallback arm is unreachable"
|
) -> Result<Value<'gc>> {
|
||||||
)]
|
let list: Slot<List<'gc>> = force(list).await?;
|
||||||
pub fn dispatch<'gc, M: Machine<'gc>>(
|
if list.get().inner.borrow().is_empty() {
|
||||||
m: &mut M,
|
return Ok(Value::new(list.get()));
|
||||||
reader: &mut BytecodeReader<'_>,
|
}
|
||||||
mc: &Mutation<'gc>,
|
let idx: Slot<i32> = spill(0i32);
|
||||||
cont: Continuation,
|
let acc: Slot<List<'gc>> = spill(List::new_gc(mc));
|
||||||
) -> Step {
|
loop {
|
||||||
use Continuation::*;
|
#[expect(
|
||||||
|
clippy::indexing_slicing,
|
||||||
match cont {
|
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||||
PFilterForceList => force_list(m, reader, mc),
|
)]
|
||||||
PFilterSetupStack => setup_stack(m, reader, mc),
|
let keep: bool = call(&pred, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||||
PFilterCallPred => call_pred(m, reader, mc),
|
if keep {
|
||||||
PFilterCheck => check(m, reader, mc),
|
#[expect(
|
||||||
_ => unreachable!(),
|
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);
|
||||||
}
|
}
|
||||||
}
|
if idx.get() as usize == list.get().inner.borrow().len() - 1 {
|
||||||
|
return Ok(Value::new(acc.get()));
|
||||||
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 ]
|
idx.set(idx.get() + 1);
|
||||||
m.push(Value::new(0));
|
|
||||||
m.push(Value::new(List::new_gc(mc)));
|
|
||||||
reader.set_pc(Continuation::PFilterCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[expect(
|
#[handler(name = PAll)]
|
||||||
clippy::indexing_slicing,
|
fn all<'gc>(
|
||||||
clippy::cast_sign_loss,
|
mc: &Mutation<'gc>,
|
||||||
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
pred: Slot<Value<'gc>>,
|
||||||
)]
|
list: Slot<Value<'gc>>,
|
||||||
fn call_pred<'gc, M: Machine<'gc>>(
|
) -> Result<Value<'gc>> {
|
||||||
m: &mut M,
|
let list: Slot<List<'gc>> = force(list).await?;
|
||||||
reader: &mut BytecodeReader<'_>,
|
if list.get().inner.borrow().is_empty() {
|
||||||
mc: &Mutation<'gc>,
|
return Ok(Value::new(true));
|
||||||
) -> 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)
|
|
||||||
}
|
}
|
||||||
|
let idx: Slot<i32> = spill(0i32);
|
||||||
#[expect(
|
loop {
|
||||||
clippy::indexing_slicing,
|
#[expect(
|
||||||
clippy::cast_sign_loss,
|
clippy::indexing_slicing,
|
||||||
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
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>>(
|
let keep: bool = call(&pred, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||||
m: &mut M,
|
if !keep || idx.get() as usize + 1 == list.get().inner.borrow().len() {
|
||||||
reader: &mut BytecodeReader<'_>,
|
return Ok(Value::new(keep));
|
||||||
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 {
|
idx.set(idx.get() + 1);
|
||||||
let acc = m.pop();
|
}
|
||||||
m.drop_n(3);
|
}
|
||||||
return m.return_from_primop(acc, reader);
|
|
||||||
|
#[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);
|
idx.set(idx.get() + 1);
|
||||||
reader.set_pc(Continuation::PFilterCallPred.ip() as usize);
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// foldl' op nul list
|
#[handler(name = PFoldlStrict)]
|
||||||
//
|
fn foldl_strict<'gc>(
|
||||||
// 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<'_>,
|
|
||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
op: Slot<Value<'gc>>,
|
||||||
m.force_slot(0, reader, mc)?;
|
nul: Slot<Value<'gc>>,
|
||||||
let list_val = m.peek_forced(0);
|
list: Slot<Value<'gc>>,
|
||||||
let Some(list) = list_val.downcast::<List>() else {
|
) -> Result<Value<'gc>> {
|
||||||
return m.finish_type_err(NixType::List, list_val.ty());
|
let list: Slot<List<'gc>> = force(list).await?;
|
||||||
};
|
if list.get().inner.borrow().is_empty() {
|
||||||
if list.inner.borrow().is_empty() {
|
return Ok(nul.get());
|
||||||
m.drop_n(1);
|
|
||||||
reader.set_pc(Continuation::PFoldlStrictEmpty.ip() as usize);
|
|
||||||
return Step::Continue(());
|
|
||||||
}
|
}
|
||||||
let list_val = m.pop();
|
let idx: Slot<i32> = spill(0i32);
|
||||||
let nul_val = m.pop();
|
let acc = spill(nul.get());
|
||||||
m.push(list_val);
|
loop {
|
||||||
m.push(Value::new(0i32));
|
let f = call(&op, acc.get()).await?;
|
||||||
m.push(nul_val);
|
#[expect(
|
||||||
reader.set_pc(Continuation::PFoldlStrictCall1.ip() as usize);
|
clippy::indexing_slicing,
|
||||||
Step::Continue(())
|
reason = "idx is a non-negative loop counter in 0..list.len(), so it indexes the list in bounds"
|
||||||
}
|
)]
|
||||||
|
let new_acc: StrictValue<'gc> =
|
||||||
pub fn foldl_strict_empty<'gc, M: Machine<'gc>>(
|
call(&f, list.get().inner.borrow()[idx.get() as usize]).await?;
|
||||||
m: &mut M,
|
acc.set(new_acc.relax());
|
||||||
reader: &mut BytecodeReader<'_>,
|
if idx.get() as usize + 1 == list.get().inner.borrow().len() {
|
||||||
mc: &Mutation<'gc>,
|
return Ok(acc.get());
|
||||||
) -> Step {
|
}
|
||||||
let nul = m.force_and_retry::<StrictValue>(reader, mc)?;
|
idx.set(idx.get() + 1);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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 io;
|
||||||
mod list;
|
mod list;
|
||||||
mod path;
|
mod path;
|
||||||
|
mod stubs;
|
||||||
|
|
||||||
pub use context::*;
|
pub use context::*;
|
||||||
pub use control::*;
|
pub use control::*;
|
||||||
@@ -18,27 +19,6 @@ 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,
|
||||||
@@ -53,28 +33,18 @@ pub fn dispatch_cont<'gc, M: Machine<'gc>>(
|
|||||||
match cont {
|
match cont {
|
||||||
PAbort => abort(m, ctx, reader, mc),
|
PAbort => abort(m, ctx, reader, mc),
|
||||||
|
|
||||||
PAll => all_entry(m, reader, mc),
|
PAll0 | PAll1 | PAll2 | PAll3 => all(m, reader, mc, cont),
|
||||||
PAllCallPred => all_call_pred(m, reader, mc),
|
PAny0 | PAny1 | PAny2 | PAny3 => any(m, reader, mc, cont),
|
||||||
PAllCheck => all_check(m, reader, mc),
|
PDeepSeq0 | PDeepSeq1 | PDeepSeq2 | PDeepSeq3 | PDeepSeq4 | PDeepSeq5 => {
|
||||||
|
deep_seq(m, reader, mc, cont)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
PSeq0 | PSeq1 => seq(m, reader, mc, cont),
|
||||||
|
|
||||||
PFoldlStrict => foldl_strict_entry(m, reader, mc),
|
PFilter0 | PFilter1 | PFilter2 | PFilter3 | PFilter4 => filter(m, reader, mc, cont),
|
||||||
PFoldlStrictEmpty => foldl_strict_empty(m, reader, mc),
|
|
||||||
PFoldlStrictCall1 => foldl_strict_call1(m, reader, mc),
|
PFoldlStrict0 | PFoldlStrict1 | PFoldlStrict2 | PFoldlStrict3 | PFoldlStrict4 | PFoldlStrict5 => {
|
||||||
PFoldlStrictCall2 => foldl_strict_call2(m, reader, mc),
|
foldl_strict(m, reader, mc, cont)
|
||||||
PFoldlStrictUpdate => foldl_strict_update(m, reader, mc),
|
}
|
||||||
|
|
||||||
ForceResultShallow => force_result_shallow(m, ctx, reader, mc),
|
ForceResultShallow => force_result_shallow(m, ctx, reader, mc),
|
||||||
ForceResultShallowPush => force_result_shallow_push(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