LookupWith: retry

This commit is contained in:
2026-04-18 16:44:06 +08:00
parent f372ebcb8e
commit df9664f5c4
3 changed files with 119 additions and 120 deletions
+3 -2
View File
@@ -336,10 +336,11 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
} }
Op::PushWith => ("PushWith", String::new()), Op::PushWith => ("PushWith", String::new()),
Op::PopWith => ("PopWith", String::new()), Op::PopWith => ("PopWith", String::new()),
Op::WithLookup => { Op::PrepareWith => ("PrepareWith", String::new()),
Op::LookupWith => {
let idx = self.read_u32(); let idx = self.read_u32();
let name = self.ctx.resolve_string(idx); let name = self.ctx.resolve_string(idx);
("WithLookup", format!("{:?}", name)) ("LookupWith", format!("{:?}", name))
} }
Op::LoadBuiltins => ("LoadBuiltins", String::new()), Op::LoadBuiltins => ("LoadBuiltins", String::new()),
+4 -2
View File
@@ -79,7 +79,8 @@ pub enum Op {
PushWith, PushWith,
PopWith, PopWith,
WithLookup, LookupWith,
PrepareWith,
LoadBuiltins, LoadBuiltins,
LoadBuiltin, LoadBuiltin,
@@ -625,7 +626,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_with(namespace, body, thunks); self.emit_with(namespace, body, thunks);
} }
&Ir::WithLookup(name) => { &Ir::WithLookup(name) => {
self.emit_op(Op::WithLookup); self.emit_op(Op::PrepareWith);
self.emit_op(Op::LookupWith);
self.emit_str_id(name); self.emit_str_id(name);
} }
} }
+67 -71
View File
@@ -1,8 +1,10 @@
#![warn(clippy::unwrap_used)]
use std::path::PathBuf; use std::path::PathBuf;
use fix_builtins::{BUILTINS, BuiltinId}; use fix_builtins::{BUILTINS, BuiltinId};
use fix_codegen::{AttrKeyType, InstructionPtr, OperandType}; use fix_codegen::{AttrKeyType, InstructionPtr, OperandType};
use fix_common::StringId; use fix_common::{StringId, Symbol};
use fix_error::{Error, Result, Source}; use fix_error::{Error, Result, Source};
use gc_arena::arena::CollectionPhase; use gc_arena::arena::CollectionPhase;
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable}; use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
@@ -19,6 +21,7 @@ pub use value::StaticValue;
type VmResult<T> = std::result::Result<T, VmError>; type VmResult<T> = std::result::Result<T, VmError>;
#[allow(dead_code)]
enum VmError { enum VmError {
Catchable(String), Catchable(String),
Uncatchable(Box<Error>), Uncatchable(Box<Error>),
@@ -109,6 +112,7 @@ impl<T: VmContext> VmContextExt for T {
} }
} }
#[allow(dead_code)]
pub struct Vm<C: VmContext> { pub struct Vm<C: VmContext> {
arena: Arena<Rootable![GcRoot<'_>]>, arena: Arena<Rootable![GcRoot<'_>]>,
error_context: Vec<ErrorFrame>, error_context: Vec<ErrorFrame>,
@@ -128,7 +132,7 @@ struct GcRoot<'gc> {
empty_list: Value<'gc>, empty_list: Value<'gc>,
empty_attrs: Value<'gc>, empty_attrs: Value<'gc>,
import_cache: HashMap<PathBuf, Value<'gc>>, import_cache: HashMap<PathBuf, Value<'gc>>,
current_env: Option<GcEnv<'gc>>, env: GcEnv<'gc>,
} }
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc> { fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc> {
@@ -197,15 +201,10 @@ impl<'gc> GcRoot<'gc> {
empty_list: Value::new_gc(Gc::new(mc, List::default())), empty_list: Value::new_gc(Gc::new(mc, List::default())),
empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())), empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())),
import_cache: HashMap::new(), import_cache: HashMap::new(),
current_env: None, env: Gc::new(mc, RefLock::new(Env::empty())),
} }
} }
#[inline(always)]
fn env(&self) -> Gc<'gc, RefLock<Env<'gc>>> {
self.current_env.expect("no current env")
}
#[inline(always)] #[inline(always)]
fn push_stack(&mut self, val: Value<'gc>) { fn push_stack(&mut self, val: Value<'gc>) {
self.stack.push(val); self.stack.push(val);
@@ -244,6 +243,7 @@ impl<'gc> GcRoot<'gc> {
} }
} }
#[allow(dead_code)]
struct ErrorFrame { struct ErrorFrame {
span_id: u32, span_id: u32,
message: Option<String>, message: Option<String>,
@@ -281,7 +281,7 @@ impl OperandData {
match *self { match *self {
OperandData::Const(sv) => sv.into(), OperandData::Const(sv) => sv.into(),
OperandData::Local { layer, idx } => { OperandData::Local { layer, idx } => {
let mut cur = root.env(); let mut cur = root.env;
for _ in 0..layer { for _ in 0..layer {
let prev = cur.borrow().prev.expect("env chain too short"); let prev = cur.borrow().prev.expect("env chain too short");
cur = prev; cur = prev;
@@ -332,12 +332,6 @@ impl<C: VmContext> Vm<C> {
pub fn run(mut self) -> Result<fix_common::Value> { pub fn run(mut self) -> Result<fix_common::Value> {
const COLLECTOR_GRANULARITY: f64 = 1024.0; const COLLECTOR_GRANULARITY: f64 = 1024.0;
self.arena.mutate_root(|mc, root| {
if root.current_env.is_none() {
root.current_env = Some(Gc::new(mc, RefLock::new(Env::empty())));
}
});
let mut pc = self.pc; let mut pc = self.pc;
loop { loop {
match self match self
@@ -391,7 +385,6 @@ impl<'gc> GcRoot<'gc> {
match ty { match ty {
OperandType::Const => { OperandType::Const => {
let id = read!(u32); let id = read!(u32);
#[allow(clippy::unwrap_used)]
OperandData::Const(ctx.get_const(id)) OperandData::Const(ctx.get_const(id))
} }
OperandType::Local => { OperandType::Local => {
@@ -438,12 +431,12 @@ impl<'gc> GcRoot<'gc> {
thunk: Some(thunk), thunk: Some(thunk),
stack_depth: $depth, stack_depth: $depth,
pc: $inst_start, pc: $inst_start,
env: self.env(), env: self.env,
with_env: self.with_env, with_env: self.with_env,
}); });
*pc = ip; *pc = ip;
self.current_env = Some(env); self.env = env;
self.with_env = with_env; self.with_env = with_env;
*state = ThunkState::Blackhole; *state = ThunkState::Blackhole;
continue 'dispatch; continue 'dispatch;
@@ -499,12 +492,12 @@ impl<'gc> GcRoot<'gc> {
LoadLocal => { LoadLocal => {
let idx = read!(u32) as usize; let idx = read!(u32) as usize;
self.push_stack(self.env().borrow().locals[idx]); self.push_stack(self.env.borrow().locals[idx]);
} }
LoadOuter => { LoadOuter => {
let layer = read!(u8); let layer = read!(u8);
let idx = read!(u32) as usize; let idx = read!(u32) as usize;
let mut cur = self.env(); let mut cur = self.env;
for _ in 0..layer { for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
cur = prev; cur = prev;
@@ -515,11 +508,11 @@ impl<'gc> GcRoot<'gc> {
StoreLocal => { StoreLocal => {
let idx = read!(u32) as usize; let idx = read!(u32) as usize;
let val = self.pop_stack(); let val = self.pop_stack();
self.env().borrow_mut(mc).locals[idx] = val; self.env.borrow_mut(mc).locals[idx] = val;
} }
AllocLocals => { AllocLocals => {
let count = read!(u32) as usize; let count = read!(u32) as usize;
self.env() self.env
.borrow_mut(mc) .borrow_mut(mc)
.locals .locals
.extend(std::iter::repeat_n(Value::default(), count)); .extend(std::iter::repeat_n(Value::default(), count));
@@ -531,7 +524,7 @@ impl<'gc> GcRoot<'gc> {
mc, mc,
RefLock::new(ThunkState::Pending { RefLock::new(ThunkState::Pending {
ip: entry_point as usize, ip: entry_point as usize,
env: self.env(), env: self.env,
with_env: self.with_env, with_env: self.with_env,
}), }),
); );
@@ -545,7 +538,7 @@ impl<'gc> GcRoot<'gc> {
Closure { Closure {
ip: entry_point, ip: entry_point,
n_locals, n_locals,
env: self.env(), env: self.env,
pattern: None, pattern: None,
}, },
); );
@@ -588,7 +581,7 @@ impl<'gc> GcRoot<'gc> {
Closure { Closure {
ip: entry_point, ip: entry_point,
n_locals, n_locals,
env: self.env(), env: self.env,
pattern: Some(pattern), pattern: Some(pattern),
}, },
); );
@@ -618,11 +611,11 @@ impl<'gc> GcRoot<'gc> {
pc: *pc, pc: *pc,
stack_depth: 0, stack_depth: 0,
thunk: None, thunk: None,
env: self.env(), env: self.env,
with_env: self.with_env, with_env: self.with_env,
}); });
*pc = ip as usize; *pc = ip as usize;
self.current_env = Some(new_env); self.env = new_env;
} }
} else { } else {
todo!("call other types: {func:?}") todo!("call other types: {func:?}")
@@ -1008,37 +1001,44 @@ impl<'gc> GcRoot<'gc> {
}; };
self.with_env = scope.prev; self.with_env = scope.prev;
} }
WithLookup => { PrepareWith => {
self.call_stack.push(CallFrame {
// sentinel value
pc: usize::MAX,
stack_depth: 0,
thunk: None,
env: self.env,
with_env: self.with_env,
})
}
LookupWith => {
let name = read!(StringId); let name = read!(StringId);
let mut cur = self.with_env; let Some(&WithEnv { env, prev }) = self.with_env.as_deref() else {
let mut found_val = None; let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
unreachable!()
};
self.with_env = with_env;
return Action::Done(Err(Error::eval_error(format!(
"undefined variable '{}'",
Symbol::from(ctx.resolve_string(name))
))));
};
self.push_stack(env);
try_force!(0, inst_start_pc);
while let Some(scope) = cur { let env = self.pop_stack().as_gc::<AttrSet>().unwrap();
// Using restrict() to force extraction sync due to CPS structure. let Some(val) = env.lookup(name) else {
let env_val = scope *pc = inst_start_pc;
.env self.with_env = prev;
.restrict() continue 'dispatch;
.unwrap_or_else(|_| panic!("with scope env must be forced"));
let Some(attrs) = env_val.as_gc::<AttrSet>() else {
return self
.handle_vm_error(vm_err("value in 'with' scope must be a set"));
}; };
if let Some(v) = attrs.lookup(name) { self.push_stack(val);
found_val = Some(v); let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
break; unreachable!()
} };
cur = scope.prev; self.with_env = with_env;
}
if let Some(v) = found_val {
self.push_stack(v);
} else {
let name_str = ctx.resolve_string(name);
return self
.handle_vm_error(vm_err(format!("undefined variable '{name_str}'")));
}
} }
LoadBuiltins => { LoadBuiltins => {
@@ -1184,27 +1184,31 @@ impl<'gc> GcRoot<'gc> {
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Option<Result<fix_common::Value>> { ) -> Option<Result<fix_common::Value>> {
let ret_inst_pc = *pc - 1; let ret_inst_pc = *pc - 1;
#[deny(unused_variables)] let Some(CallFrame {
if let Some(CallFrame {
pc: ret_pc, pc: ret_pc,
stack_depth, stack_depth,
thunk, thunk,
env, env,
with_env, with_env,
}) = self.call_stack.pop() }) = self.call_stack.pop()
{ else {
// Evaluation complete, return value
// FIXME: ForceMode
let val = self.pop_stack();
return Some(Ok(ctx.convert_value(val)));
};
*pc = ret_pc; *pc = ret_pc;
if let Some(outer_thunk) = thunk { if let Some(outer_thunk) = thunk {
let val = self.pop_stack(); let val = self.pop_stack();
match val.restrict() { match val.restrict() {
Ok(val) => { Ok(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if ctx.bytecode().get(ret_pc).copied() if ctx.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8) {
== Some(fix_codegen::Op::Return as u8)
{
self.push_stack(val.relax()); self.push_stack(val.relax());
} }
} }
// Recursively force the thunk
// TODO: extract forcing logic
Err(inner_thunk) => { Err(inner_thunk) => {
let mut state = inner_thunk.borrow_mut(mc); let mut state = inner_thunk.borrow_mut(mc);
match *state { match *state {
@@ -1229,7 +1233,7 @@ impl<'gc> GcRoot<'gc> {
}); });
*state = ThunkState::Blackhole; *state = ThunkState::Blackhole;
*pc = inner_ip; *pc = inner_ip;
self.current_env = Some(inner_env); self.env = inner_env;
self.with_env = inner_with_env; self.with_env = inner_with_env;
return None; return None;
} }
@@ -1243,9 +1247,7 @@ impl<'gc> GcRoot<'gc> {
} }
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"), ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
ThunkState::Blackhole => { ThunkState::Blackhole => {
return Some(Err(Error::eval_error( return Some(Err(Error::eval_error("infinite recursion encountered")));
"infinite recursion encountered",
)));
} }
} }
} }
@@ -1253,15 +1255,9 @@ impl<'gc> GcRoot<'gc> {
} else { } else {
self.call_depth -= 1; self.call_depth -= 1;
} }
self.current_env = Some(env); self.env = env;
self.with_env = with_env; self.with_env = with_env;
return None; None
}
// FIXME: ForceMode
self.current_env = None;
self.with_env = None;
let val = self.pop_stack();
Some(Ok(ctx.convert_value(val)))
} }
fn handle_vm_error(&mut self, e: VmError) -> Action { fn handle_vm_error(&mut self, e: VmError) -> Action {