use std::ops::ControlFlow; use std::path::{Path, PathBuf}; use fix_error::Error; use fix_lang::{self, StringId}; use gc_arena::Mutation; use crate::{ Break, BytecodeReader, CallFrame, ForceMode, Forced, GcEnv, NixType, PendingLoad, Step, StrictValue, Value, VmError, }; /// Abstract VM-side operations consumed by instruction handlers and primops. /// /// Implementors maintain a value stack, a call stack, an environment chain, /// pending result/error state, and a set of GC-allocated globals. Methods /// fall into a few groups: /// /// - Stack ops (`push` / `pop` / `peek` / `replace` / `pop_forced` / ...) /// - Forcing primitives (`force_slot` / `force_slot_to_pc`) /// - Calling (`call` / `return_from_primop`) /// - Call-frame management (`push_call_frame` / `pop_call_frame` / call-depth) /// - Environment access (`env` / `set_env` / `local`) /// - Result finalization (`finish_ok` / `finish_err` / ...) /// - Global lookup (`builtins` / `empty_list` / `empty_attrs` / ...) /// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`) pub trait Machine<'gc> { fn push(&mut self, val: Value<'gc>); fn pop(&mut self) -> Value<'gc>; fn peek(&self, depth: usize) -> Value<'gc>; fn peek_forced(&self, depth: usize) -> StrictValue<'gc>; fn pop_forced(&mut self) -> StrictValue<'gc>; fn replace(&mut self, depth: usize, val: Value<'gc>); fn stack_len(&self) -> usize; fn force_slot_to_pc( &mut self, depth: usize, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, resume_pc: usize, ) -> Step; #[inline(always)] fn force_slot( &mut self, depth: usize, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { let pc = reader.inst_start_pc(); self.force_slot_to_pc(depth, reader, mc, pc) } fn call( &mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, arg: Value<'gc>, resume_pc: usize, ) -> Step; #[inline(always)] 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.pop_call_frame() else { unreachable!() }; reader.set_pc(ret_pc); self.dec_call_depth(); self.set_env(env); Step::Continue(()) } fn push_call_frame(&mut self, frame: CallFrame<'gc>); fn pop_call_frame(&mut self) -> Option>; fn call_depth(&self) -> usize; fn inc_call_depth(&mut self); fn dec_call_depth(&mut self); fn env(&self) -> GcEnv<'gc>; fn set_env(&mut self, env: GcEnv<'gc>); #[inline(always)] fn local(&self, layer: u8, idx: u32) -> Value<'gc> { let mut cur = self.env(); for _ in 0..layer { let prev = cur.borrow().prev.expect("env chain too short"); cur = prev; } cur.borrow().locals[idx as usize] } fn finish_ok(&mut self, val: fix_lang::Value) -> Step; fn finish_err(&mut self, err: Box) -> Step; fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step; #[inline(always)] fn finish_vm_err(&mut self, err: VmError) -> Step { self.finish_err(err.into_error()) } fn builtins(&self) -> Value<'gc>; fn functor_sym(&self) -> StringId; fn empty_list(&self) -> Value<'gc>; fn empty_attrs(&self) -> Value<'gc>; fn force_mode(&self) -> ForceMode; fn import_cache_get(&self, path: &Path) -> Option>; fn import_cache_insert(&mut self, path: PathBuf, val: Value<'gc>); fn scope_slot(&self, idx: u32) -> Value<'gc>; fn scope_slots_push(&mut self, val: Value<'gc>) -> u32; fn set_pending_load(&mut self, load: PendingLoad); } /// Extension trait with convenience helpers built on top of [`Machine`]. /// /// Auto-implemented for every `Machine<'gc>` so callers just need to bring /// `MachineExt` (or `Machine`) into scope. pub trait MachineExt<'gc>: Machine<'gc> { /// Force the top `T::WIDTH` stack slots and return them as `T`. /// /// If any slot holds a pending thunk, this method pushes a call frame /// whose resume PC is the **start of the current instruction** /// (`reader.inst_start_pc()`), enters the thunk, and returns /// `Break::Force`. When the thunk eventually returns, the VM will /// **re-execute the entire opcode handler from the beginning**. /// /// # Invariants /// /// * **Do not call this method more than once in a single handler.** /// If you need to force multiple values, use a tuple type such as /// `(StrictValue, StrictValue)` so they are forced and popped in one /// atomic operation. /// * The stack layout at the call site must be **identical** every time /// the handler is re-entered. /// * Propagate the return value with `?` so `Break::Force` correctly /// unwinds to the dispatch loop. #[inline(always)] fn force_and_retry>( &mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> ControlFlow where Self: Sized, { let pc = reader.inst_start_pc(); self.force_and_retry_pc(reader, mc, pc) } /// Same as [`force_and_retry`](Self::force_and_retry) but allows /// specifying a custom resume PC. #[inline(always)] fn force_and_retry_pc>( &mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, resume_pc: usize, ) -> ControlFlow where Self: Sized, { T::force_and_check(self, reader, mc, 0, resume_pc)?; ControlFlow::Continue(T::pop_converted(self)) } } impl<'gc, M: Machine<'gc>> MachineExt<'gc> for M {}