LookupWith: retry
This commit is contained in:
@@ -336,10 +336,11 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
}
|
||||
Op::PushWith => ("PushWith", String::new()),
|
||||
Op::PopWith => ("PopWith", String::new()),
|
||||
Op::WithLookup => {
|
||||
Op::PrepareWith => ("PrepareWith", String::new()),
|
||||
Op::LookupWith => {
|
||||
let idx = self.read_u32();
|
||||
let name = self.ctx.resolve_string(idx);
|
||||
("WithLookup", format!("{:?}", name))
|
||||
("LookupWith", format!("{:?}", name))
|
||||
}
|
||||
|
||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||
|
||||
@@ -79,7 +79,8 @@ pub enum Op {
|
||||
|
||||
PushWith,
|
||||
PopWith,
|
||||
WithLookup,
|
||||
LookupWith,
|
||||
PrepareWith,
|
||||
|
||||
LoadBuiltins,
|
||||
LoadBuiltin,
|
||||
@@ -625,7 +626,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_with(namespace, body, thunks);
|
||||
}
|
||||
&Ir::WithLookup(name) => {
|
||||
self.emit_op(Op::WithLookup);
|
||||
self.emit_op(Op::PrepareWith);
|
||||
self.emit_op(Op::LookupWith);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
}
|
||||
|
||||
+67
-71
@@ -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;
|
||||
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);
|
||||
|
||||
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 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) = attrs.lookup(name) {
|
||||
found_val = Some(v);
|
||||
break;
|
||||
}
|
||||
cur = scope.prev;
|
||||
}
|
||||
|
||||
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,27 +1184,31 @@ 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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if ctx.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8) {
|
||||
self.push_stack(val.relax());
|
||||
}
|
||||
}
|
||||
// Recursively force the thunk
|
||||
// TODO: extract forcing logic
|
||||
Err(inner_thunk) => {
|
||||
let mut state = inner_thunk.borrow_mut(mc);
|
||||
match *state {
|
||||
@@ -1229,7 +1233,7 @@ impl<'gc> GcRoot<'gc> {
|
||||
});
|
||||
*state = ThunkState::Blackhole;
|
||||
*pc = inner_ip;
|
||||
self.current_env = Some(inner_env);
|
||||
self.env = inner_env;
|
||||
self.with_env = inner_with_env;
|
||||
return None;
|
||||
}
|
||||
@@ -1243,9 +1247,7 @@ impl<'gc> GcRoot<'gc> {
|
||||
}
|
||||
ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"),
|
||||
ThunkState::Blackhole => {
|
||||
return Some(Err(Error::eval_error(
|
||||
"infinite recursion encountered",
|
||||
)));
|
||||
return Some(Err(Error::eval_error("infinite recursion encountered")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1253,15 +1255,9 @@ impl<'gc> GcRoot<'gc> {
|
||||
} else {
|
||||
self.call_depth -= 1;
|
||||
}
|
||||
self.current_env = Some(env);
|
||||
self.env = env;
|
||||
self.with_env = with_env;
|
||||
return None;
|
||||
}
|
||||
// FIXME: ForceMode
|
||||
self.current_env = None;
|
||||
self.with_env = None;
|
||||
let val = self.pop_stack();
|
||||
Some(Ok(ctx.convert_value(val)))
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_vm_error(&mut self, e: VmError) -> Action {
|
||||
|
||||
Reference in New Issue
Block a user