fix-vm: use Machine trait exclusively

This commit is contained in:
2026-06-19 21:56:12 +08:00
parent afbc471e40
commit f0e3f1eeca
13 changed files with 1240 additions and 1326 deletions
+81 -199
View File
@@ -137,45 +137,20 @@ impl<'gc> Vm<'gc> {
functor_sym: ctx.intern_string("__functor"),
}
}
}
#[inline(always)]
fn finish_ok(&mut self, val: fix_lang::Value) -> Step {
self.result = Some(Ok(val));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_err(&mut self, err: Box<Error>) -> Step {
self.result = Some(Err(err));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
self.result = Some(Err(Error::eval_error(format!(
"expected {expected}, got {got}"
))));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_vm_err(&mut self, err: VmError) -> Step {
self.finish_err(err.into_error())
}
impl<'gc> Machine<'gc> for Vm<'gc> {
#[inline(always)]
fn push(&mut self, val: Value<'gc>) {
self.stack.push(val);
}
#[inline(always)]
#[must_use]
fn pop(&mut self) -> Value<'gc> {
self.stack.pop().expect("stack underflow")
}
#[inline(always)]
#[must_use]
fn peek(&self, depth: usize) -> Value<'gc> {
*self
.stack
@@ -184,7 +159,6 @@ impl<'gc> Vm<'gc> {
}
#[inline(always)]
#[must_use]
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
self.stack
.get(self.stack.len() - depth - 1)
@@ -193,6 +167,15 @@ impl<'gc> Vm<'gc> {
.expect("forced")
}
#[inline(always)]
fn pop_forced(&mut self) -> StrictValue<'gc> {
self.stack
.pop()
.expect("stack underflow")
.restrict()
.expect("forced")
}
#[inline(always)]
fn replace(&mut self, depth: usize, val: Value<'gc>) {
let len = self.stack.len();
@@ -203,72 +186,13 @@ impl<'gc> Vm<'gc> {
}
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
fn pop_forced(&mut self) -> StrictValue<'gc> {
self.stack
.pop()
.expect("stack underflow")
.restrict()
.expect("forced")
}
/// 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. Calling `force_and_retry` twice (or more)
/// means the handler will be re-run from the top after each retry;
/// any stack modifications between the two calls would be duplicated
/// and corrupt the stack layout.
///
/// * The caller must ensure that the stack layout at the point of
/// invocation is **identical** every time the handler is re-entered.
/// In practice this means no pushes, pops, or local mutations may
/// happen before the call, and the call must be the first thing
/// that consumes the instruction's operand values.
///
/// * The return value must be propagated with `?` so that
/// `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>,
) -> std::ops::ControlFlow<Break, T> {
self.force_and_retry_pc(reader, mc, reader.inst_start_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,
) -> std::ops::ControlFlow<Break, T> {
T::force_and_check(self, reader, mc, 0, resume_pc)?;
std::ops::ControlFlow::Continue(T::pop_converted(self))
fn drop_n(&mut self, depth: usize) {
self.stack.truncate(self.stack.len() - depth);
}
#[inline(always)]
#[allow(unused)]
fn force_slot(
&mut self,
depth: usize,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())
fn stack_len(&self) -> usize {
self.stack.len()
}
#[inline(always)]
@@ -313,54 +237,6 @@ impl<'gc> Vm<'gc> {
}
}
}
}
impl<'gc> Machine<'gc> for Vm<'gc> {
#[inline(always)]
fn push(&mut self, val: Value<'gc>) {
self.push(val);
}
#[inline(always)]
fn pop(&mut self) -> Value<'gc> {
self.pop()
}
#[inline(always)]
fn peek(&self, depth: usize) -> Value<'gc> {
Vm::peek(self, depth)
}
#[inline(always)]
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
Vm::peek_forced(self, depth)
}
#[inline(always)]
fn pop_forced(&mut self) -> StrictValue<'gc> {
self.pop_forced()
}
#[inline(always)]
fn replace(&mut self, depth: usize, val: Value<'gc>) {
self.replace(depth, val);
}
#[inline(always)]
fn stack_len(&self) -> usize {
self.stack.len()
}
#[inline(always)]
fn force_slot_to_pc(
&mut self,
depth: usize,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
resume_pc: usize,
) -> Step {
self.force_slot_to_pc(depth, reader, mc, resume_pc)
}
#[inline(always)]
fn call(
@@ -370,7 +246,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
arg: Value<'gc>,
resume_pc: usize,
) -> Step {
self.call(reader, mc, arg, resume_pc)
instructions::call(self, reader, mc, arg, resume_pc)
}
#[inline(always)]
@@ -410,17 +286,22 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
#[inline(always)]
fn finish_ok(&mut self, val: fix_lang::Value) -> Step {
self.finish_ok(val)
self.result = Some(Ok(val));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_err(&mut self, err: Box<Error>) -> Step {
self.finish_err(err)
self.result = Some(Err(err));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
self.finish_type_err(expected, got)
self.result = Some(Err(Error::eval_error(format!(
"expected {expected}, got {got}"
))));
Step::Break(Break::Done)
}
#[inline(always)]
@@ -590,6 +471,7 @@ impl<'gc> Vm<'gc> {
mc: &Mutation<'gc>,
) -> Action {
use fix_bytecode::Op::*;
use instructions::*;
let mut reader = BytecodeReader::new(bytecode, pc);
let mut fuel = Self::DEFAULT_FUEL_AMOUNT;
@@ -603,75 +485,75 @@ impl<'gc> Vm<'gc> {
let op = reader.read_op();
let result = match op {
PushSmi => self.op_push_smi(&mut reader),
PushBigInt => self.op_push_bigint(&mut reader, mc),
PushFloat => self.op_push_float(&mut reader),
PushString => self.op_push_string(&mut reader),
PushNull => self.op_push_null(),
PushTrue => self.op_push_true(),
PushFalse => self.op_push_false(),
PushSmi => op_push_smi(self, &mut reader),
PushBigInt => op_push_bigint(self, &mut reader, mc),
PushFloat => op_push_float(self, &mut reader),
PushString => op_push_string(self, &mut reader),
PushNull => op_push_null(self),
PushTrue => op_push_true(self),
PushFalse => op_push_false(self),
LoadLocal => self.op_load_local(&mut reader),
LoadOuter => self.op_load_outer(&mut reader),
StoreLocal => self.op_store_local(&mut reader, mc),
AllocLocals => self.op_alloc_locals(&mut reader, mc),
LoadLocal => op_load_local(self, &mut reader),
LoadOuter => op_load_outer(self, &mut reader),
StoreLocal => op_store_local(self, &mut reader, mc),
AllocLocals => op_alloc_locals(self, &mut reader, mc),
MakeThunk => self.op_make_thunk(&mut reader, mc),
MakeClosure => self.op_make_closure(&mut reader, mc),
MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc),
MakeThunk => op_make_thunk(self, &mut reader, mc),
MakeClosure => op_make_closure(self, &mut reader, mc),
MakePatternClosure => op_make_pattern_closure(self, &mut reader, mc),
Call => self.op_call(ctx, &mut reader, mc),
DispatchPrimOp => self.op_dispatch_primop(ctx, &mut reader, mc),
Return => self.op_return(ctx, &mut reader, mc),
Call => op_call(self, ctx, &mut reader, mc),
DispatchPrimOp => op_dispatch_primop(self, ctx, &mut reader, mc),
Return => op_return(self, ctx, &mut reader, mc),
MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc),
MakeEmptyAttrs => self.op_make_empty_attrs(),
SelectStatic => self.op_select_static(ctx, &mut reader, mc),
SelectDynamic => self.op_select_dynamic(ctx, &mut reader, mc),
HasAttrPathStatic => self.op_has_attr_path_static(ctx, &mut reader, mc),
HasAttrPathDynamic => self.op_has_attr_path_dynamic(ctx, &mut reader, mc),
HasAttrStatic => self.op_has_attr_static(&mut reader, mc),
HasAttrDynamic => self.op_has_attr_dynamic(ctx, &mut reader, mc),
HasAttrResolve => self.op_has_attr_resolve(),
JumpIfSelectFailed => self.op_jump_if_select_failed(&mut reader),
JumpIfSelectSucceeded => self.op_jump_if_select_succeeded(&mut reader),
MakeAttrs => op_make_attrs(self, ctx, &mut reader, mc),
MakeEmptyAttrs => op_make_empty_attrs(self),
SelectStatic => op_select_static(self, ctx, &mut reader, mc),
SelectDynamic => op_select_dynamic(self, ctx, &mut reader, mc),
HasAttrPathStatic => op_has_attr_path_static(self, ctx, &mut reader, mc),
HasAttrPathDynamic => op_has_attr_path_dynamic(self, ctx, &mut reader, mc),
HasAttrStatic => op_has_attr_static(self, &mut reader, mc),
HasAttrDynamic => op_has_attr_dynamic(self, ctx, &mut reader, mc),
HasAttrResolve => op_has_attr_resolve(self),
JumpIfSelectFailed => op_jump_if_select_failed(self, &mut reader),
JumpIfSelectSucceeded => op_jump_if_select_succeeded(self, &mut reader),
MakeList => self.op_make_list(ctx, &mut reader, mc),
MakeEmptyList => self.op_make_empty_list(),
MakeList => op_make_list(self, ctx, &mut reader, mc),
MakeEmptyList => op_make_empty_list(self),
OpAdd => self.op_add(ctx, &mut reader, mc),
OpSub => self.op_sub(&mut reader, mc),
OpMul => self.op_mul(&mut reader, mc),
OpDiv => self.op_div(&mut reader, mc),
OpEq => self.op_eq(ctx, &mut reader, mc),
OpNeq => self.op_neq(ctx, &mut reader, mc),
OpLt => self.op_lt(ctx, &mut reader, mc),
OpGt => self.op_gt(ctx, &mut reader, mc),
OpLeq => self.op_leq(ctx, &mut reader, mc),
OpGeq => self.op_geq(ctx, &mut reader, mc),
OpConcat => self.op_concat(&mut reader, mc),
OpUpdate => self.op_update(&mut reader, mc),
OpAdd => op_add(self, ctx, &mut reader, mc),
OpSub => op_sub(self, &mut reader, mc),
OpMul => op_mul(self, &mut reader, mc),
OpDiv => op_div(self, &mut reader, mc),
OpEq => op_eq(self, ctx, &mut reader, mc),
OpNeq => op_neq(self, ctx, &mut reader, mc),
OpLt => op_lt(self, ctx, &mut reader, mc),
OpGt => op_gt(self, ctx, &mut reader, mc),
OpLeq => op_leq(self, ctx, &mut reader, mc),
OpGeq => op_geq(self, ctx, &mut reader, mc),
OpConcat => op_concat(self, &mut reader, mc),
OpUpdate => op_update(self, &mut reader, mc),
OpNeg => self.op_neg(&mut reader, mc),
OpNot => self.op_not(&mut reader, mc),
OpNeg => op_neg(self, &mut reader, mc),
OpNot => op_not(self, &mut reader, mc),
JumpIfFalse => self.op_jump_if_false(&mut reader, mc),
JumpIfTrue => self.op_jump_if_true(&mut reader, mc),
Jump => self.op_jump(&mut reader),
JumpIfFalse => op_jump_if_false(self, &mut reader, mc),
JumpIfTrue => op_jump_if_true(self, &mut reader, mc),
Jump => op_jump(self, &mut reader),
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc),
CoerceToString => self.op_coerce_to_string(&mut reader, mc),
ResolvePath => self.op_resolve_path(ctx, &mut reader, mc),
ConcatStrings => op_concat_strings(self, ctx, &mut reader, mc),
CoerceToString => op_coerce_to_string(self, &mut reader, mc),
ResolvePath => op_resolve_path(self, ctx, &mut reader, mc),
Assert => self.op_assert(ctx, &mut reader, mc),
Assert => op_assert(self, ctx, &mut reader, mc),
LookupWith => self.op_lookup_with(ctx, &mut reader, mc),
LookupWith => op_lookup_with(self, ctx, &mut reader, mc),
LoadBuiltins => self.op_load_builtins(),
LoadBuiltin => self.op_load_builtin(&mut reader),
LoadBuiltins => op_load_builtins(self),
LoadBuiltin => op_load_builtin(self, &mut reader),
LoadReplBinding => self.op_load_repl_binding(&mut reader),
LoadScopedBinding => self.op_load_scoped_binding(ctx, &mut reader, mc),
LoadReplBinding => op_load_repl_binding(self, &mut reader),
LoadScopedBinding => op_load_scoped_binding(self, ctx, &mut reader, mc),
Illegal => unreachable!(),
};