LookupWith: retry
This commit is contained in:
@@ -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()),
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user