refactor primops

This commit is contained in:
2026-05-04 18:05:31 +08:00
parent b3e6591809
commit 62d65b2e5f
14 changed files with 163 additions and 133 deletions
+1
View File
@@ -0,0 +1 @@
+327
View File
@@ -0,0 +1,327 @@
use fix_builtins::PrimOpPhase;
use fix_error::Error;
use gc_arena::{Gc, Mutation, RefLock};
use smallvec::SmallVec;
use crate::value::*;
use crate::{BytecodeReader, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
impl<'gc> Vm<'gc> {
pub(crate) fn primop_seq(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
// stack: [e1, e2] - force e1, return e2
self.force_slot(1, reader, mc)?;
let e2 = self.pop();
let _ = self.pop();
self.return_from_primop(e2, reader)
}
pub(crate) fn primop_abort(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
// stack: [msg] - force msg, then abort with it
self.force_slot(0, reader, mc)?;
let msg_val = self.peek_forced(0);
let msg = ctx.get_string(msg_val).unwrap_or("<non-string-value>");
self.finish_err(Error::eval_error(format!(
"evaluation aborted with the following error message: '{msg}'"
)))
}
pub(crate) fn primop_deep_seq_force_top(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
// stack: [e1, e2] - force e1, return e2
self.force_slot(1, reader, mc)?;
let e1 = self.peek_forced(1);
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
let attrs = &attrs.entries;
if attrs.is_empty() {
SmallVec::new()
} else {
attrs.iter().map(|&(_, v)| v).collect()
}
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
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 = self.pop();
let _ = self.pop();
return self.return_from_primop(e2, reader);
}
let count = children.len() as i32;
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default());
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children);
let e2 = self.pop();
let _ = self.pop();
self.push(e2);
self.push(Value::new_gc(seen));
self.push(Value::new_gc(worklist));
self.push(Value::new_inline(count));
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
Step::Continue(())
}
pub(crate) fn primop_deep_seq_push(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
// stack: [e2, seen, worklist, counter]
#[allow(clippy::unwrap_used)]
let counter = self.peek(0).as_inline::<i32>().unwrap();
if counter == 0 {
let _ = self.pop(); // counter
let _ = self.pop(); // worklist
let _ = self.pop(); // seen
let val = self.pop();
return self.return_from_primop(val, reader);
}
#[allow(clippy::unwrap_used)]
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
#[allow(clippy::unwrap_used)]
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
self.replace(0, Value::new_inline(counter - 1));
self.push(item);
// force item at TOS, resume at DeepSeqLoop after force
self.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
Step::Continue(())
}
pub(crate) fn primop_deep_seq_loop(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
// stack after pop: [e2, seen, worklist, counter]
let item = self.pop();
#[allow(clippy::unwrap_used)]
let counter = self.peek(0).as_inline::<i32>().unwrap();
let mut added: usize = 0;
if let Some(attrs) = item.as_gc::<AttrSet>() {
let attrs = &attrs.entries;
#[allow(clippy::unwrap_used)]
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
if !self.is_value_in_seen(seen, item) {
self.add_value_to_seen(seen, mc, item);
#[allow(clippy::unwrap_used)]
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
{
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.as_gc::<List<'gc>>() {
#[allow(clippy::unwrap_used)]
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
if !self.is_value_in_seen(seen, item) {
self.add_value_to_seen(seen, mc, item);
#[allow(clippy::unwrap_used)]
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
{
let inner = list.inner.borrow();
let mut wl = worklist.unlock(mc).borrow_mut();
for &v in inner.iter() {
wl.push(v);
}
added = inner.len();
}
}
}
self.replace(0, Value::new_inline(counter + added as i32));
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
Step::Continue(())
}
pub(crate) fn primop_force_result_shallow(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.force_slot(0, reader, mc)?;
let val = self.peek_forced(0);
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
let len = attrs.entries.len();
(len, len > 0)
} else if let Some(list) = val.as_gc::<List<'gc>>() {
let len = list.inner.borrow().len();
(len, len > 0)
} else {
(0, false)
};
if !has_children {
let val = self.pop();
return self.finish_ok(ctx.convert_value(val));
}
self.push(Value::new_inline(0i32));
self.push(Value::new_inline(count as i32));
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
Step::Continue(())
}
pub(crate) fn primop_force_result_shallow_push(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
#[allow(clippy::unwrap_used)]
let idx = self.peek(1).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let len = self.peek(0).as_inline::<i32>().unwrap();
if idx == len {
let _ = self.pop(); // len
let _ = self.pop(); // idx
let val = self.pop();
return self.finish_ok(ctx.convert_value(val));
}
let val = self.peek_forced(2);
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
attrs.entries.get(idx as usize).map(|&(_, v)| v)
} else if let Some(list) = val.as_gc::<List<'gc>>() {
list.inner.borrow().get(idx as usize).copied()
} else {
None
};
if let Some(child) = child {
self.replace(1, Value::new_inline(idx + 1));
self.push(child);
self.force_slot_to_pc(
0,
reader,
mc,
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
)?;
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
}
Step::Continue(())
}
pub(crate) fn primop_force_result_shallow_loop(
&mut self,
reader: &mut BytecodeReader<'_>,
_mc: &Mutation<'gc>,
) -> Step {
let _ = self.pop(); // forced child
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
Step::Continue(())
}
pub(crate) fn primop_force_result_deep_finish(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
self.finish_ok(ctx.convert_value(val.relax()))
}
fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
if !is_container(val) {
return false;
}
let target = val.to_bits();
for &v in seen.inner.borrow().iter() {
if v.to_bits() == target {
return true;
}
}
false
}
fn add_value_to_seen(&self, seen: Gc<'gc, List<'gc>>, mc: &Mutation<'gc>, val: Value<'gc>) {
if is_container(val) {
seen.unlock(mc).borrow_mut().push(val);
}
}
pub(crate) fn primop_call_pattern(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (func, attrset) = self.force_and_retry::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
let Closure {
ip,
n_locals,
env,
pattern,
} = *func;
let Some(pattern) = pattern else {
unreachable!()
};
// TODO: get function name
// TODO: param spans
if !pattern.ellipsis {
for key in pattern.required.iter().copied() {
if attrset.lookup(key).is_none() {
let name = ctx.resolve_string(key);
return self.finish_err(Error::eval_error(format!(
"function 'anonymous lambda' called without required argument '{name}'"
)));
}
}
for &(key, _) in attrset.entries.iter() {
let is_expected =
pattern.required.contains(&key) || pattern.optional.contains(&key);
if !is_expected {
let name = ctx.resolve_string(key);
return self.finish_err(Error::eval_error(format!(
"function 'anonymous lambda' called with unexpected argument '{name}'"
)));
}
}
}
let new_env = Gc::new(
mc,
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
);
reader.set_pc(ip as usize);
self.env = new_env;
Step::Continue(())
}
}
fn is_container(val: Value<'_>) -> bool {
val.is::<AttrSet>() || val.is::<List<'_>>()
}
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+73
View File
@@ -0,0 +1,73 @@
use fix_builtins::PrimOpPhase;
use gc_arena::Mutation;
use crate::bytecode_reader::BytecodeReader;
use crate::value::*;
use crate::{Step, Vm};
impl<'gc> Vm<'gc> {
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() {
let val = self.pop();
return self.return_from_primop(val, 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.force_and_retry::<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
return self.return_from_primop(acc, reader);
}
self.replace(1, Value::new_inline(idx + 1));
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
Step::Continue(())
}
}
+70
View File
@@ -0,0 +1,70 @@
use fix_builtins::PrimOpPhase;
use fix_error::Error;
use gc_arena::Mutation;
use num_enum::TryFromPrimitive;
use crate::bytecode_reader::BytecodeReader;
use crate::value::Value;
use crate::{CallFrame, Step, Vm, VmRuntimeCtx};
mod attrs;
mod control;
mod conv;
mod io;
mod list;
mod path;
mod regex;
mod version;
impl<'gc> Vm<'gc> {
#[allow(clippy::too_many_lines)]
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 {
Abort => self.primop_abort(ctx, reader, mc),
DeepSeq => self.primop_deep_seq_force_top(reader, mc),
DeepSeqPush => self.primop_deep_seq_push(reader, mc),
DeepSeqLoop => self.primop_deep_seq_loop(reader, mc),
Seq => self.primop_seq(reader, mc),
FilterForceList => self.primop_filter_force_list(reader, mc),
FilterCallPred => self.primop_filter_call_pred(reader, mc),
FilterCheck => self.primop_filter_check(reader, mc),
ForceResultShallow => self.primop_force_result_shallow(ctx, reader, mc),
ForceResultShallowPush => self.primop_force_result_shallow_push(ctx, reader, mc),
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
CallPattern => self.primop_call_pattern(ctx, reader, mc),
phase => todo!("primop phase {phase:?}"),
}
}
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
self.push(val);
let Some(CallFrame {
pc: ret_pc,
thunk: _,
env,
}) = self.call_stack.pop()
else {
unreachable!()
};
reader.set_pc(ret_pc);
self.call_depth -= 1;
self.env = env;
Step::Continue(())
}
}
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
View File
+1
View File
@@ -0,0 +1 @@