184 lines
6.3 KiB
Rust
184 lines
6.3 KiB
Rust
use fix_bytecode::PrimOpPhase;
|
|
use fix_error::Error;
|
|
use fix_runtime::{resolve_operand, *};
|
|
use gc_arena::{Gc, Mutation, RefLock};
|
|
|
|
use crate::{
|
|
BytecodeReader, CallFrame, Closure, Env, ForceMode, Step, ThunkState, VmRuntimeCtx,
|
|
VmRuntimeCtxExt,
|
|
};
|
|
|
|
impl<'gc> crate::Vm<'gc> {
|
|
#[inline(always)]
|
|
pub(crate) fn call(
|
|
&mut self,
|
|
reader: &mut BytecodeReader<'_>,
|
|
mc: &Mutation<'gc>,
|
|
arg: Value<'gc>,
|
|
resume_pc: usize,
|
|
) -> Step {
|
|
let func = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
if self.call_depth > 10000 {
|
|
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
|
}
|
|
self.call_depth += 1;
|
|
if let Some(closure) = func.as_gc::<Closure>() {
|
|
if closure.pattern.is_some() {
|
|
// FIXME: better DX...
|
|
self.push(func.relax());
|
|
self.push(arg);
|
|
self.call_stack.push(CallFrame {
|
|
pc: resume_pc,
|
|
thunk: None,
|
|
env: self.env,
|
|
});
|
|
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
|
|
return Step::Continue(());
|
|
}
|
|
|
|
let ip = closure.ip;
|
|
let n_locals = closure.n_locals;
|
|
let env = closure.env;
|
|
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
|
|
self.call_stack.push(CallFrame {
|
|
pc: resume_pc,
|
|
thunk: None,
|
|
env: self.env,
|
|
});
|
|
reader.set_pc(ip as usize);
|
|
self.env = new_env;
|
|
} else if let Some(primop) = func.as_inline::<PrimOp>() {
|
|
if primop.arity == 1 {
|
|
self.push(arg);
|
|
self.call_stack.push(CallFrame {
|
|
pc: resume_pc,
|
|
thunk: None,
|
|
env: self.env,
|
|
});
|
|
reader.set_pc(primop.dispatch_ip as usize)
|
|
} else {
|
|
let app = PrimOpApp {
|
|
primop,
|
|
arity: primop.arity - 1,
|
|
args: [arg, Value::default(), Value::default()],
|
|
};
|
|
self.push(Value::new_gc(Gc::new(mc, app)));
|
|
}
|
|
} else if let Some(app) = func.as_gc::<PrimOpApp>() {
|
|
if app.arity == 1 {
|
|
for i in 0..app.primop.arity - 1 {
|
|
self.push(app.args[i as usize]);
|
|
}
|
|
self.push(arg);
|
|
self.call_stack.push(CallFrame {
|
|
pc: resume_pc,
|
|
thunk: None,
|
|
env: self.env,
|
|
});
|
|
reader.set_pc(app.primop.dispatch_ip as usize)
|
|
} else {
|
|
let position = (app.primop.arity - app.arity) as usize;
|
|
let mut new_app = PrimOpApp {
|
|
arity: app.arity - 1,
|
|
..*app
|
|
};
|
|
new_app.args[position] = arg;
|
|
self.push(Value::new_gc(Gc::new(mc, new_app)))
|
|
}
|
|
} else if let Some(attrs) = func.as_gc::<AttrSet>()
|
|
&& let Some(functor) = attrs.lookup(self.functor_sym)
|
|
{
|
|
// f arg => (f.__functor f) arg
|
|
//
|
|
// Stage the work for `CallFunctor1` so retries during force are
|
|
// safe: the stack invariant `[..., orig_arg, self, functor]`
|
|
// holds every time control re-enters phase 1.
|
|
self.call_depth -= 1;
|
|
self.call_stack.push(CallFrame {
|
|
pc: resume_pc,
|
|
thunk: None,
|
|
env: self.env,
|
|
});
|
|
self.push(arg);
|
|
self.push(func.relax());
|
|
self.push(functor);
|
|
reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize);
|
|
return Step::Continue(());
|
|
} else {
|
|
return self.finish_err(Error::eval_error(format!(
|
|
"attempt to call something which is not a function but {}",
|
|
func.ty()
|
|
)));
|
|
}
|
|
Step::Continue(())
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) fn op_call(
|
|
&mut self,
|
|
ctx: &impl VmRuntimeCtx,
|
|
reader: &mut BytecodeReader<'_>,
|
|
mc: &Mutation<'gc>,
|
|
) -> Step {
|
|
let arg = resolve_operand(&reader.read_operand_data(), mc, ctx, self);
|
|
let pc = reader.pc();
|
|
self.call(reader, mc, arg, pc)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) fn op_return(
|
|
&mut self,
|
|
ctx: &mut impl VmRuntimeCtx,
|
|
reader: &mut BytecodeReader<'_>,
|
|
mc: &Mutation<'gc>,
|
|
) -> Step {
|
|
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
|
let Some(CallFrame {
|
|
pc: ret_pc,
|
|
thunk,
|
|
env,
|
|
}) = self.call_stack.pop()
|
|
else {
|
|
match self.force_mode {
|
|
ForceMode::AsIs => return self.finish_ok(ctx.convert_value(val.relax())),
|
|
ForceMode::Shallow => {
|
|
self.push(val.relax());
|
|
reader.set_pc(PrimOpPhase::ForceResultShallow.ip() as usize);
|
|
return Step::Continue(());
|
|
}
|
|
ForceMode::Deep => {
|
|
self.push(val.relax());
|
|
self.push(val.relax());
|
|
self.call_stack.push(CallFrame {
|
|
pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize,
|
|
thunk: None,
|
|
env: self.env,
|
|
});
|
|
self.call_depth += 1;
|
|
reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize);
|
|
return Step::Continue(());
|
|
}
|
|
}
|
|
};
|
|
reader.set_pc(ret_pc);
|
|
if let Some(outer_thunk) = thunk {
|
|
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
|
} else {
|
|
self.call_depth -= 1;
|
|
self.push(val.relax())
|
|
}
|
|
self.env = env;
|
|
Step::Continue(())
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) fn op_dispatch_primop(
|
|
&mut self,
|
|
ctx: &mut impl VmRuntimeCtx,
|
|
reader: &mut BytecodeReader<'_>,
|
|
mc: &Mutation<'gc>,
|
|
) -> Step {
|
|
crate::primops::dispatch_primop(self, ctx, reader, mc)
|
|
}
|
|
}
|