From bc16596dd3cbcb33c7c40d5e424ae30e3fbb6086 Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sun, 26 Apr 2026 14:51:34 +0800 Subject: [PATCH] ForceMode --- fix-builtins/src/lib.rs | 5 ++ fix-vm/src/instructions/builtins.rs | 126 ++++++++++++++++++++++------ fix-vm/src/instructions/calls.rs | 27 +++++- fix-vm/src/lib.rs | 43 ++++++++-- fix-vm/src/value.rs | 9 +- 5 files changed, 175 insertions(+), 35 deletions(-) diff --git a/fix-builtins/src/lib.rs b/fix-builtins/src/lib.rs index c05e270..0a43f8d 100644 --- a/fix-builtins/src/lib.rs +++ b/fix-builtins/src/lib.rs @@ -237,6 +237,11 @@ pub enum PrimOpPhase { Warn, ZipAttrsWith, + ForceResultShallow, + ForceResultShallowPush, + ForceResultShallowLoop, + ForceResultDeepFinish, + Illegal, } diff --git a/fix-vm/src/instructions/builtins.rs b/fix-vm/src/instructions/builtins.rs index de5a5a0..c24b077 100644 --- a/fix-vm/src/instructions/builtins.rs +++ b/fix-vm/src/instructions/builtins.rs @@ -5,9 +5,7 @@ use num_enum::TryFromPrimitive as _; use smallvec::SmallVec; use crate::value::*; -use crate::{ - BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt, -}; +use crate::{BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt}; impl<'gc> Vm<'gc> { #[allow(clippy::too_many_lines)] @@ -34,7 +32,12 @@ impl<'gc> Vm<'gc> { FilterCallPred => self.primop_filter_call_pred(reader, mc), FilterCheck => self.primop_filter_check(reader, mc), - _ => todo!("dispatch builtin phase"), + 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), + + phase => todo!("primop phase {phase:?}"), } } @@ -143,9 +146,7 @@ impl<'gc> Vm<'gc> { // 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(""); + let msg = ctx.get_string(msg_val).unwrap_or(""); self.finish_err(Error::eval_error(format!( "evaluation aborted with the following error message: '{msg}'" ))) @@ -222,12 +223,7 @@ impl<'gc> Vm<'gc> { 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, - )?; + self.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?; reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize); Step::Continue(()) } @@ -281,11 +277,98 @@ impl<'gc> Vm<'gc> { Step::Continue(()) } - fn is_value_in_seen( - &self, - seen: Gc<'gc, List<'gc>>, - val: Value<'gc>, - ) -> bool { + 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::() { + let len = attrs.len(); + (len, len > 0) + } else if let Some(list) = val.as_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::().unwrap(); + #[allow(clippy::unwrap_used)] + let len = self.peek(0).as_inline::().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::() { + attrs.get(idx as usize).map(|&(_, v)| v) + } else if let Some(list) = val.as_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.try_force::(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; } @@ -298,12 +381,7 @@ impl<'gc> Vm<'gc> { false } - fn add_value_to_seen( - &self, - seen: Gc<'gc, List<'gc>>, - mc: &Mutation<'gc>, - val: Value<'gc>, - ) { + 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); } diff --git a/fix-vm/src/instructions/calls.rs b/fix-vm/src/instructions/calls.rs index 958f4e5..456ec90 100644 --- a/fix-vm/src/instructions/calls.rs +++ b/fix-vm/src/instructions/calls.rs @@ -1,9 +1,11 @@ +use fix_builtins::PrimOpPhase; use fix_error::Error; use gc_arena::{Gc, Mutation, RefLock}; use crate::value::*; use crate::{ - BytecodeReader, CallFrame, Closure, Env, Step, ThunkState, VmRuntimeCtx, VmRuntimeCtxExt, + BytecodeReader, CallFrame, Closure, Env, ForceMode, Step, ThunkState, VmRuntimeCtx, + VmRuntimeCtxExt, }; impl<'gc> crate::Vm<'gc> { @@ -112,7 +114,28 @@ impl<'gc> crate::Vm<'gc> { with_env, }) = self.call_stack.pop() else { - return self.finish_ok(ctx.convert_value(val.relax())); + match self.force_mode { + ForceMode::AsIs => return self.finish_ok(ctx.convert_value(val.relax())), + ForceMode::Shallow => { + self.push(val.relax()); + reader.set_pc(PrimOpPhase::ForceResultShallow.ip() as usize); + return Step::Continue(()); + } + ForceMode::Deep => { + self.push(val.relax()); + self.push(val.relax()); + self.call_stack.push(CallFrame { + pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize, + stack_depth: 0, + thunk: None, + env: self.env, + with_env: self.with_env, + }); + self.call_depth += 1; + reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize); + return Step::Continue(()); + } + } }; reader.set_pc(ret_pc); if let Some(outer_thunk) = thunk { diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index 0b36762..d50db93 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -13,7 +13,7 @@ use fix_common::StringId; use fix_error::{Error, Result, Source}; use gc_arena::arena::CollectionPhase; use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable}; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use num_enum::TryFromPrimitive; use smallvec::SmallVec; @@ -115,6 +115,16 @@ impl VmRuntimeCtxExt for T { } fn convert_value(&self, val: Value) -> fix_common::Value { + self.convert_value_with_seen(val, &mut HashSet::new()) + } +} + +trait ConvertValueWithSeen: VmRuntimeCtx { + fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet) -> fix_common::Value; +} + +impl ConvertValueWithSeen for T { + fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet) -> fix_common::Value { use fix_common::Value; if let Some(i) = val.as_inline::() { Value::Int(i as i64) @@ -132,26 +142,44 @@ impl VmRuntimeCtxExt for T { } else if let Some(ns) = val.as_gc::() { Value::String(ns.as_str().to_owned()) } else if let Some(attrs) = val.as_gc::() { + let bits = val.to_bits(); + if attrs.is_empty() { + return Value::AttrSet(Default::default()); + } + if !seen.insert(bits) { + return Value::Repeated; + } let mut map = std::collections::BTreeMap::new(); for &(key, val) in attrs.iter() { let key = self.resolve_string(key).to_owned(); - let converted = self.convert_value(val); + let converted = self.convert_value_with_seen(val, seen); map.insert(fix_common::Symbol::from(key), converted); } Value::AttrSet(fix_common::AttrSet::new(map)) } else if let Some(list) = val.as_gc::() { + let bits = val.to_bits(); + if list.inner.borrow().is_empty() { + return Value::List(Default::default()); + } + if !seen.insert(bits) { + return Value::Repeated; + } let items: Vec<_> = list .inner .borrow() .iter() .copied() - .map(|v| self.convert_value(v)) + .map(|v| self.convert_value_with_seen(v, seen)) .collect(); Value::List(fix_common::List::new(items)) } else if val.is::() { Value::Func - } else if val.is::() { - Value::Thunk + } else if let Some(thunk) = val.as_gc::() { + if let ThunkState::Evaluated(v) = *thunk.borrow() { + self.convert_value_with_seen(v.relax(), seen) + } else { + Value::Thunk + } } else if let Some(primop) = val.as_inline::() { let name = fix_builtins::BUILTINS[primop.id as usize].0; Value::PrimOp(name.strip_prefix("__").unwrap_or(name)) @@ -278,7 +306,10 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value< entries.sort_by_key(|(k, _)| *k); let builtins_set = Gc::new(mc, AttrSet::from_sorted_unchecked(entries)); - Value::new_gc(builtins_set) + let builtins_value = Value::new_gc(builtins_set); + *self_ref_thunk.borrow_mut(mc) = + ThunkState::Evaluated(builtins_value.restrict().expect("builtins is not a thunk")); + builtins_value } impl<'gc> Vm<'gc> { diff --git a/fix-vm/src/value.rs b/fix-vm/src/value.rs index e2e6f64..704f29a 100644 --- a/fix-vm/src/value.rs +++ b/fix-vm/src/value.rs @@ -506,9 +506,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) - }) + Gc::new( + mc, + Self { + inner: RefLock::new(data), + }, + ) } pub(crate) fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> {