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::(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::() { 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::() { 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::() { 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::() && 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::(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) } }