Files
nix-js/fix-vm/src/instructions/calls.rs
T

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)
}
}