diff --git a/fix-runtime/src/machine.rs b/fix-runtime/src/machine.rs index 1fe6863..5527da6 100644 --- a/fix-runtime/src/machine.rs +++ b/fix-runtime/src/machine.rs @@ -26,11 +26,15 @@ use crate::{ /// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`) pub trait Machine<'gc> { fn push(&mut self, val: Value<'gc>); + #[must_use] fn pop(&mut self) -> Value<'gc>; + #[must_use] fn peek(&self, depth: usize) -> Value<'gc>; + #[must_use] fn peek_forced(&self, depth: usize) -> StrictValue<'gc>; fn pop_forced(&mut self) -> StrictValue<'gc>; fn replace(&mut self, depth: usize, val: Value<'gc>); + fn drop_n(&mut self, depth: usize); fn stack_len(&self) -> usize; fn force_slot_to_pc( diff --git a/fix-vm/src/dispatch_tailcall.rs b/fix-vm/src/dispatch_tailcall.rs index 02135a4..1bccdfc 100644 --- a/fix-vm/src/dispatch_tailcall.rs +++ b/fix-vm/src/dispatch_tailcall.rs @@ -61,7 +61,7 @@ macro_rules! tail_fn { pc: u32, fuel: u32, ) -> TailResult { - let result = vm.$name(); + let result = crate::instructions::$name(vm); tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel) } }; @@ -76,7 +76,7 @@ macro_rules! tail_fn { fuel: u32, ) -> TailResult { let mut reader = BytecodeReader::from_after_op(bc, pc as usize); - let result = vm.$name(&mut reader); + let result = crate::instructions::$name(vm, &mut reader); tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel) } }; @@ -91,7 +91,7 @@ macro_rules! tail_fn { fuel: u32, ) -> TailResult { let mut reader = BytecodeReader::from_after_op(bc, pc as usize); - let result = vm.$name(&mut reader, mc); + let result = crate::instructions::$name(vm, &mut reader, mc); tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel) } }; @@ -106,7 +106,7 @@ macro_rules! tail_fn { fuel: u32, ) -> TailResult { let mut reader = BytecodeReader::from_after_op(bc, pc as usize); - let result = vm.$name(ctx, &mut reader, mc); + let result = crate::instructions::$name(vm, ctx, &mut reader, mc); tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel) } }; @@ -120,7 +120,7 @@ macro_rules! tail_fn { pc: u32, fuel: u32, ) -> TailResult { - let result = vm.$name(ctx); + let result = crate::instructions::$name(vm, ctx); tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel) } }; diff --git a/fix-vm/src/instructions/arithmetic.rs b/fix-vm/src/instructions/arithmetic.rs index 075841a..8a562f4 100644 --- a/fix-vm/src/instructions/arithmetic.rs +++ b/fix-vm/src/instructions/arithmetic.rs @@ -5,267 +5,285 @@ use gc_arena::{Gc, Mutation, RefLock}; use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx}; -impl<'gc> crate::Vm<'gc> { - #[inline(always)] - pub(crate) fn op_add( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - // if the LHS is a path, the result is a path obtained by - // canonicalizing the concatenated string. RHS may be a path or a - // string. (A `string + path` keeps the string-typed result, handled - // by the next branch.) - if lhs.is::() { - let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs)) - else { - return self.finish_err(fix_error::Error::eval_error(format!( - "cannot append {} to a path", - rhs.ty() - ))); - }; - let combined = format!("{ls}{rs}"); - let canon = canon_path_str(&combined); - let sid = ctx.intern_string(canon); - self.push(Value::new_inline(fix_runtime::Path(sid))); - return Step::Continue(()); - } - if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) { - let merged = ctx - .get_string_context(lhs) - .merge(ctx.get_string_context(rhs)); - let ns = Gc::new( - mc, - crate::NixString::with_context(format!("{ls}{rs}"), merged), - ); - self.push(Value::new_gc(ns)); - return Step::Continue(()); - } - let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b); - match res { - Ok(val) => { - self.push(val); - Step::Continue(()) - } - Err(e) => self.finish_vm_err(e), - } +#[inline(always)] +pub(crate) fn op_add<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + // if the LHS is a path, the result is a path obtained by + // canonicalizing the concatenated string. RHS may be a path or a + // string. (A `string + path` keeps the string-typed result, handled + // by the next branch.) + if lhs.is::() { + let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs)) + else { + return m.finish_err(fix_error::Error::eval_error(format!( + "cannot append {} to a path", + rhs.ty() + ))); + }; + let combined = format!("{ls}{rs}"); + let canon = canon_path_str(&combined); + let sid = ctx.intern_string(canon); + m.push(Value::new_inline(fix_runtime::Path(sid))); + return Step::Continue(()); } - - #[inline(always)] - pub(crate) fn op_sub(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { - self.op_arith(reader, mc, i64::wrapping_sub, |a, b| a - b) - } - - #[inline(always)] - pub(crate) fn op_mul(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { - self.op_arith(reader, mc, i64::wrapping_mul, |a, b| a * b) - } - - #[inline(always)] - fn op_arith( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - int_op: fn(i64, i64) -> i64, - float_op: fn(f64, f64) -> f64, - ) -> Step { - let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - let res = numeric_binop(lhs, rhs, mc, int_op, float_op); - match res { - Ok(val) => { - self.push(val); - Step::Continue(()) - } - Err(e) => self.finish_vm_err(e), - } - } - - #[inline(always)] - pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { - let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - match (get_num(lhs), get_num(rhs)) { - (_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => { - return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error( - "division by zero", - ))); - } - _ => {} - } - let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b); - match res { - Ok(val) => { - self.push(val); - Step::Continue(()) - } - Err(e) => self.finish_vm_err(e), - } - } - - #[inline(always)] - pub(crate) fn op_eq( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - crate::primops::start_eq(self, ctx, reader, mc, lhs, rhs, false) - } - - #[inline(always)] - pub(crate) fn op_neq( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - crate::primops::start_eq(self, ctx, reader, mc, lhs, rhs, true) - } - - #[inline(always)] - pub(crate) fn op_lt( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - self.compare_values(ctx, reader, mc, Ordering::is_lt) - } - - #[inline(always)] - pub(crate) fn op_gt( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - self.compare_values(ctx, reader, mc, Ordering::is_gt) - } - - #[inline(always)] - pub(crate) fn op_leq( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - self.compare_values(ctx, reader, mc, Ordering::is_le) - } - - #[inline(always)] - pub(crate) fn op_geq( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - self.compare_values(ctx, reader, mc, Ordering::is_ge) - } - - fn compare_values( - &mut self, - ctx: &impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - pred: fn(Ordering) -> bool, - ) -> Step { - let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - match self.compare_values_inner(ctx, pred, lhs, rhs) { - Ok(()) => Step::Continue(()), - Err(e) => self.finish_vm_err(e), - } - } - - #[inline(always)] - pub(crate) fn op_concat( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let (l, r) = self.force_and_retry::<(Gc, Gc)>(reader, mc)?; - let mut items = smallvec::SmallVec::new(); - items.extend_from_slice(&l.inner.borrow()); - items.extend_from_slice(&r.inner.borrow()); - self.push(Value::new_gc(Gc::new( + if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) { + let merged = ctx + .get_string_context(lhs) + .merge(ctx.get_string_context(rhs)); + let ns = Gc::new( mc, - crate::List { - inner: RefLock::new(items), - }, - ))); - Step::Continue(()) + crate::NixString::with_context(format!("{ls}{rs}"), merged), + ); + m.push(Value::new_gc(ns)); + return Step::Continue(()); } - - #[inline(always)] - pub(crate) fn op_update( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let (l, r) = self.force_and_retry::<(Gc, Gc)>(reader, mc)?; - self.push(Value::new_gc(l.merge(&r, mc))); - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_neg(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { - let rhs = self.force_and_retry::(reader, mc)?; - match rhs { - NixNum::Int(int) => self.push(Value::make_int(-int, mc)), - NixNum::Float(float) => self.push(Value::new_float(-float)), + let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b); + match res { + Ok(val) => { + m.push(val); + Step::Continue(()) } - Step::Continue(()) + Err(e) => m.finish_vm_err(e), } +} - #[inline(always)] - pub(crate) fn op_not(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { - let rhs = self.force_and_retry::(reader, mc)?; - self.push(Value::new_inline(!rhs)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_sub<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + op_arith(m, reader, mc, i64::wrapping_sub, |a, b| a - b) +} - fn compare_values_inner( - &mut self, - ctx: &impl VmRuntimeCtx, - pred: fn(Ordering) -> bool, - lhs: StrictValue<'gc>, - rhs: StrictValue<'gc>, - ) -> crate::VmResult<()> { - if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { - let ord = match (a, b) { - (NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), - (NixNum::Float(a), NixNum::Float(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Less), - (NixNum::Int(a), NixNum::Float(b)) => { - (a as f64).partial_cmp(&b).unwrap_or(Ordering::Less) - } - (NixNum::Float(a), NixNum::Int(b)) => { - a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less) - } - }; - self.push(Value::new_inline(pred(ord))); - return Ok(()); +#[inline(always)] +pub(crate) fn op_mul<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + op_arith(m, reader, mc, i64::wrapping_mul, |a, b| a * b) +} + +#[inline(always)] +fn op_arith<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + int_op: fn(i64, i64) -> i64, + float_op: fn(f64, f64) -> f64, +) -> Step { + let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + let res = numeric_binop(lhs, rhs, mc, int_op, float_op); + match res { + Ok(val) => { + m.push(val); + Step::Continue(()) } - if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { - self.push(Value::new_inline(pred(a.cmp(b)))); - return Ok(()); - } - if let (Some(a), Some(b)) = (lhs.as_inline::(), rhs.as_inline::()) { - let a = ctx.resolve_string(a.0); - let b = ctx.resolve_string(b.0); - self.push(Value::new_inline(pred(a.cmp(b)))); - return Ok(()); - } - // TODO: compare other types - Err(crate::vm_err(format!( - "cannot compare {} with {}", - lhs.ty(), - rhs.ty() - ))) + Err(e) => m.finish_vm_err(e), } } +#[inline(always)] +pub(crate) fn op_div<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + match (get_num(lhs), get_num(rhs)) { + (_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => { + return m.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error( + "division by zero", + ))); + } + _ => {} + } + let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b); + match res { + Ok(val) => { + m.push(val); + Step::Continue(()) + } + Err(e) => m.finish_vm_err(e), + } +} + +#[inline(always)] +pub(crate) fn op_eq<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + crate::primops::start_eq(m, ctx, reader, mc, lhs, rhs, false) +} + +#[inline(always)] +pub(crate) fn op_neq<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + crate::primops::start_eq(m, ctx, reader, mc, lhs, rhs, true) +} + +#[inline(always)] +pub(crate) fn op_lt<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + compare_values(m, ctx, reader, mc, Ordering::is_lt) +} + +#[inline(always)] +pub(crate) fn op_gt<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + compare_values(m, ctx, reader, mc, Ordering::is_gt) +} + +#[inline(always)] +pub(crate) fn op_leq<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + compare_values(m, ctx, reader, mc, Ordering::is_le) +} + +#[inline(always)] +pub(crate) fn op_geq<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + compare_values(m, ctx, reader, mc, Ordering::is_ge) +} + +fn compare_values<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + pred: fn(Ordering) -> bool, +) -> Step { + let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + match compare_values_inner(m, ctx, pred, lhs, rhs) { + Ok(()) => Step::Continue(()), + Err(e) => m.finish_vm_err(e), + } +} + +#[inline(always)] +pub(crate) fn op_concat<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let (l, r) = m.force_and_retry::<(Gc, Gc)>(reader, mc)?; + let mut items = smallvec::SmallVec::new(); + items.extend_from_slice(&l.inner.borrow()); + items.extend_from_slice(&r.inner.borrow()); + m.push(Value::new_gc(Gc::new( + mc, + crate::List { + inner: RefLock::new(items), + }, + ))); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_update<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let (l, r) = m.force_and_retry::<(Gc, Gc)>(reader, mc)?; + m.push(Value::new_gc(l.merge(&r, mc))); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_neg<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let rhs = m.force_and_retry::(reader, mc)?; + match rhs { + NixNum::Int(int) => m.push(Value::make_int(-int, mc)), + NixNum::Float(float) => m.push(Value::new_float(-float)), + } + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_not<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let rhs = m.force_and_retry::(reader, mc)?; + m.push(Value::new_inline(!rhs)); + Step::Continue(()) +} + +fn compare_values_inner<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &impl VmRuntimeCtx, + pred: fn(Ordering) -> bool, + lhs: StrictValue<'gc>, + rhs: StrictValue<'gc>, +) -> crate::VmResult<()> { + if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { + let ord = match (a, b) { + (NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), + (NixNum::Float(a), NixNum::Float(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Less), + (NixNum::Int(a), NixNum::Float(b)) => { + (a as f64).partial_cmp(&b).unwrap_or(Ordering::Less) + } + (NixNum::Float(a), NixNum::Int(b)) => { + a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less) + } + }; + m.push(Value::new_inline(pred(ord))); + return Ok(()); + } + if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { + m.push(Value::new_inline(pred(a.cmp(b)))); + return Ok(()); + } + if let (Some(a), Some(b)) = (lhs.as_inline::(), rhs.as_inline::()) { + let a = ctx.resolve_string(a.0); + let b = ctx.resolve_string(b.0); + m.push(Value::new_inline(pred(a.cmp(b)))); + return Ok(()); + } + // TODO: compare other types + Err(crate::vm_err(format!( + "cannot compare {} with {}", + lhs.ty(), + rhs.ty() + ))) +} + pub(crate) fn get_num(val: StrictValue<'_>) -> Option { if let Some(i) = val.as_inline::() { Some(NixNum::Int(i64::from(i))) diff --git a/fix-vm/src/instructions/calls.rs b/fix-vm/src/instructions/calls.rs index 64708ca..3ee79cd 100644 --- a/fix-vm/src/instructions/calls.rs +++ b/fix-vm/src/instructions/calls.rs @@ -8,176 +8,174 @@ use crate::{ 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")); +#[inline(always)] +pub(crate) fn call<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + arg: Value<'gc>, + resume_pc: usize, +) -> Step { + let func = m.force_and_retry::(reader, mc)?; + if m.call_depth() > 10000 { + return m.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded")); + } + m.inc_call_depth(); + if let Some(closure) = func.as_gc::() { + if closure.pattern.is_some() { + // FIXME: better DX... + m.push(func.relax()); + m.push(arg); + m.push_call_frame(CallFrame { + pc: resume_pc, + thunk: None, + env: m.env(), + }); + reader.set_pc(PrimOpPhase::CallPattern.ip() as usize); + return Step::Continue(()); } - 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); + + 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))); + m.push_call_frame(CallFrame { + pc: resume_pc, + thunk: None, + env: m.env(), + }); + reader.set_pc(ip as usize); + m.set_env(new_env); + } else if let Some(primop) = func.as_inline::() { + if primop.arity == 1 { + m.push(arg); + m.push_call_frame(CallFrame { + pc: resume_pc, + thunk: None, + env: m.env(), + }); + reader.set_pc(primop.dispatch_ip as usize) + } else { + let app = PrimOpApp { + primop, + arity: primop.arity - 1, + args: [arg, Value::default(), Value::default()], + }; + m.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 { + m.push(app.args[i as usize]); + } + m.push(arg); + m.push_call_frame(CallFrame { + pc: resume_pc, + thunk: None, + env: m.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; + m.push(Value::new_gc(Gc::new(mc, new_app))) + } + } else if let Some(attrs) = func.as_gc::() + && let Some(functor) = attrs.lookup(m.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. + m.dec_call_depth(); + m.push_call_frame(CallFrame { + pc: resume_pc, + thunk: None, + env: m.env(), + }); + m.push(arg); + m.push(func.relax()); + m.push(functor); + reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize); + return Step::Continue(()); + } else { + return m.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<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let arg = resolve_operand(&reader.read_operand_data(), mc, ctx, m); + let pc = reader.pc(); + m.call(reader, mc, arg, pc) +} + +#[inline(always)] +pub(crate) fn op_return<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let val = m.force_and_retry::(reader, mc)?; + let Some(CallFrame { + pc: ret_pc, + thunk, + env, + }) = m.pop_call_frame() + else { + match m.force_mode() { + ForceMode::AsIs => return m.finish_ok(ctx.convert_value(val.relax())), + ForceMode::Shallow => { + m.push(val.relax()); + reader.set_pc(PrimOpPhase::ForceResultShallow.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, + ForceMode::Deep => { + m.push(val.relax()); + m.push(val.relax()); + m.push_call_frame(CallFrame { + pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize, thunk: None, - env: self.env, + env: m.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))); + m.inc_call_depth(); + reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize); + return Step::Continue(()); } - } 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) + }; + reader.set_pc(ret_pc); + if let Some(outer_thunk) = thunk { + *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); + } else { + m.dec_call_depth(); + m.push(val.relax()) } + m.set_env(env); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_dispatch_primop<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + crate::primops::dispatch_primop(m, ctx, reader, mc) } diff --git a/fix-vm/src/instructions/closures.rs b/fix-vm/src/instructions/closures.rs index 173f97b..3e1b01f 100644 --- a/fix-vm/src/instructions/closures.rs +++ b/fix-vm/src/instructions/closures.rs @@ -1,94 +1,93 @@ +use fix_runtime::Machine; use gc_arena::{Gc, Mutation, RefLock}; use crate::{BytecodeReader, Step, ThunkState, Value}; -impl<'gc> crate::Vm<'gc> { - #[inline(always)] - pub(crate) fn op_make_thunk( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let entry_point = reader.read_u32(); - let thunk = Gc::new( - mc, - RefLock::new(ThunkState::Pending { - ip: entry_point as usize, - env: self.env, - }), - ); - self.push(Value::new_gc(thunk)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_make_thunk<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let entry_point = reader.read_u32(); + let thunk = Gc::new( + mc, + RefLock::new(ThunkState::Pending { + ip: entry_point as usize, + env: m.env(), + }), + ); + m.push(Value::new_gc(thunk)); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_make_closure( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let entry_point = reader.read_u32(); - let n_locals = reader.read_u32(); - let closure = Gc::new( - mc, - crate::Closure { - ip: entry_point, - n_locals, - env: self.env, - pattern: None, - }, - ); - self.push(Value::new_gc(closure)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_make_closure<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let entry_point = reader.read_u32(); + let n_locals = reader.read_u32(); + let closure = Gc::new( + mc, + crate::Closure { + ip: entry_point, + n_locals, + env: m.env(), + pattern: None, + }, + ); + m.push(Value::new_gc(closure)); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_make_pattern_closure( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let entry_point = reader.read_u32(); - let n_locals = reader.read_u32(); - let req_count = reader.read_u16() as usize; - let opt_count = reader.read_u16() as usize; - let has_ellipsis = reader.read_u8() != 0; +#[inline(always)] +pub(crate) fn op_make_pattern_closure<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let entry_point = reader.read_u32(); + let n_locals = reader.read_u32(); + let req_count = reader.read_u16() as usize; + let opt_count = reader.read_u16() as usize; + let has_ellipsis = reader.read_u8() != 0; - let mut required = smallvec::SmallVec::new(); - for _ in 0..req_count { - required.push(reader.read_string_id()); - } - let mut optional = smallvec::SmallVec::new(); - for _ in 0..opt_count { - optional.push(reader.read_string_id()); - } - let total = req_count + opt_count; - let mut param_spans = Vec::with_capacity(total); - for _ in 0..total { - let name = reader.read_string_id(); - let span_id = reader.read_u32(); - param_spans.push((name, span_id)); - } + let mut required = smallvec::SmallVec::new(); + for _ in 0..req_count { + required.push(reader.read_string_id()); + } + let mut optional = smallvec::SmallVec::new(); + for _ in 0..opt_count { + optional.push(reader.read_string_id()); + } + let total = req_count + opt_count; + let mut param_spans = Vec::with_capacity(total); + for _ in 0..total { + let name = reader.read_string_id(); + let span_id = reader.read_u32(); + param_spans.push((name, span_id)); + } - let pattern = Gc::new( - mc, - crate::PatternInfo { - required, - optional, - ellipsis: has_ellipsis, - param_spans: param_spans.into_boxed_slice(), - }, - ); - let closure = Gc::new( - mc, - crate::Closure { - ip: entry_point, - n_locals, - env: self.env, - pattern: Some(pattern), - }, - ); - self.push(Value::new_gc(closure)); - Step::Continue(()) - } + let pattern = Gc::new( + mc, + crate::PatternInfo { + required, + optional, + ellipsis: has_ellipsis, + param_spans: param_spans.into_boxed_slice(), + }, + ); + let closure = Gc::new( + mc, + crate::Closure { + ip: entry_point, + n_locals, + env: m.env(), + pattern: Some(pattern), + }, + ); + m.push(Value::new_gc(closure)); + Step::Continue(()) } diff --git a/fix-vm/src/instructions/collections.rs b/fix-vm/src/instructions/collections.rs index 75763e1..e4d24e3 100644 --- a/fix-vm/src/instructions/collections.rs +++ b/fix-vm/src/instructions/collections.rs @@ -1,6 +1,6 @@ use fix_error::Error; use fix_lang::StringId; -use fix_runtime::{NixType, resolve_operand}; +use fix_runtime::{Machine, MachineExt, NixType, resolve_operand}; use gc_arena::{Gc, RefLock}; use smallvec::SmallVec; @@ -8,327 +8,330 @@ use crate::{ AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, }; -impl<'gc> crate::Vm<'gc> { - #[inline(always)] - pub(crate) fn op_make_attrs( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let static_count = reader.read_u32() as usize; - let dynamic_count = reader.read_u32() as usize; +#[inline(always)] +pub(crate) fn op_make_attrs<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let static_count = reader.read_u32() as usize; + let dynamic_count = reader.read_u32() as usize; - for i in 0..dynamic_count { - let depth = dynamic_count - 1 - i; - self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?; - } - - let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count); - for i in 0..dynamic_count { - let depth = dynamic_count - 1 - i; - let key_val = self.peek_forced(depth); - let key_sid = match ctx.get_string_id(key_val) { - Ok(id) => Some(id), - Err(NixType::Null) => None, - Err(got) => return self.finish_type_err(NixType::String, got), - }; - dyn_keys.push(key_sid); - } - - self.stack.truncate(self.stack.len() - dynamic_count); - - let mut kv: SmallVec<[(crate::StringId, Value); 4]> = - SmallVec::with_capacity(static_count + dynamic_count); - - for _ in 0..static_count { - let key = reader.read_string_id(); - let val = resolve_operand(&reader.read_operand_data(), mc, ctx, self); - let _span_id = reader.read_u32(); - kv.push((key, val)); - } - - for key in dyn_keys { - let val = resolve_operand(&reader.read_operand_data(), mc, ctx, self); - let _span_id = reader.read_u32(); - if let Some(key) = key { - kv.push((key, val)) - } - } - - kv.sort_by_key(|(k, _)| *k); - let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv)); - self.push(Value::new_gc(attrs)); - Step::Continue(()) + for i in 0..dynamic_count { + let depth = dynamic_count - 1 - i; + m.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?; } - #[inline(always)] - pub(crate) fn op_make_empty_attrs(&mut self) -> Step { - self.push(self.empty_attrs); - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_select_static( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let _span_id = reader.read_u32(); - let key = reader.read_string_id(); - - let attrset = self.force_and_retry::>(reader, mc)?; - - match attrset.lookup(key) { - Some(v) => { - self.push(v); - } - None => return self.select_skip(key, ctx, reader), - } - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_select_dynamic( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let _span_id = reader.read_u32(); - - let (attrset, key_val) = self.force_and_retry::<(Gc, StrictValue)>(reader, mc)?; - + let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count); + for i in 0..dynamic_count { + let depth = dynamic_count - 1 - i; + let key_val = m.peek_forced(depth); let key_sid = match ctx.get_string_id(key_val) { - Ok(id) => id, - Err(got) => return self.finish_type_err(NixType::String, got), + Ok(id) => Some(id), + Err(NixType::Null) => None, + Err(got) => return m.finish_type_err(NixType::String, got), }; - - match attrset.lookup(key_sid) { - Some(v) => { - self.push(v); - } - None => return self.select_skip(key_sid, ctx, reader), - } - Step::Continue(()) + dyn_keys.push(key_sid); } - /// Skip the rest of a **Select** attrpath after a missing attribute. - /// Only recognises Select opcodes and jumps; encountering any other - /// opcode means we've reached the end of the select sequence and - /// should report the missing-attribute error. - fn select_skip( - &mut self, - key: StringId, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - ) -> Step { - use fix_bytecode::Op::*; - loop { - match reader.read_op() { - SelectStatic => { - reader.set_pc(reader.pc() + 4 + 4); - } - SelectDynamic => { - reader.set_pc(reader.pc() + 4); - } - JumpIfSelectSucceeded => { - reader.set_pc(reader.pc() + 4); - break Step::Continue(()); - } - JumpIfSelectFailed => { - let offset = reader.read_i32(); - reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); - } - _ => { - let name = ctx.resolve_string(key); - return self - .finish_err(Error::eval_error(format!("attribute '{name}' missing"))); - } - } - } - } + m.drop_n(dynamic_count); - /// Skip the rest of a **HasAttr** attrpath after an intermediate - /// lookup failed. Only recognises HasAttr opcodes and jumps. - fn has_attr_skip(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - use fix_bytecode::Op::*; - loop { - match reader.read_op() { - HasAttrPathStatic => { - reader.set_pc(reader.pc() + 4 + 4); - } - HasAttrPathDynamic => { - reader.set_pc(reader.pc() + 4); - } - HasAttrStatic => { - reader.set_pc(reader.pc() + 4); - break Step::Continue(()); - } - JumpIfSelectFailed => { - let offset = reader.read_i32(); - reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); - } - HasAttrDynamic => { - break Step::Continue(()); - } - HasAttrResolve => { - reader.set_pc(reader.pc() - 1); - break Step::Continue(()); - } - other => { - unreachable!("unexpected opcode {:?} in has_attr_skip", other as u8) - } - } - } - } + let mut kv: SmallVec<[(crate::StringId, Value); 4]> = + SmallVec::with_capacity(static_count + dynamic_count); - #[inline(always)] - pub(crate) fn op_has_attr_path_static( - &mut self, - _ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let _span_id = reader.read_u32(); + for _ in 0..static_count { let key = reader.read_string_id(); - - let current = self.force_and_retry::(reader, mc)?; - - match current - .as_gc::() - .and_then(|attrs| attrs.lookup(key)) - { - Some(v) => { - self.push(v); - } - None => return self.has_attr_skip(reader), - } - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_has_attr_path_dynamic( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { + let val = resolve_operand(&reader.read_operand_data(), mc, ctx, m); let _span_id = reader.read_u32(); + kv.push((key, val)); + } - let (current, key_val) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + for key in dyn_keys { + let val = resolve_operand(&reader.read_operand_data(), mc, ctx, m); + let _span_id = reader.read_u32(); + if let Some(key) = key { + kv.push((key, val)) + } + } - let key_sid = match ctx.get_string_id(key_val) { - Ok(id) => id, - Err(got) => return self.finish_type_err(NixType::String, got), - }; + kv.sort_by_key(|(k, _)| *k); + let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv)); + m.push(Value::new_gc(attrs)); + Step::Continue(()) +} - match current - .as_gc::() - .and_then(|attrs| attrs.lookup(key_sid)) - { - Some(v) => { - self.push(v); +#[inline(always)] +pub(crate) fn op_make_empty_attrs<'gc, M: Machine<'gc>>(m: &mut M) -> Step { + m.push(m.empty_attrs()); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_select_static<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let _span_id = reader.read_u32(); + let key = reader.read_string_id(); + + let attrset = m.force_and_retry::>(reader, mc)?; + + match attrset.lookup(key) { + Some(v) => { + m.push(v); + } + None => return select_skip(m, key, ctx, reader), + } + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_select_dynamic<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let _span_id = reader.read_u32(); + + let (attrset, key_val) = m.force_and_retry::<(Gc, StrictValue)>(reader, mc)?; + + let key_sid = match ctx.get_string_id(key_val) { + Ok(id) => id, + Err(got) => return m.finish_type_err(NixType::String, got), + }; + + match attrset.lookup(key_sid) { + Some(v) => { + m.push(v); + } + None => return select_skip(m, key_sid, ctx, reader), + } + Step::Continue(()) +} + +/// Skip the rest of a **Select** attrpath after a missing attribute. +/// Only recognises Select opcodes and jumps; encountering any other +/// opcode means we've reached the end of the select sequence and +/// should report the missing-attribute error. +fn select_skip<'gc, M: Machine<'gc>>( + m: &mut M, + key: StringId, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, +) -> Step { + use fix_bytecode::Op::*; + loop { + match reader.read_op() { + SelectStatic => { + reader.set_pc(reader.pc() + 4 + 4); + } + SelectDynamic => { + reader.set_pc(reader.pc() + 4); + } + JumpIfSelectSucceeded => { + reader.set_pc(reader.pc() + 4); + break Step::Continue(()); + } + JumpIfSelectFailed => { + let offset = reader.read_i32(); + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + } + _ => { + let name = ctx.resolve_string(key); + return m.finish_err(Error::eval_error(format!("attribute '{name}' missing"))); } - None => return self.has_attr_skip(reader), } - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_jump_if_select_failed(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - // No-op - let _offset = reader.read_i32(); - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - let offset = reader.read_i32(); - reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_has_attr_static( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let key = reader.read_string_id(); - let current = self.force_and_retry::(reader, mc)?; - - self.push(Value::new_inline( - current - .as_gc::() - .and_then(|attrs| attrs.lookup(key)) - .is_some(), - )); - // Skip HasAttrResolve - reader.set_pc(reader.pc() + 1); - - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_has_attr_dynamic( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let (current, dyn_key) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; - - let key_sid = match ctx.get_string_id(dyn_key) { - Ok(id) => id, - Err(got) => return self.finish_type_err(NixType::String, got), - }; - - self.push(Value::new_inline( - current - .as_gc::() - .and_then(|attrs| attrs.lookup(key_sid)) - .is_some(), - )); - // Skip HasAttrResolve - reader.set_pc(reader.pc() + 1); - - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_has_attr_resolve(&mut self) -> Step { - // If we reach here, has_attr check has failed, push false (AttrSet is already popped) - self.push(Value::new_inline(false)); - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_make_list( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let count = reader.read_u32() as usize; - let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); - for _ in 0..count { - items.push(resolve_operand(&reader.read_operand_data(), mc, ctx, self)); - } - let list = Gc::new( - mc, - List { - inner: RefLock::new(items), - }, - ); - self.push(Value::new_gc(list)); - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_make_empty_list(&mut self) -> Step { - self.push(self.empty_list); - Step::Continue(()) } } + +/// Skip the rest of a **HasAttr** attrpath after an intermediate +/// lookup failed. Only recognises HasAttr opcodes and jumps. +fn has_attr_skip(reader: &mut BytecodeReader<'_>) -> Step { + use fix_bytecode::Op::*; + loop { + match reader.read_op() { + HasAttrPathStatic => { + reader.set_pc(reader.pc() + 4 + 4); + } + HasAttrPathDynamic => { + reader.set_pc(reader.pc() + 4); + } + HasAttrStatic => { + reader.set_pc(reader.pc() + 4); + break Step::Continue(()); + } + JumpIfSelectFailed => { + let offset = reader.read_i32(); + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + } + HasAttrDynamic => { + break Step::Continue(()); + } + HasAttrResolve => { + reader.set_pc(reader.pc() - 1); + break Step::Continue(()); + } + other => { + unreachable!("unexpected opcode {:?} in has_attr_skip", other as u8) + } + } + } +} + +#[inline(always)] +pub(crate) fn op_has_attr_path_static<'gc, M: Machine<'gc>>( + m: &mut M, + _ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let _span_id = reader.read_u32(); + let key = reader.read_string_id(); + + let current = m.force_and_retry::(reader, mc)?; + + match current + .as_gc::() + .and_then(|attrs| attrs.lookup(key)) + { + Some(v) => { + m.push(v); + } + None => return has_attr_skip(reader), + } + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_has_attr_path_dynamic<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let _span_id = reader.read_u32(); + + let (current, key_val) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + + let key_sid = match ctx.get_string_id(key_val) { + Ok(id) => id, + Err(got) => return m.finish_type_err(NixType::String, got), + }; + + match current + .as_gc::() + .and_then(|attrs| attrs.lookup(key_sid)) + { + Some(v) => { + m.push(v); + } + None => return has_attr_skip(reader), + } + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_jump_if_select_failed<'gc, M: Machine<'gc>>( + _m: &mut M, + reader: &mut BytecodeReader<'_>, +) -> Step { + // No-op + let _offset = reader.read_i32(); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_jump_if_select_succeeded<'gc, M: Machine<'gc>>( + _m: &mut M, + reader: &mut BytecodeReader<'_>, +) -> Step { + let offset = reader.read_i32(); + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_has_attr_static<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let key = reader.read_string_id(); + let current = m.force_and_retry::(reader, mc)?; + + m.push(Value::new_inline( + current + .as_gc::() + .and_then(|attrs| attrs.lookup(key)) + .is_some(), + )); + // Skip HasAttrResolve + reader.set_pc(reader.pc() + 1); + + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_has_attr_dynamic<'gc, M: MachineExt<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let (current, dyn_key) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; + + let key_sid = match ctx.get_string_id(dyn_key) { + Ok(id) => id, + Err(got) => return m.finish_type_err(NixType::String, got), + }; + + m.push(Value::new_inline( + current + .as_gc::() + .and_then(|attrs| attrs.lookup(key_sid)) + .is_some(), + )); + // Skip HasAttrResolve + reader.set_pc(reader.pc() + 1); + + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_has_attr_resolve<'gc, M: Machine<'gc>>(m: &mut M) -> Step { + // If we reach here, has_attr check has failed, push false (AttrSet is already popped) + m.push(Value::new_inline(false)); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_make_list<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let count = reader.read_u32() as usize; + let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); + for _ in 0..count { + items.push(resolve_operand(&reader.read_operand_data(), mc, ctx, m)); + } + let list = Gc::new( + mc, + List { + inner: RefLock::new(items), + }, + ); + m.push(Value::new_gc(list)); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_make_empty_list<'gc, M: Machine<'gc>>(m: &mut M) -> Step { + m.push(m.empty_list()); + Step::Continue(()) +} diff --git a/fix-vm/src/instructions/control.rs b/fix-vm/src/instructions/control.rs index 4d30b2d..67fca78 100644 --- a/fix-vm/src/instructions/control.rs +++ b/fix-vm/src/instructions/control.rs @@ -4,57 +4,55 @@ use gc_arena::Mutation; use crate::{BytecodeReader, Step, VmRuntimeCtx}; -impl<'gc> crate::Vm<'gc> { - #[inline(always)] - pub(crate) fn op_jump_if_false( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let offset = reader.read_i32(); - let cond = self.force_and_retry::(reader, mc)?; - if cond.as_inline::() == Some(false) { - reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); - } - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_jump_if_true( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let offset = reader.read_i32(); - let cond = self.force_and_retry::(reader, mc)?; - if cond.as_inline::() == Some(true) { - reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); - } - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_jump(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - let offset = reader.read_i32(); +#[inline(always)] +pub(crate) fn op_jump_if_false<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let offset = reader.read_i32(); + let cond = m.force_and_retry::(reader, mc)?; + if cond.as_inline::() == Some(false) { reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_assert( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let raw_id = reader.read_string_id(); - let raw = ctx.resolve_string(raw_id); - let _span_id = reader.read_u32(); - let assertion = self.force_and_retry::(reader, mc)?; - if !assertion { - // FIXME: use catchable error - return self.finish_err(Error::eval_error(format!("assertion '{raw}' failed"))); - } - Step::Continue(()) } + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_jump_if_true<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let offset = reader.read_i32(); + let cond = m.force_and_retry::(reader, mc)?; + if cond.as_inline::() == Some(true) { + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + } + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_jump<'gc, M: Machine<'gc>>(_m: &mut M, reader: &mut BytecodeReader<'_>) -> Step { + let offset = reader.read_i32(); + reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_assert<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let raw_id = reader.read_string_id(); + let raw = ctx.resolve_string(raw_id); + let _span_id = reader.read_u32(); + let assertion = m.force_and_retry::(reader, mc)?; + if !assertion { + // FIXME: use catchable error + return m.finish_err(Error::eval_error(format!("assertion '{raw}' failed"))); + } + Step::Continue(()) } diff --git a/fix-vm/src/instructions/literals.rs b/fix-vm/src/instructions/literals.rs index 248b4bd..802ccd4 100644 --- a/fix-vm/src/instructions/literals.rs +++ b/fix-vm/src/instructions/literals.rs @@ -1,55 +1,63 @@ +use fix_runtime::Machine; use gc_arena::{Gc, Mutation}; use crate::{BytecodeReader, Step, Value}; -impl<'gc> crate::Vm<'gc> { - #[inline(always)] - pub(crate) fn op_push_smi(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - let val = reader.read_i32(); - self.push(Value::new_inline(val)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_push_smi<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, +) -> Step { + let val = reader.read_i32(); + m.push(Value::new_inline(val)); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_push_bigint( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - let val = reader.read_i64(); - self.push(Value::new_gc(Gc::new(mc, val))); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_push_bigint<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, +) -> Step { + let val = reader.read_i64(); + m.push(Value::new_gc(Gc::new(mc, val))); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_push_float(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - let val = reader.read_f64(); - self.push(Value::new_float(val)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_push_float<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, +) -> Step { + let val = reader.read_f64(); + m.push(Value::new_float(val)); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_push_string(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - let sid = reader.read_string_id(); - self.push(Value::new_inline(sid)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_push_string<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, +) -> Step { + let sid = reader.read_string_id(); + m.push(Value::new_inline(sid)); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_push_null(&mut self) -> Step { - self.push(Value::new_inline(crate::Null)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_push_null<'gc, M: Machine<'gc>>(m: &mut M) -> Step { + m.push(Value::new_inline(crate::Null)); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_push_true(&mut self) -> Step { - self.push(Value::new_inline(true)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_push_true<'gc, M: Machine<'gc>>(m: &mut M) -> Step { + m.push(Value::new_inline(true)); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_push_false(&mut self) -> Step { - self.push(Value::new_inline(false)); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_push_false<'gc, M: Machine<'gc>>(m: &mut M) -> Step { + m.push(Value::new_inline(false)); + Step::Continue(()) } diff --git a/fix-vm/src/instructions/misc.rs b/fix-vm/src/instructions/misc.rs index 51db134..c0e3c5d 100644 --- a/fix-vm/src/instructions/misc.rs +++ b/fix-vm/src/instructions/misc.rs @@ -3,165 +3,164 @@ use std::path::PathBuf; use fix_bytecode::PrimOpPhase; use fix_error::Error; use fix_lang::{BUILTINS, BuiltinId, StringId}; -use fix_runtime::{AttrSet, NixString, Path, StrictValue, StringContext, canon_path_str}; +use fix_runtime::{ + AttrSet, Machine, MachineExt, NixString, Path, StrictValue, StringContext, canon_path_str, +}; use num_enum::TryFromPrimitive; use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt}; -impl<'gc> crate::Vm<'gc> { - #[inline(always)] - pub(crate) fn op_load_builtins(&mut self) -> Step { - self.push(self.builtins); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_load_builtins<'gc, M: Machine<'gc>>(m: &mut M) -> Step { + m.push(m.builtins()); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8()) - .map_err(|err| panic!("unknown builtin id: {}", err.number)); - self.push(Value::new_inline(PrimOp { - id, - arity: BUILTINS[id as usize].1, - dispatch_ip: PrimOpPhase::entry_for_builtin(id).ip(), - })); - Step::Continue(()) - } +#[inline(always)] +pub(crate) fn op_load_builtin<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, +) -> Step { + let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8()) + .map_err(|err| panic!("unknown builtin id: {}", err.number)); + m.push(Value::new_inline(PrimOp { + id, + arity: BUILTINS[id as usize].1, + dispatch_ip: PrimOpPhase::entry_for_builtin(id).ip(), + })); + Step::Continue(()) +} - #[inline(always)] - pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step { - let _name = reader.read_string_id(); - todo!("LoadReplBinding"); - } +#[inline(always)] +pub(crate) fn op_load_repl_binding<'gc, M: Machine<'gc>>( + _m: &mut M, + reader: &mut BytecodeReader<'_>, +) -> Step { + let _name = reader.read_string_id(); + todo!("LoadReplBinding"); +} - #[inline(always)] - pub(crate) fn op_load_scoped_binding( - &mut self, - ctx: &impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - _mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let slot_id = reader.read_u32(); - let name = reader.read_string_id(); - let scope = match self.scope_slots.get(slot_id as usize).copied() { - Some(s) => s, - None => { - return self.finish_err(Error::eval_error(format!( - "internal: invalid scope slot {slot_id}" - ))); - } - }; - let Some(attrs) = scope.as_gc::() else { - return self.finish_err(Error::eval_error("internal: scope slot is not an attrset")); - }; - match attrs.lookup(name) { - Some(val) => { - self.push(val); - Step::Continue(()) - } - None => self.finish_err(Error::eval_error(format!( - "scoped binding '{}' not found", - ctx.resolve_string(name) - ))), +#[inline(always)] +pub(crate) fn op_load_scoped_binding<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + _mc: &gc_arena::Mutation<'gc>, +) -> Step { + let slot_id = reader.read_u32(); + let name = reader.read_string_id(); + let scope = m.scope_slot(slot_id); + let Some(attrs) = scope.as_gc::() else { + return m.finish_err(Error::eval_error("internal: scope slot is not an attrset")); + }; + match attrs.lookup(name) { + Some(val) => { + m.push(val); + Step::Continue(()) + } + None => m.finish_err(Error::eval_error(format!( + "scoped binding '{}' not found", + ctx.resolve_string(name) + ))), + } +} + +#[inline(always)] +pub(crate) fn op_coerce_to_string<'gc, M: Machine<'gc>>( + m: &mut M, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let val = m.force_and_retry::(reader, mc)?; + if val.is::() || val.is::() { + m.push(val.relax()); + } else if let Some(p) = val.as_inline::() { + // Coercing a path to a string yields the canonical path text. + // FIXME: copy to store + m.push(Value::new_inline(p.0)); + } else { + todo!("coerce other types to string: {:?}", val.ty()); + } + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_concat_strings<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let count = reader.read_u16() as usize; + let _force_string = reader.read_u8() != 0; + + let mut total_len = 0; + let mut has_any_context = false; + for i in 0..count { + let val = m.peek_forced(count - 1 - i); + let s = ctx.get_string(val).expect("coerced"); + total_len += s.len(); + if !ctx.get_string_context(val).is_empty() { + has_any_context = true; } } - #[inline(always)] - pub(crate) fn op_coerce_to_string( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let val = self.force_and_retry::(reader, mc)?; - if val.is::() || val.is::() { - self.push(val.relax()); - } else if let Some(p) = val.as_inline::() { - // Coercing a path to a string yields the canonical path text. - // FIXME: copy to store - self.push(Value::new_inline(p.0)); - } else { - todo!("coerce other types to string: {:?}", val.ty()); - } - Step::Continue(()) - } - - #[inline(always)] - pub(crate) fn op_concat_strings( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let count = reader.read_u16() as usize; - let _force_string = reader.read_u8() != 0; - - let mut total_len = 0; - let mut has_any_context = false; - for i in 0..count { - let val = self.peek_forced(count - 1 - i); - let s = ctx.get_string(val).expect("coerced"); - total_len += s.len(); - if !ctx.get_string_context(val).is_empty() { - has_any_context = true; + let mut result = String::with_capacity(total_len); + let mut merged = StringContext::new(); + for i in 0..count { + let val = m.peek_forced(count - 1 - i); + let s = ctx.get_string(val).expect("coerced"); + result.push_str(s); + if has_any_context { + let ctx = ctx.get_string_context(val); + if !ctx.is_empty() { + merged = merged.merge(ctx); } } - - let mut result = String::with_capacity(total_len); - let mut merged = StringContext::new(); - for i in 0..count { - let val = self.peek_forced(count - 1 - i); - let s = ctx.get_string(val).expect("coerced"); - result.push_str(s); - if has_any_context { - let ctx = ctx.get_string_context(val); - if !ctx.is_empty() { - merged = merged.merge(ctx); - } - } - } - - self.stack.truncate(self.stack.len() - count); - - if merged.is_empty() { - let sid = ctx.intern_string(result); - self.push(Value::new_inline(sid)); - } else { - let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged)); - self.push(Value::new_gc(ns)); - } - Step::Continue(()) } - #[inline(always)] - pub(crate) fn op_resolve_path( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - let path_val = self.force_and_retry::(reader, mc)?; - let dir_id = reader.read_string_id(); - // Already a path: keep as-is. ResolvePath is idempotent on paths. - if let Some(p) = path_val.as_inline::() { - self.push(Value::new_inline(p)); - return Step::Continue(()); - } - let path = match ctx.get_string(path_val) { - Some(s) => s.to_owned(), - None => { - return self.finish_err(Error::eval_error(format!( - "expected a string for path, got {}", - path_val.ty() - ))); - } - }; - let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) { - Ok(s) => s, - Err(e) => return self.finish_err(e), - }; - let sid = ctx.intern_string(resolved); - self.push(Value::new_inline(Path(sid))); - Step::Continue(()) + m.drop_n(count); + + if merged.is_empty() { + let sid = ctx.intern_string(result); + m.push(Value::new_inline(sid)); + } else { + let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged)); + m.push(Value::new_gc(ns)); } + Step::Continue(()) +} + +#[inline(always)] +pub(crate) fn op_resolve_path<'gc, M: MachineExt<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + let path_val = m.force_and_retry::(reader, mc)?; + let dir_id = reader.read_string_id(); + // Already a path: keep as-is. ResolvePath is idempotent on paths. + if let Some(p) = path_val.as_inline::() { + m.push(Value::new_inline(p)); + return Step::Continue(()); + } + let path = match ctx.get_string(path_val) { + Some(s) => s.to_owned(), + None => { + return m.finish_err(Error::eval_error(format!( + "expected a string for path, got {}", + path_val.ty() + ))); + } + }; + let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) { + Ok(s) => s, + Err(e) => return m.finish_err(e), + }; + let sid = ctx.intern_string(resolved); + m.push(Value::new_inline(Path(sid))); + Step::Continue(()) } fn resolve_path_str(current_dir: &str, path: &str) -> Result> { diff --git a/fix-vm/src/instructions/mod.rs b/fix-vm/src/instructions/mod.rs index d5f222c..894a730 100644 --- a/fix-vm/src/instructions/mod.rs +++ b/fix-vm/src/instructions/mod.rs @@ -7,3 +7,13 @@ pub(crate) mod literals; pub(crate) mod misc; pub(crate) mod variables; pub(crate) mod with_scope; + +pub(crate) use arithmetic::*; +pub(crate) use calls::*; +pub(crate) use closures::*; +pub(crate) use collections::*; +pub(crate) use control::*; +pub(crate) use literals::*; +pub(crate) use misc::*; +pub(crate) use variables::*; +pub(crate) use with_scope::*; diff --git a/fix-vm/src/instructions/variables.rs b/fix-vm/src/instructions/variables.rs index 8698eab..d3f06d0 100644 --- a/fix-vm/src/instructions/variables.rs +++ b/fix-vm/src/instructions/variables.rs @@ -1,50 +1,48 @@ +use fix_runtime::Machine; + use crate::{BytecodeReader, Mutation, Step, Value}; -impl<'gc> crate::Vm<'gc> { #[inline(always)] - pub(crate) fn op_load_local(&mut self, reader: &mut BytecodeReader<'_>) -> Step { + pub(crate) fn op_load_local<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) -> Step { let idx = reader.read_u32() as usize; - self.push(self.env.borrow().locals[idx]); + m.push(m.env().borrow().locals[idx]); Step::Continue(()) } #[inline(always)] - pub(crate) fn op_load_outer(&mut self, reader: &mut BytecodeReader<'_>) -> Step { + pub(crate) fn op_load_outer<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) -> Step { let layer = reader.read_u8(); let idx = reader.read_u32() as usize; - let mut cur = self.env; + let mut cur = m.env(); for _ in 0..layer { let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); cur = prev; } let val = cur.borrow().locals[idx]; - self.push(val); + m.push(val); Step::Continue(()) } #[inline(always)] - pub(crate) fn op_store_local( - &mut self, + pub(crate) fn op_store_local<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { let idx = reader.read_u32() as usize; - let val = self.pop(); - self.env.borrow_mut(mc).locals[idx] = val; + let val = m.pop(); + m.env().borrow_mut(mc).locals[idx] = val; Step::Continue(()) } #[inline(always)] - pub(crate) fn op_alloc_locals( - &mut self, + pub(crate) fn op_alloc_locals<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { let count = reader.read_u32() as usize; - self.env + m.env() .borrow_mut(mc) .locals .extend(std::iter::repeat_n(Value::default(), count)); Step::Continue(()) } -} diff --git a/fix-vm/src/instructions/with_scope.rs b/fix-vm/src/instructions/with_scope.rs index 21cb767..8cba083 100644 --- a/fix-vm/src/instructions/with_scope.rs +++ b/fix-vm/src/instructions/with_scope.rs @@ -5,74 +5,71 @@ use smallvec::SmallVec; use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx}; -impl<'gc> crate::Vm<'gc> { - #[inline(always)] - pub(crate) fn op_lookup_with( - &mut self, - ctx: &mut impl VmRuntimeCtx, - reader: &mut BytecodeReader<'_>, - mc: &gc_arena::Mutation<'gc>, - ) -> Step { - #[allow(clippy::unwrap_used)] - let counter = self.peek_forced(0).as_inline::().unwrap(); +#[inline(always)] +pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>( + m: &mut M, + ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &gc_arena::Mutation<'gc>, +) -> Step { + #[allow(clippy::unwrap_used)] + let counter = m.peek_forced(0).as_inline::().unwrap(); - let name = reader.read_string_id(); - let n = reader.read_u8(); - let mut namespaces = SmallVec::<[_; 2]>::new(); - for _ in 0..n { - namespaces.push(resolve_operand(&reader.read_operand_data(), mc, ctx, self)); - } + let name = reader.read_string_id(); + let n = reader.read_u8(); + let mut namespaces = SmallVec::<[_; 2]>::new(); + for _ in 0..n { + namespaces.push(resolve_operand(&reader.read_operand_data(), mc, ctx, m)); + } - let resume_pc = reader.inst_start_pc(); - let namespace = match namespaces[counter as usize].restrict() { - Ok(val) => val, - Err(thunk) => { - let mut state = thunk.borrow_mut(mc); - match *state { - ThunkState::Pending { ip, env } => { - *state = ThunkState::Blackhole; - self.call_stack.push(CallFrame { - thunk: Some(thunk), - pc: resume_pc, - env: self.env, - }); - self.env = env; - reader.set_pc(ip); - return Step::Break(Break::Force); - } - ThunkState::Evaluated(v) => v, - ThunkState::Apply { func, arg } => { - self.call_stack.push(CallFrame { - thunk: Some(thunk), - pc: resume_pc, - env: self.env, - }); - self.push(func); - return self.call(reader, mc, arg, resume_pc); - } - ThunkState::Blackhole => { - return self - .finish_err(Error::eval_error("infinite recursion encountered")); - } + let resume_pc = reader.inst_start_pc(); + let namespace = match namespaces[counter as usize].restrict() { + Ok(val) => val, + Err(thunk) => { + let mut state = thunk.borrow_mut(mc); + match *state { + ThunkState::Pending { ip, env } => { + *state = ThunkState::Blackhole; + m.push_call_frame(CallFrame { + thunk: Some(thunk), + pc: resume_pc, + env: m.env(), + }); + m.set_env(env); + reader.set_pc(ip); + return Step::Break(Break::Force); + } + ThunkState::Evaluated(v) => v, + ThunkState::Apply { func, arg } => { + m.push_call_frame(CallFrame { + thunk: Some(thunk), + pc: resume_pc, + env: m.env(), + }); + m.push(func); + return m.call(reader, mc, arg, resume_pc); + } + ThunkState::Blackhole => { + return m.finish_err(Error::eval_error("infinite recursion encountered")); } } - }; - - if let Some(val) = namespace - .as_gc::() - .and_then(|attrs| attrs.lookup(name)) - { - self.replace(0, val); - } else if counter + 1 == n as i32 { - return self.finish_err(Error::eval_error(format!( - "undefined variable '{}'", - Symbol::from(ctx.resolve_string(name)) - ))); - } else { - self.replace(0, Value::new_inline(counter + 1)); - reader.set_pc(resume_pc); } + }; - Step::Continue(()) + if let Some(val) = namespace + .as_gc::() + .and_then(|attrs| attrs.lookup(name)) + { + m.replace(0, val); + } else if counter + 1 == n as i32 { + return m.finish_err(Error::eval_error(format!( + "undefined variable '{}'", + Symbol::from(ctx.resolve_string(name)) + ))); + } else { + m.replace(0, Value::new_inline(counter + 1)); + reader.set_pc(resume_pc); } + + Step::Continue(()) } diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index 521d52e..391766b 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -137,45 +137,20 @@ impl<'gc> Vm<'gc> { functor_sym: ctx.intern_string("__functor"), } } +} - #[inline(always)] - fn finish_ok(&mut self, val: fix_lang::Value) -> Step { - self.result = Some(Ok(val)); - Step::Break(Break::Done) - } - - #[inline(always)] - fn finish_err(&mut self, err: Box) -> Step { - self.result = Some(Err(err)); - Step::Break(Break::Done) - } - - #[inline(always)] - fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step { - self.result = Some(Err(Error::eval_error(format!( - "expected {expected}, got {got}" - )))); - Step::Break(Break::Done) - } - - #[inline(always)] - fn finish_vm_err(&mut self, err: VmError) -> Step { - self.finish_err(err.into_error()) - } - +impl<'gc> Machine<'gc> for Vm<'gc> { #[inline(always)] fn push(&mut self, val: Value<'gc>) { self.stack.push(val); } #[inline(always)] - #[must_use] fn pop(&mut self) -> Value<'gc> { self.stack.pop().expect("stack underflow") } #[inline(always)] - #[must_use] fn peek(&self, depth: usize) -> Value<'gc> { *self .stack @@ -184,7 +159,6 @@ impl<'gc> Vm<'gc> { } #[inline(always)] - #[must_use] fn peek_forced(&self, depth: usize) -> StrictValue<'gc> { self.stack .get(self.stack.len() - depth - 1) @@ -193,6 +167,15 @@ impl<'gc> Vm<'gc> { .expect("forced") } + #[inline(always)] + fn pop_forced(&mut self) -> StrictValue<'gc> { + self.stack + .pop() + .expect("stack underflow") + .restrict() + .expect("forced") + } + #[inline(always)] fn replace(&mut self, depth: usize, val: Value<'gc>) { let len = self.stack.len(); @@ -203,72 +186,13 @@ impl<'gc> Vm<'gc> { } #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - fn pop_forced(&mut self) -> StrictValue<'gc> { - self.stack - .pop() - .expect("stack underflow") - .restrict() - .expect("forced") - } - - /// Force the top `T::WIDTH` stack slots and return them as `T`. - /// - /// If any slot holds a pending thunk, this method pushes a call frame - /// whose resume PC is the **start of the current instruction** - /// (`reader.inst_start_pc()`), enters the thunk, and returns - /// `Break::Force`. When the thunk eventually returns, the VM will - /// **re-execute the entire opcode handler from the beginning**. - /// - /// # Invariants - /// - /// * **Do not call this method more than once in a single handler.** - /// If you need to force multiple values, use a tuple type such as - /// `(StrictValue, StrictValue)` so they are forced and popped in one - /// atomic operation. Calling `force_and_retry` twice (or more) - /// means the handler will be re-run from the top after each retry; - /// any stack modifications between the two calls would be duplicated - /// and corrupt the stack layout. - /// - /// * The caller must ensure that the stack layout at the point of - /// invocation is **identical** every time the handler is re-entered. - /// In practice this means no pushes, pops, or local mutations may - /// happen before the call, and the call must be the first thing - /// that consumes the instruction's operand values. - /// - /// * The return value must be propagated with `?` so that - /// `Break::Force` correctly unwinds to the dispatch loop. - #[inline(always)] - fn force_and_retry>( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> std::ops::ControlFlow { - self.force_and_retry_pc(reader, mc, reader.inst_start_pc()) - } - - /// Same as [`force_and_retry`](Self::force_and_retry) but allows - /// specifying a custom resume PC. - #[inline(always)] - fn force_and_retry_pc>( - &mut self, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - resume_pc: usize, - ) -> std::ops::ControlFlow { - T::force_and_check(self, reader, mc, 0, resume_pc)?; - std::ops::ControlFlow::Continue(T::pop_converted(self)) + fn drop_n(&mut self, depth: usize) { + self.stack.truncate(self.stack.len() - depth); } #[inline(always)] - #[allow(unused)] - fn force_slot( - &mut self, - depth: usize, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - ) -> Step { - self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc()) + fn stack_len(&self) -> usize { + self.stack.len() } #[inline(always)] @@ -313,54 +237,6 @@ impl<'gc> Vm<'gc> { } } } -} - -impl<'gc> Machine<'gc> for Vm<'gc> { - #[inline(always)] - fn push(&mut self, val: Value<'gc>) { - self.push(val); - } - - #[inline(always)] - fn pop(&mut self) -> Value<'gc> { - self.pop() - } - - #[inline(always)] - fn peek(&self, depth: usize) -> Value<'gc> { - Vm::peek(self, depth) - } - - #[inline(always)] - fn peek_forced(&self, depth: usize) -> StrictValue<'gc> { - Vm::peek_forced(self, depth) - } - - #[inline(always)] - fn pop_forced(&mut self) -> StrictValue<'gc> { - self.pop_forced() - } - - #[inline(always)] - fn replace(&mut self, depth: usize, val: Value<'gc>) { - self.replace(depth, val); - } - - #[inline(always)] - fn stack_len(&self) -> usize { - self.stack.len() - } - - #[inline(always)] - fn force_slot_to_pc( - &mut self, - depth: usize, - reader: &mut BytecodeReader<'_>, - mc: &Mutation<'gc>, - resume_pc: usize, - ) -> Step { - self.force_slot_to_pc(depth, reader, mc, resume_pc) - } #[inline(always)] fn call( @@ -370,7 +246,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> { arg: Value<'gc>, resume_pc: usize, ) -> Step { - self.call(reader, mc, arg, resume_pc) + instructions::call(self, reader, mc, arg, resume_pc) } #[inline(always)] @@ -410,17 +286,22 @@ impl<'gc> Machine<'gc> for Vm<'gc> { #[inline(always)] fn finish_ok(&mut self, val: fix_lang::Value) -> Step { - self.finish_ok(val) + self.result = Some(Ok(val)); + Step::Break(Break::Done) } #[inline(always)] fn finish_err(&mut self, err: Box) -> Step { - self.finish_err(err) + self.result = Some(Err(err)); + Step::Break(Break::Done) } #[inline(always)] fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step { - self.finish_type_err(expected, got) + self.result = Some(Err(Error::eval_error(format!( + "expected {expected}, got {got}" + )))); + Step::Break(Break::Done) } #[inline(always)] @@ -590,6 +471,7 @@ impl<'gc> Vm<'gc> { mc: &Mutation<'gc>, ) -> Action { use fix_bytecode::Op::*; + use instructions::*; let mut reader = BytecodeReader::new(bytecode, pc); let mut fuel = Self::DEFAULT_FUEL_AMOUNT; @@ -603,75 +485,75 @@ impl<'gc> Vm<'gc> { let op = reader.read_op(); let result = match op { - PushSmi => self.op_push_smi(&mut reader), - PushBigInt => self.op_push_bigint(&mut reader, mc), - PushFloat => self.op_push_float(&mut reader), - PushString => self.op_push_string(&mut reader), - PushNull => self.op_push_null(), - PushTrue => self.op_push_true(), - PushFalse => self.op_push_false(), + PushSmi => op_push_smi(self, &mut reader), + PushBigInt => op_push_bigint(self, &mut reader, mc), + PushFloat => op_push_float(self, &mut reader), + PushString => op_push_string(self, &mut reader), + PushNull => op_push_null(self), + PushTrue => op_push_true(self), + PushFalse => op_push_false(self), - LoadLocal => self.op_load_local(&mut reader), - LoadOuter => self.op_load_outer(&mut reader), - StoreLocal => self.op_store_local(&mut reader, mc), - AllocLocals => self.op_alloc_locals(&mut reader, mc), + LoadLocal => op_load_local(self, &mut reader), + LoadOuter => op_load_outer(self, &mut reader), + StoreLocal => op_store_local(self, &mut reader, mc), + AllocLocals => op_alloc_locals(self, &mut reader, mc), - MakeThunk => self.op_make_thunk(&mut reader, mc), - MakeClosure => self.op_make_closure(&mut reader, mc), - MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc), + MakeThunk => op_make_thunk(self, &mut reader, mc), + MakeClosure => op_make_closure(self, &mut reader, mc), + MakePatternClosure => op_make_pattern_closure(self, &mut reader, mc), - Call => self.op_call(ctx, &mut reader, mc), - DispatchPrimOp => self.op_dispatch_primop(ctx, &mut reader, mc), - Return => self.op_return(ctx, &mut reader, mc), + Call => op_call(self, ctx, &mut reader, mc), + DispatchPrimOp => op_dispatch_primop(self, ctx, &mut reader, mc), + Return => op_return(self, ctx, &mut reader, mc), - MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc), - MakeEmptyAttrs => self.op_make_empty_attrs(), - SelectStatic => self.op_select_static(ctx, &mut reader, mc), - SelectDynamic => self.op_select_dynamic(ctx, &mut reader, mc), - HasAttrPathStatic => self.op_has_attr_path_static(ctx, &mut reader, mc), - HasAttrPathDynamic => self.op_has_attr_path_dynamic(ctx, &mut reader, mc), - HasAttrStatic => self.op_has_attr_static(&mut reader, mc), - HasAttrDynamic => self.op_has_attr_dynamic(ctx, &mut reader, mc), - HasAttrResolve => self.op_has_attr_resolve(), - JumpIfSelectFailed => self.op_jump_if_select_failed(&mut reader), - JumpIfSelectSucceeded => self.op_jump_if_select_succeeded(&mut reader), + MakeAttrs => op_make_attrs(self, ctx, &mut reader, mc), + MakeEmptyAttrs => op_make_empty_attrs(self), + SelectStatic => op_select_static(self, ctx, &mut reader, mc), + SelectDynamic => op_select_dynamic(self, ctx, &mut reader, mc), + HasAttrPathStatic => op_has_attr_path_static(self, ctx, &mut reader, mc), + HasAttrPathDynamic => op_has_attr_path_dynamic(self, ctx, &mut reader, mc), + HasAttrStatic => op_has_attr_static(self, &mut reader, mc), + HasAttrDynamic => op_has_attr_dynamic(self, ctx, &mut reader, mc), + HasAttrResolve => op_has_attr_resolve(self), + JumpIfSelectFailed => op_jump_if_select_failed(self, &mut reader), + JumpIfSelectSucceeded => op_jump_if_select_succeeded(self, &mut reader), - MakeList => self.op_make_list(ctx, &mut reader, mc), - MakeEmptyList => self.op_make_empty_list(), + MakeList => op_make_list(self, ctx, &mut reader, mc), + MakeEmptyList => op_make_empty_list(self), - OpAdd => self.op_add(ctx, &mut reader, mc), - OpSub => self.op_sub(&mut reader, mc), - OpMul => self.op_mul(&mut reader, mc), - OpDiv => self.op_div(&mut reader, mc), - OpEq => self.op_eq(ctx, &mut reader, mc), - OpNeq => self.op_neq(ctx, &mut reader, mc), - OpLt => self.op_lt(ctx, &mut reader, mc), - OpGt => self.op_gt(ctx, &mut reader, mc), - OpLeq => self.op_leq(ctx, &mut reader, mc), - OpGeq => self.op_geq(ctx, &mut reader, mc), - OpConcat => self.op_concat(&mut reader, mc), - OpUpdate => self.op_update(&mut reader, mc), + OpAdd => op_add(self, ctx, &mut reader, mc), + OpSub => op_sub(self, &mut reader, mc), + OpMul => op_mul(self, &mut reader, mc), + OpDiv => op_div(self, &mut reader, mc), + OpEq => op_eq(self, ctx, &mut reader, mc), + OpNeq => op_neq(self, ctx, &mut reader, mc), + OpLt => op_lt(self, ctx, &mut reader, mc), + OpGt => op_gt(self, ctx, &mut reader, mc), + OpLeq => op_leq(self, ctx, &mut reader, mc), + OpGeq => op_geq(self, ctx, &mut reader, mc), + OpConcat => op_concat(self, &mut reader, mc), + OpUpdate => op_update(self, &mut reader, mc), - OpNeg => self.op_neg(&mut reader, mc), - OpNot => self.op_not(&mut reader, mc), + OpNeg => op_neg(self, &mut reader, mc), + OpNot => op_not(self, &mut reader, mc), - JumpIfFalse => self.op_jump_if_false(&mut reader, mc), - JumpIfTrue => self.op_jump_if_true(&mut reader, mc), - Jump => self.op_jump(&mut reader), + JumpIfFalse => op_jump_if_false(self, &mut reader, mc), + JumpIfTrue => op_jump_if_true(self, &mut reader, mc), + Jump => op_jump(self, &mut reader), - ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc), - CoerceToString => self.op_coerce_to_string(&mut reader, mc), - ResolvePath => self.op_resolve_path(ctx, &mut reader, mc), + ConcatStrings => op_concat_strings(self, ctx, &mut reader, mc), + CoerceToString => op_coerce_to_string(self, &mut reader, mc), + ResolvePath => op_resolve_path(self, ctx, &mut reader, mc), - Assert => self.op_assert(ctx, &mut reader, mc), + Assert => op_assert(self, ctx, &mut reader, mc), - LookupWith => self.op_lookup_with(ctx, &mut reader, mc), + LookupWith => op_lookup_with(self, ctx, &mut reader, mc), - LoadBuiltins => self.op_load_builtins(), - LoadBuiltin => self.op_load_builtin(&mut reader), + LoadBuiltins => op_load_builtins(self), + LoadBuiltin => op_load_builtin(self, &mut reader), - LoadReplBinding => self.op_load_repl_binding(&mut reader), - LoadScopedBinding => self.op_load_scoped_binding(ctx, &mut reader, mc), + LoadReplBinding => op_load_repl_binding(self, &mut reader), + LoadScopedBinding => op_load_scoped_binding(self, ctx, &mut reader, mc), Illegal => unreachable!(), };