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
+112 -116
View File
@@ -1,8 +1,10 @@
#![warn(clippy::unwrap_used)]
use std::path::PathBuf;
use fix_builtins::{BUILTINS, BuiltinId};
use fix_codegen::{AttrKeyType, InstructionPtr, OperandType};
use fix_common::StringId;
use fix_common::{StringId, Symbol};
use fix_error::{Error, Result, Source};
use gc_arena::arena::CollectionPhase;
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>;
#[allow(dead_code)]
enum VmError {
Catchable(String),
Uncatchable(Box<Error>),
@@ -109,6 +112,7 @@ impl<T: VmContext> VmContextExt for T {
}
}
#[allow(dead_code)]
pub struct Vm<C: VmContext> {
arena: Arena<Rootable![GcRoot<'_>]>,
error_context: Vec<ErrorFrame>,
@@ -128,7 +132,7 @@ struct GcRoot<'gc> {
empty_list: Value<'gc>,
empty_attrs: 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> {
@@ -197,15 +201,10 @@ impl<'gc> GcRoot<'gc> {
empty_list: Value::new_gc(Gc::new(mc, List::default())),
empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())),
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)]
fn push_stack(&mut self, val: Value<'gc>) {
self.stack.push(val);
@@ -244,6 +243,7 @@ impl<'gc> GcRoot<'gc> {
}
}
#[allow(dead_code)]
struct ErrorFrame {
span_id: u32,
message: Option<String>,
@@ -281,7 +281,7 @@ impl OperandData {
match *self {
OperandData::Const(sv) => sv.into(),
OperandData::Local { layer, idx } => {
let mut cur = root.env();
let mut cur = root.env;
for _ in 0..layer {
let prev = cur.borrow().prev.expect("env chain too short");
cur = prev;
@@ -332,12 +332,6 @@ impl<C: VmContext> Vm<C> {
pub fn run(mut self) -> Result<fix_common::Value> {
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;
loop {
match self
@@ -391,7 +385,6 @@ impl<'gc> GcRoot<'gc> {
match ty {
OperandType::Const => {
let id = read!(u32);
#[allow(clippy::unwrap_used)]
OperandData::Const(ctx.get_const(id))
}
OperandType::Local => {
@@ -438,12 +431,12 @@ impl<'gc> GcRoot<'gc> {
thunk: Some(thunk),
stack_depth: $depth,
pc: $inst_start,
env: self.env(),
env: self.env,
with_env: self.with_env,
});
*pc = ip;
self.current_env = Some(env);
self.env = env;
self.with_env = with_env;
*state = ThunkState::Blackhole;
continue 'dispatch;
@@ -499,12 +492,12 @@ impl<'gc> GcRoot<'gc> {
LoadLocal => {
let idx = read!(u32) as usize;
self.push_stack(self.env().borrow().locals[idx]);
self.push_stack(self.env.borrow().locals[idx]);
}
LoadOuter => {
let layer = read!(u8);
let idx = read!(u32) as usize;
let mut cur = self.env();
let mut cur = self.env;
for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
cur = prev;
@@ -515,11 +508,11 @@ impl<'gc> GcRoot<'gc> {
StoreLocal => {
let idx = read!(u32) as usize;
let val = self.pop_stack();
self.env().borrow_mut(mc).locals[idx] = val;
self.env.borrow_mut(mc).locals[idx] = val;
}
AllocLocals => {
let count = read!(u32) as usize;
self.env()
self.env
.borrow_mut(mc)
.locals
.extend(std::iter::repeat_n(Value::default(), count));
@@ -531,7 +524,7 @@ impl<'gc> GcRoot<'gc> {
mc,
RefLock::new(ThunkState::Pending {
ip: entry_point as usize,
env: self.env(),
env: self.env,
with_env: self.with_env,
}),
);
@@ -545,7 +538,7 @@ impl<'gc> GcRoot<'gc> {
Closure {
ip: entry_point,
n_locals,
env: self.env(),
env: self.env,
pattern: None,
},
);
@@ -588,7 +581,7 @@ impl<'gc> GcRoot<'gc> {
Closure {
ip: entry_point,
n_locals,
env: self.env(),
env: self.env,
pattern: Some(pattern),
},
);
@@ -618,11 +611,11 @@ impl<'gc> GcRoot<'gc> {
pc: *pc,
stack_depth: 0,
thunk: None,
env: self.env(),
env: self.env,
with_env: self.with_env,
});
*pc = ip as usize;
self.current_env = Some(new_env);
self.env = new_env;
}
} else {
todo!("call other types: {func:?}")
@@ -1008,37 +1001,44 @@ impl<'gc> GcRoot<'gc> {
};
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 mut cur = self.with_env;
let mut found_val = None;
while let Some(scope) = cur {
// Using restrict() to force extraction sync due to CPS structure.
let env_val = scope
.env
.restrict()
.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"));
let Some(&WithEnv { env, prev }) = self.with_env.as_deref() else {
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);
if let Some(v) = attrs.lookup(name) {
found_val = Some(v);
break;
}
cur = scope.prev;
}
let env = self.pop_stack().as_gc::<AttrSet>().unwrap();
let Some(val) = env.lookup(name) else {
*pc = inst_start_pc;
self.with_env = prev;
continue 'dispatch;
};
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}'")));
}
self.push_stack(val);
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
unreachable!()
};
self.with_env = with_env;
}
LoadBuiltins => {
@@ -1184,84 +1184,80 @@ impl<'gc> GcRoot<'gc> {
mc: &Mutation<'gc>,
) -> Option<Result<fix_common::Value>> {
let ret_inst_pc = *pc - 1;
#[deny(unused_variables)]
if let Some(CallFrame {
let Some(CallFrame {
pc: ret_pc,
stack_depth,
thunk,
env,
with_env,
}) = self.call_stack.pop()
{
*pc = ret_pc;
if let Some(outer_thunk) = thunk {
let val = self.pop_stack();
match val.restrict() {
Ok(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if ctx.bytecode().get(ret_pc).copied()
== Some(fix_codegen::Op::Return as u8)
{
self.push_stack(val.relax());
}
else {
// Evaluation complete, return value
// FIXME: ForceMode
let val = self.pop_stack();
return Some(Ok(ctx.convert_value(val)));
};
*pc = ret_pc;
if let Some(outer_thunk) = thunk {
let val = self.pop_stack();
match val.restrict() {
Ok(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if ctx.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8) {
self.push_stack(val.relax());
}
Err(inner_thunk) => {
let mut state = inner_thunk.borrow_mut(mc);
match *state {
ThunkState::Pending {
ip: inner_ip,
}
// Recursively force the thunk
// TODO: extract forcing logic
Err(inner_thunk) => {
let mut state = inner_thunk.borrow_mut(mc);
match *state {
ThunkState::Pending {
ip: inner_ip,
env: inner_env,
with_env: inner_with_env,
} => {
self.call_stack.push(CallFrame {
pc: ret_pc,
stack_depth,
thunk: Some(outer_thunk),
env,
with_env,
});
self.call_stack.push(CallFrame {
pc: ret_inst_pc,
stack_depth: 0,
thunk: Some(inner_thunk),
env: inner_env,
with_env: inner_with_env,
} => {
self.call_stack.push(CallFrame {
pc: ret_pc,
stack_depth,
thunk: Some(outer_thunk),
env,
with_env,
});
self.call_stack.push(CallFrame {
pc: ret_inst_pc,
stack_depth: 0,
thunk: Some(inner_thunk),
env: inner_env,
with_env: inner_with_env,
});
*state = ThunkState::Blackhole;
*pc = inner_ip;
self.current_env = Some(inner_env);
self.with_env = inner_with_env;
return None;
}
ThunkState::Evaluated(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if ctx.bytecode().get(ret_pc).copied()
== Some(fix_codegen::Op::Return as u8)
{
self.push_stack(val.relax());
}
}
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
ThunkState::Blackhole => {
return Some(Err(Error::eval_error(
"infinite recursion encountered",
)));
});
*state = ThunkState::Blackhole;
*pc = inner_ip;
self.env = inner_env;
self.with_env = inner_with_env;
return None;
}
ThunkState::Evaluated(val) => {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
if ctx.bytecode().get(ret_pc).copied()
== Some(fix_codegen::Op::Return as u8)
{
self.push_stack(val.relax());
}
}
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
ThunkState::Blackhole => {
return Some(Err(Error::eval_error("infinite recursion encountered")));
}
}
}
} else {
self.call_depth -= 1;
}
self.current_env = Some(env);
self.with_env = with_env;
return None;
} else {
self.call_depth -= 1;
}
// FIXME: ForceMode
self.current_env = None;
self.with_env = None;
let val = self.pop_stack();
Some(Ok(ctx.convert_value(val)))
self.env = env;
self.with_env = with_env;
None
}
fn handle_vm_error(&mut self, e: VmError) -> Action {