From c08e0b81c42b6b9a0417833d62b1de750b2d8a33 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sun, 26 Apr 2026 13:27:54 +0800 Subject: [PATCH] implement __seq and __deepSeq --- fix-builtins/src/lib.rs | 4 + fix-vm/src/instructions/builtins.rs | 210 +++++++++++++++++++++++++++- fix-vm/src/value.rs | 11 ++ 3 files changed, 222 insertions(+), 3 deletions(-) diff --git a/fix-builtins/src/lib.rs b/fix-builtins/src/lib.rs index 6cd7176..c05e270 100644 --- a/fix-builtins/src/lib.rs +++ b/fix-builtins/src/lib.rs @@ -146,7 +146,11 @@ pub enum PrimOpPhase { ConcatMap, ConcatStringsSep, ConvertHash, + DeepSeq, + DeepSeqPush, + DeepSeqLoop, + Derivation, DerivationStrict, DirOf, diff --git a/fix-vm/src/instructions/builtins.rs b/fix-vm/src/instructions/builtins.rs index 8e95cdc..de5a5a0 100644 --- a/fix-vm/src/instructions/builtins.rs +++ b/fix-vm/src/instructions/builtins.rs @@ -1,15 +1,19 @@ use fix_builtins::PrimOpPhase; use fix_error::Error; -use gc_arena::Mutation; +use gc_arena::{Gc, Mutation}; use num_enum::TryFromPrimitive as _; +use smallvec::SmallVec; use crate::value::*; -use crate::{BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx}; +use crate::{ + BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt, +}; impl<'gc> Vm<'gc> { + #[allow(clippy::too_many_lines)] pub(crate) fn op_dispatch_primop( &mut self, - _ctx: &mut impl VmRuntimeCtx, + ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { @@ -19,9 +23,17 @@ impl<'gc> Vm<'gc> { 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), + _ => todo!("dispatch builtin phase"), } } @@ -108,4 +120,196 @@ impl<'gc> Vm<'gc> { reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize); Step::Continue(()) } + + 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.push(e2); + self.return_from_primop(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(""); + 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::() { + if attrs.is_empty() { + SmallVec::new() + } else { + attrs.iter().map(|&(_, v)| v).collect() + } + } else if let Some(list) = e1.as_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(); + self.push(e2); + return self.return_from_primop(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::().unwrap(); + if counter == 0 { + let _ = self.pop(); // counter + let _ = self.pop(); // worklist + let _ = self.pop(); // seen + return self.return_from_primop(reader); + } + + #[allow(clippy::unwrap_used)] + let worklist = self.peek_forced(1).as_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::().unwrap(); + + let mut added: usize = 0; + if let Some(attrs) = item.as_gc::() { + #[allow(clippy::unwrap_used)] + let seen = self.peek_forced(2).as_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::>().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::>() { + #[allow(clippy::unwrap_used)] + let seen = self.peek_forced(2).as_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::>().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(()) + } + + 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); + } + } +} + +fn is_container(val: Value<'_>) -> bool { + val.is::() || val.is::>() } diff --git a/fix-vm/src/value.rs b/fix-vm/src/value.rs index 9578a97..e2e6f64 100644 --- a/fix-vm/src/value.rs +++ b/fix-vm/src/value.rs @@ -248,6 +248,11 @@ impl<'gc> Value<'gc> { } } + #[inline] + pub(crate) fn to_bits(self) -> u64 { + self.raw.to_bits() + } + #[inline] pub fn as_num(self) -> Option { if let Some(i) = self.as_inline::() { @@ -500,6 +505,12 @@ pub(crate) struct List<'gc> { } impl<'gc> List<'gc> { + pub(crate) fn new(mc: &Mutation<'gc>, data: SmallVec<[Value<'gc>; 4]>) -> Gc<'gc, Self> { + Gc::new(mc, Self { + inner: RefLock::new(data) + }) + } + pub(crate) fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> { Gc::new(mc, Self::default()) }