implement primop (filter)

This commit is contained in:
2026-04-25 21:08:54 +08:00
parent 4f3cd0ef4c
commit 26717a8184
18 changed files with 560 additions and 129 deletions
+11 -6
View File
@@ -1,6 +1,6 @@
use std::cmp::Ordering;
use gc_arena::{Gc, Mutation};
use gc_arena::{Gc, Mutation, RefLock};
use crate::value::*;
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _};
@@ -173,9 +173,14 @@ impl<'gc> crate::Vm<'gc> {
) -> Step {
let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(reader, mc)?;
let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l);
items.extend_from_slice(&r);
self.push(Value::new_gc(Gc::new(mc, crate::List { inner: items })));
items.extend_from_slice(&l.inner.borrow());
items.extend_from_slice(&r.inner.borrow());
self.push(Value::new_gc(Gc::new(
mc,
crate::List {
inner: RefLock::new(items),
},
)));
Step::Continue(())
}
@@ -224,10 +229,10 @@ impl<'gc> crate::Vm<'gc> {
return Ok(a == b);
}
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
if a.inner.len() != b.inner.len() {
if a.inner.borrow().len() != b.inner.borrow().len() {
return Ok(false);
}
for (x, y) in a.inner.iter().zip(b.inner.iter()) {
for (x, y) in a.inner.borrow().iter().zip(b.inner.borrow().iter()) {
let lx = x.restrict().expect("forced");
let ly = y.restrict().expect("forced");
if !self.values_equal(ctx, lx, ly)? {
+111
View File
@@ -0,0 +1,111 @@
use fix_builtins::PrimOpPhase;
use fix_error::Error;
use gc_arena::Mutation;
use num_enum::TryFromPrimitive as _;
use crate::value::*;
use crate::{BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx};
impl<'gc> Vm<'gc> {
pub(crate) fn op_dispatch_primop(
&mut self,
_ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
use PrimOpPhase::*;
let phase_disc = reader.read_u8();
let Ok(phase) = PrimOpPhase::try_from_primitive(phase_disc) else {
return self.finish_err(Error::eval_error("invalid primop phase"));
};
match phase {
FilterForceList => self.primop_filter_force_list(reader, mc),
FilterCallPred => self.primop_filter_call_pred(reader, mc),
FilterCheck => self.primop_filter_check(reader, mc),
_ => todo!("dispatch builtin phase"),
}
}
fn return_from_primop(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let Some(CallFrame {
pc: ret_pc,
stack_depth: _,
thunk: _,
env,
with_env,
}) = self.call_stack.pop()
else {
unreachable!()
};
reader.set_pc(ret_pc);
self.call_depth -= 1;
self.env = env;
self.with_env = with_env;
Step::Continue(())
}
pub(crate) fn primop_filter_force_list(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.force_slot(0, reader, mc)?;
let list = match self.peek_forced(0).expect_gc::<List>() {
Ok(list) => list,
Err(got) => return self.finish_type_err(NixType::List, got),
};
if list.inner.borrow().is_empty() {
return self.return_from_primop(reader);
}
// prepare stack layout: [ pred list idx acc ]
self.push(Value::new_inline(0));
self.push(Value::new_gc(List::new_gc(mc)));
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
Step::Continue(())
}
pub(crate) fn primop_filter_call_pred(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.force_slot(3, reader, mc)?;
let pred = self.peek_forced(3);
#[allow(clippy::unwrap_used)]
let idx = self.peek(1).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let elem = self.peek_forced(2).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
self.push(pred.relax());
self.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize)
}
pub(crate) fn primop_filter_check(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let ret = self.try_force::<bool>(reader, mc)?;
#[allow(clippy::unwrap_used)]
let idx = self.peek(1).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = self.peek_forced(2).as_gc::<List>().unwrap();
let list = list.inner.borrow();
#[allow(clippy::unwrap_used)]
let acc = self.peek_forced(0).as_gc::<List>().unwrap();
if ret {
let mut acc = acc.unlock(mc).borrow_mut();
acc.push(list[idx as usize]);
}
if idx as usize == list.len() - 1 {
let acc = self.pop();
let _ = self.pop(); // idx
let _ = self.pop(); // list
let _ = self.pop(); // pred
self.push(acc);
return self.return_from_primop(reader);
}
self.replace(1, Value::new_inline(idx + 1));
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
Step::Continue(())
}
}
+63 -69
View File
@@ -8,18 +8,18 @@ use crate::{
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_call(
pub(crate) fn call(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
arg: Value<'gc>,
resume_pc: usize,
) -> Step {
let func = self.try_force::<StrictValue>(reader, mc)?;
if self.call_depth > 10000 {
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
}
self.call_depth += 1;
let arg = reader.read_operand_data(ctx).resolve(mc, self);
if let Some(closure) = func.as_gc::<Closure>() {
let ip = closure.ip;
let n_locals = closure.n_locals;
@@ -29,7 +29,7 @@ impl<'gc> crate::Vm<'gc> {
} else {
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame {
pc: reader.pc(),
pc: resume_pc,
stack_depth: 0,
thunk: None,
env: self.env,
@@ -38,12 +38,66 @@ impl<'gc> crate::Vm<'gc> {
reader.set_pc(ip as usize);
self.env = new_env;
}
} else if let Some(primop) = func.as_inline::<PrimOp>() {
if primop.arity == 1 {
self.push(arg);
self.call_stack.push(CallFrame {
pc: resume_pc,
stack_depth: 0,
thunk: None,
env: self.env,
with_env: self.with_env,
});
reader.set_pc(primop.dispatch_ip as usize)
} else {
let app = PrimOpApp {
primop,
arity: primop.arity - 1,
args: [arg, Value::default(), Value::default()],
};
self.push(Value::new_gc(Gc::new(mc, app)));
}
} else if let Some(app) = func.as_gc::<PrimOpApp>() {
if app.arity == 1 {
for i in 0..app.primop.arity - 1 {
self.push(app.args[i as usize]);
}
self.push(arg);
self.call_stack.push(CallFrame {
pc: resume_pc,
stack_depth: 0,
thunk: None,
env: self.env,
with_env: self.with_env,
});
reader.set_pc(app.primop.dispatch_ip as usize)
} else {
let position = (app.primop.arity - app.arity) as usize;
let mut new_app = PrimOpApp {
arity: app.arity - 1,
..*app
};
new_app.args[position] = arg;
self.push(Value::new_gc(Gc::new(mc, new_app)))
}
} else {
todo!("call other types: {func:?}")
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_call(
&mut self,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let arg = reader.read_operand_data(ctx).resolve(mc, self);
let pc = reader.pc();
self.call(reader, mc, arg, pc)
}
#[inline(always)]
pub(crate) fn op_return(
&mut self,
@@ -51,16 +105,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.handle_return(reader, ctx, mc)
}
pub(crate) fn handle_return<C: VmRuntimeCtx>(
&mut self,
reader: &mut BytecodeReader<'_>,
ctx: &C,
mc: &Mutation<'gc>,
) -> Step {
let ret_inst_pc = reader.pc() - 1;
let val = self.try_force::<StrictValue>(reader, mc)?;
let Some(CallFrame {
pc: ret_pc,
stack_depth,
@@ -69,66 +114,15 @@ impl<'gc> crate::Vm<'gc> {
with_env,
}) = self.call_stack.pop()
else {
let val = self.pop();
return self.finish_ok(ctx.convert_value(val));
return self.finish_ok(ctx.convert_value(val.relax()));
};
reader.set_pc(ret_pc);
if let Some(outer_thunk) = thunk {
let val = self.pop();
match val.restrict() {
Ok(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if reader.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8)
{
self.push(val.relax());
}
}
Err(inner_thunk) => {
let mut state = inner_thunk.borrow_mut(mc);
match *state {
ThunkState::Pending {
ip: inner_ip,
env: inner_env,
with_env: inner_with_env,
} => {
self.call_stack.push(CallFrame {
pc: ret_pc,
stack_depth,
thunk: Some(outer_thunk),
env,
with_env,
});
self.call_stack.push(CallFrame {
pc: ret_inst_pc,
stack_depth: 0,
thunk: Some(inner_thunk),
env: inner_env,
with_env: inner_with_env,
});
*state = ThunkState::Blackhole;
reader.set_pc(inner_ip);
self.env = inner_env;
self.with_env = inner_with_env;
return Step::Continue(());
}
ThunkState::Evaluated(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if reader.bytecode().get(ret_pc).copied()
== Some(fix_codegen::Op::Return as u8)
{
self.push(val.relax());
}
}
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
ThunkState::Blackhole => {
return self
.finish_err(Error::eval_error("infinite recursion encountered"));
}
}
}
}
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
self.replace(stack_depth, val.relax());
} else {
self.call_depth -= 1;
self.push(val.relax())
}
self.env = env;
self.with_env = with_env;
+7 -2
View File
@@ -1,6 +1,6 @@
use fix_common::StringId;
use fix_error::Error;
use gc_arena::Gc;
use gc_arena::{Gc, RefLock};
use smallvec::SmallVec;
use crate::value::NixType;
@@ -298,7 +298,12 @@ impl<'gc> crate::Vm<'gc> {
for _ in 0..count {
items.push(reader.read_operand_data(ctx).resolve(mc, self));
}
let list = Gc::new(mc, List { inner: items });
let list = Gc::new(
mc,
List {
inner: RefLock::new(items),
},
);
self.push(Value::new_gc(list));
Step::Continue(())
}
@@ -17,6 +17,7 @@ impl<'gc> crate::Vm<'gc> {
self.push(Value::new_inline(PrimOp {
id,
arity: fix_builtins::BUILTINS[id as usize].1,
dispatch_ip: id.entry_phase().ip(),
}));
Step::Continue(())
}
+2 -1
View File
@@ -1,9 +1,10 @@
pub(crate) mod arithmetic;
pub(crate) mod builtins_misc;
pub(crate) mod builtins;
pub(crate) mod calls;
pub(crate) mod closures;
pub(crate) mod collections;
pub(crate) mod control;
pub(crate) mod literals;
pub(crate) mod misc;
pub(crate) mod variables;
pub(crate) mod with_scope;