refactor: reorganize crate hierarchy
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
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<CallFrame<'gc>>;
|
||||
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<Error>) -> 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<Value<'gc>>;
|
||||
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<T: Forced<'gc>>(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> ControlFlow<Break, T>
|
||||
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<T: Forced<'gc>>(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
resume_pc: usize,
|
||||
) -> ControlFlow<Break, T>
|
||||
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 {}
|
||||
Reference in New Issue
Block a user