document trying mechanism

This commit is contained in:
2026-05-03 22:05:54 +08:00
parent 430d1218de
commit 6a8ec079ea
8 changed files with 56 additions and 28 deletions
+10 -10
View File
@@ -13,7 +13,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}"))); let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
self.push(Value::new_gc(ns)); self.push(Value::new_gc(ns));
@@ -47,7 +47,7 @@ impl<'gc> crate::Vm<'gc> {
int_op: fn(i64, i64) -> i64, int_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64, float_op: fn(f64, f64) -> f64,
) -> Step { ) -> Step {
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let res = numeric_binop(lhs, rhs, mc, int_op, float_op); let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
match res { match res {
Ok(val) => { Ok(val) => {
@@ -60,7 +60,7 @@ impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match (get_num(lhs), get_num(rhs)) { match (get_num(lhs), get_num(rhs)) {
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => { (_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error( return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
@@ -86,7 +86,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let eq = match self.values_equal(ctx, lhs, rhs) { let eq = match self.values_equal(ctx, lhs, rhs) {
Ok(eq) => eq, Ok(eq) => eq,
Err(e) => return self.finish_vm_err(e), Err(e) => return self.finish_vm_err(e),
@@ -102,7 +102,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let eq = match self.values_equal(ctx, lhs, rhs) { let eq = match self.values_equal(ctx, lhs, rhs) {
Ok(eq) => eq, Ok(eq) => eq,
Err(e) => return self.finish_vm_err(e), Err(e) => return self.finish_vm_err(e),
@@ -158,7 +158,7 @@ impl<'gc> crate::Vm<'gc> {
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
pred: fn(Ordering) -> bool, pred: fn(Ordering) -> bool,
) -> Step { ) -> Step {
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match self.compare_values_inner(ctx, pred, lhs, rhs) { match self.compare_values_inner(ctx, pred, lhs, rhs) {
Ok(()) => Step::Continue(()), Ok(()) => Step::Continue(()),
Err(e) => self.finish_vm_err(e), Err(e) => self.finish_vm_err(e),
@@ -171,7 +171,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(reader, mc)?; let (l, r) = self.force_and_retry::<(Gc<List>, Gc<List>)>(reader, mc)?;
let mut items = smallvec::SmallVec::new(); let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l.inner.borrow()); items.extend_from_slice(&l.inner.borrow());
items.extend_from_slice(&r.inner.borrow()); items.extend_from_slice(&r.inner.borrow());
@@ -190,14 +190,14 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (l, r) = self.try_force::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?; let (l, r) = self.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
self.push(Value::new_gc(l.merge(&r, mc))); self.push(Value::new_gc(l.merge(&r, mc)));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_neg(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_neg(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let rhs = self.try_force::<NixNum>(reader, mc)?; let rhs = self.force_and_retry::<NixNum>(reader, mc)?;
match rhs { match rhs {
NixNum::Int(int) => self.push(Value::make_int(-int, mc)), NixNum::Int(int) => self.push(Value::make_int(-int, mc)),
NixNum::Float(float) => self.push(Value::new_float(-float)), NixNum::Float(float) => self.push(Value::new_float(-float)),
@@ -207,7 +207,7 @@ impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_not(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_not(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let rhs = self.try_force::<bool>(reader, mc)?; let rhs = self.force_and_retry::<bool>(reader, mc)?;
self.push(Value::new_inline(!rhs)); self.push(Value::new_inline(!rhs));
Step::Continue(()) Step::Continue(())
} }
+3 -3
View File
@@ -101,7 +101,7 @@ impl<'gc> Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let ret = self.try_force::<bool>(reader, mc)?; let ret = self.force_and_retry::<bool>(reader, mc)?;
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let idx = self.peek(1).as_inline::<i32>().unwrap(); let idx = self.peek(1).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
@@ -368,7 +368,7 @@ impl<'gc> Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = self.try_force::<StrictValue>(reader, mc)?; let val = self.force_and_retry::<StrictValue>(reader, mc)?;
self.finish_ok(ctx.convert_value(val.relax())) self.finish_ok(ctx.convert_value(val.relax()))
} }
@@ -378,7 +378,7 @@ impl<'gc> Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (func, attrset) = self.try_force::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?; let (func, attrset) = self.force_and_retry::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
let Closure { let Closure {
ip, ip,
+2 -2
View File
@@ -17,7 +17,7 @@ impl<'gc> crate::Vm<'gc> {
arg: Value<'gc>, arg: Value<'gc>,
resume_pc: usize, resume_pc: usize,
) -> Step { ) -> Step {
let func = self.try_force::<StrictValue>(reader, mc)?; let func = self.force_and_retry::<StrictValue>(reader, mc)?;
if self.call_depth > 10000 { if self.call_depth > 10000 {
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded")); return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
} }
@@ -116,7 +116,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = self.try_force::<StrictValue>(reader, mc)?; let val = self.force_and_retry::<StrictValue>(reader, mc)?;
let Some(CallFrame { let Some(CallFrame {
pc: ret_pc, pc: ret_pc,
stack_depth, stack_depth,
+6 -6
View File
@@ -75,7 +75,7 @@ impl<'gc> crate::Vm<'gc> {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let key = reader.read_string_id(); let key = reader.read_string_id();
let attrset = self.try_force::<Gc<AttrSet>>(reader, mc)?; let attrset = self.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
match attrset.lookup(key) { match attrset.lookup(key) {
Some(v) => { Some(v) => {
@@ -95,7 +95,7 @@ impl<'gc> crate::Vm<'gc> {
) -> Step { ) -> Step {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let (attrset, key_val) = self.try_force::<(Gc<AttrSet>, StrictValue)>(reader, mc)?; let (attrset, key_val) = self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) { let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id, Ok(id) => id,
@@ -191,7 +191,7 @@ impl<'gc> crate::Vm<'gc> {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let key = reader.read_string_id(); let key = reader.read_string_id();
let current = self.try_force::<StrictValue>(reader, mc)?; let current = self.force_and_retry::<StrictValue>(reader, mc)?;
match current match current
.as_gc::<AttrSet>() .as_gc::<AttrSet>()
@@ -214,7 +214,7 @@ impl<'gc> crate::Vm<'gc> {
) -> Step { ) -> Step {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let (current, key_val) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let (current, key_val) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) { let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id, Ok(id) => id,
@@ -254,7 +254,7 @@ impl<'gc> crate::Vm<'gc> {
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let key = reader.read_string_id(); let key = reader.read_string_id();
let current = self.try_force::<StrictValue>(reader, mc)?; let current = self.force_and_retry::<StrictValue>(reader, mc)?;
self.push(Value::new_inline( self.push(Value::new_inline(
current current
@@ -275,7 +275,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let (current, dyn_key) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let (current, dyn_key) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(dyn_key) { let key_sid = match ctx.get_string_id(dyn_key) {
Ok(id) => id, Ok(id) => id,
+2 -2
View File
@@ -9,7 +9,7 @@ impl<'gc> crate::Vm<'gc> {
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let offset = reader.read_i32(); let offset = reader.read_i32();
let cond = self.try_force::<StrictValue>(reader, mc)?; let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
if cond.as_inline::<bool>() == Some(false) { if cond.as_inline::<bool>() == Some(false) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
} }
@@ -23,7 +23,7 @@ impl<'gc> crate::Vm<'gc> {
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let offset = reader.read_i32(); let offset = reader.read_i32();
let cond = self.try_force::<StrictValue>(reader, mc)?; let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
if cond.as_inline::<bool>() == Some(true) { if cond.as_inline::<bool>() == Some(true) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
} }
+1 -1
View File
@@ -42,7 +42,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let val = self.try_force::<StrictValue>(reader, mc)?; let val = self.force_and_retry::<StrictValue>(reader, mc)?;
if val.is::<StringId>() || val.is::<NixString>() { if val.is::<StringId>() || val.is::<NixString>() {
self.push(val.relax()); self.push(val.relax());
} else { } else {
+1 -1
View File
@@ -66,7 +66,7 @@ impl<'gc> crate::Vm<'gc> {
))); )));
}; };
self.push(env); self.push(env);
let env = self.try_force::<Gc<AttrSet>>(reader, mc)?; let env = self.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
let Some(val) = env.lookup(name) else { let Some(val) = env.lookup(name) else {
reader.set_pc(reader.inst_start_pc()); reader.set_pc(reader.inst_start_pc());
self.with_env = prev; self.with_env = prev;
+31 -3
View File
@@ -420,17 +420,45 @@ impl<'gc> Vm<'gc> {
.expect("forced") .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)] #[inline(always)]
pub(crate) fn try_force<T: Forced<'gc>>( pub(crate) fn force_and_retry<T: Forced<'gc>>(
&mut self, &mut self,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> std::ops::ControlFlow<Break, T> { ) -> std::ops::ControlFlow<Break, T> {
self.try_force_to_pc(reader, mc, reader.inst_start_pc()) 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)] #[inline(always)]
pub(crate) fn try_force_to_pc<T: Forced<'gc>>( pub(crate) fn force_and_retry_pc<T: Forced<'gc>>(
&mut self, &mut self,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,