macros, vm, bytecode: add #[primop] await-style primop macro

This commit is contained in:
2026-08-23 17:38:08 +08:00
parent 89f5d84009
commit 0e344b1097
16 changed files with 1770 additions and 610 deletions
+1 -1
View File
@@ -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(());
}
}
+4
View File
@@ -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>;
+36
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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),
+9
View File
@@ -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};