diff --git a/Cargo.lock b/Cargo.lock index f7f82b0..270f605 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,6 +449,7 @@ dependencies = [ "clap", "criterion", "ere", + "fix-builtins", "fix-codegen", "fix-common", "fix-error", diff --git a/fix-builtins/src/lib.rs b/fix-builtins/src/lib.rs index acd3b42..6cd7176 100644 --- a/fix-builtins/src/lib.rs +++ b/fix-builtins/src/lib.rs @@ -88,7 +88,6 @@ define_builtins! { ("__mapAttrs", MapAttrs, 2), ("__match", Match, 2), ("__mul", Mul, 2), - ("null", Null, 0), // constant, not a function ("__parseDrvName", ParseDrvName, 1), ("__partition", Partition, 2), ("__path", Path, 1), @@ -123,3 +122,233 @@ define_builtins! { ("__warn", Warn, 2), ("__zipAttrsWith", ZipAttrsWith, 2), } + +#[repr(u8)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, TryFromPrimitive)] +pub enum PrimOpPhase { + Abort, + Add, + AddErrorContext, + All, + Any, + AppendContext, + AttrNames, + AttrValues, + BaseNameOf, + BitAnd, + BitOr, + BitXor, + Break, + CatAttrs, + Ceil, + CompareVersions, + ConcatLists, + ConcatMap, + ConcatStringsSep, + ConvertHash, + DeepSeq, + Derivation, + DerivationStrict, + DirOf, + Div, + Elem, + ElemAt, + FetchGit, + FetchMercurial, + FetchTarball, + FetchTree, + FetchUrl, + + FilterForceList, + FilterCallPred, + FilterCheck, + + FilterSource, + FindFile, + Floor, + FoldlStrict, + FromJSON, + FromTOML, + FunctionArgs, + GenList, + GenericClosure, + GetAttr, + GetContext, + GetEnv, + GroupBy, + HasAttr, + HasContext, + HashFile, + HashString, + Head, + Import, + IntersectAttrs, + IsAttrs, + IsBool, + IsFloat, + IsFunction, + IsInt, + IsList, + IsNull, + IsPath, + IsString, + Length, + LessThan, + ListToAttrs, + Map, + MapAttrs, + Match, + Mul, + ParseDrvName, + Partition, + Path, + PathExists, + Placeholder, + ReadDir, + ReadFile, + ReadFileType, + RemoveAttrs, + ReplaceStrings, + ScopedImport, + Seq, + Sort, + Split, + SplitVersion, + StorePath, + StringLength, + Sub, + Substring, + Tail, + Throw, + ToFile, + ToJSON, + ToPath, + ToString, + ToXML, + Trace, + TryEval, + TypeOf, + UnsafeDiscardStringContext, + UnsafeGetAttrPos, + Warn, + ZipAttrsWith, + + Illegal, +} + +impl BuiltinId { + #[inline(always)] + pub fn entry_phase(self) -> PrimOpPhase { + use BuiltinId::*; + match self { + Abort => PrimOpPhase::Abort, + Add => PrimOpPhase::Add, + AddErrorContext => PrimOpPhase::AddErrorContext, + All => PrimOpPhase::All, + Any => PrimOpPhase::Any, + AppendContext => PrimOpPhase::AppendContext, + AttrNames => PrimOpPhase::AttrNames, + AttrValues => PrimOpPhase::AttrValues, + BaseNameOf => PrimOpPhase::BaseNameOf, + BitAnd => PrimOpPhase::BitAnd, + BitOr => PrimOpPhase::BitOr, + BitXor => PrimOpPhase::BitXor, + Break => PrimOpPhase::Break, + CatAttrs => PrimOpPhase::CatAttrs, + Ceil => PrimOpPhase::Ceil, + CompareVersions => PrimOpPhase::CompareVersions, + ConcatLists => PrimOpPhase::ConcatLists, + ConcatMap => PrimOpPhase::ConcatMap, + ConcatStringsSep => PrimOpPhase::ConcatStringsSep, + ConvertHash => PrimOpPhase::ConvertHash, + DeepSeq => PrimOpPhase::DeepSeq, + Derivation => PrimOpPhase::Derivation, + DerivationStrict => PrimOpPhase::DerivationStrict, + DirOf => PrimOpPhase::DirOf, + Div => PrimOpPhase::Div, + Elem => PrimOpPhase::Elem, + ElemAt => PrimOpPhase::ElemAt, + FetchGit => PrimOpPhase::FetchGit, + FetchMercurial => PrimOpPhase::FetchMercurial, + FetchTarball => PrimOpPhase::FetchTarball, + FetchTree => PrimOpPhase::FetchTree, + FetchUrl => PrimOpPhase::FetchUrl, + Filter => PrimOpPhase::FilterForceList, + FilterSource => PrimOpPhase::FilterSource, + FindFile => PrimOpPhase::FindFile, + Floor => PrimOpPhase::Floor, + FoldlStrict => PrimOpPhase::FoldlStrict, + FromJSON => PrimOpPhase::FromJSON, + FromTOML => PrimOpPhase::FromTOML, + FunctionArgs => PrimOpPhase::FunctionArgs, + GenList => PrimOpPhase::GenList, + GenericClosure => PrimOpPhase::GenericClosure, + GetAttr => PrimOpPhase::GetAttr, + GetContext => PrimOpPhase::GetContext, + GetEnv => PrimOpPhase::GetEnv, + GroupBy => PrimOpPhase::GroupBy, + HasAttr => PrimOpPhase::HasAttr, + HasContext => PrimOpPhase::HasContext, + HashFile => PrimOpPhase::HashFile, + HashString => PrimOpPhase::HashString, + Head => PrimOpPhase::Head, + Import => PrimOpPhase::Import, + IntersectAttrs => PrimOpPhase::IntersectAttrs, + IsAttrs => PrimOpPhase::IsAttrs, + IsBool => PrimOpPhase::IsBool, + IsFloat => PrimOpPhase::IsFloat, + IsFunction => PrimOpPhase::IsFunction, + IsInt => PrimOpPhase::IsInt, + IsList => PrimOpPhase::IsList, + IsNull => PrimOpPhase::IsNull, + IsPath => PrimOpPhase::IsPath, + IsString => PrimOpPhase::IsString, + Length => PrimOpPhase::Length, + LessThan => PrimOpPhase::LessThan, + ListToAttrs => PrimOpPhase::ListToAttrs, + Map => PrimOpPhase::Map, + MapAttrs => PrimOpPhase::MapAttrs, + Match => PrimOpPhase::Match, + Mul => PrimOpPhase::Mul, + ParseDrvName => PrimOpPhase::ParseDrvName, + Partition => PrimOpPhase::Partition, + Path => PrimOpPhase::Path, + PathExists => PrimOpPhase::PathExists, + Placeholder => PrimOpPhase::Placeholder, + ReadDir => PrimOpPhase::ReadDir, + ReadFile => PrimOpPhase::ReadFile, + ReadFileType => PrimOpPhase::ReadFileType, + RemoveAttrs => PrimOpPhase::RemoveAttrs, + ReplaceStrings => PrimOpPhase::ReplaceStrings, + ScopedImport => PrimOpPhase::ScopedImport, + Seq => PrimOpPhase::Seq, + Sort => PrimOpPhase::Sort, + Split => PrimOpPhase::Split, + SplitVersion => PrimOpPhase::SplitVersion, + StorePath => PrimOpPhase::StorePath, + StringLength => PrimOpPhase::StringLength, + Sub => PrimOpPhase::Sub, + Substring => PrimOpPhase::Substring, + Tail => PrimOpPhase::Tail, + Throw => PrimOpPhase::Throw, + ToFile => PrimOpPhase::ToFile, + ToJSON => PrimOpPhase::ToJSON, + ToPath => PrimOpPhase::ToPath, + ToString => PrimOpPhase::ToString, + ToXML => PrimOpPhase::ToXML, + Trace => PrimOpPhase::Trace, + TryEval => PrimOpPhase::TryEval, + TypeOf => PrimOpPhase::TypeOf, + UnsafeDiscardStringContext => PrimOpPhase::UnsafeDiscardStringContext, + UnsafeGetAttrPos => PrimOpPhase::UnsafeGetAttrPos, + Warn => PrimOpPhase::Warn, + ZipAttrsWith => PrimOpPhase::ZipAttrsWith, + } + } +} + +impl PrimOpPhase { + pub fn ip(self) -> u32 { + self as u32 * 2 + } +} diff --git a/fix-codegen/src/disassembler.rs b/fix-codegen/src/disassembler.rs index e183dc8..a7958a1 100644 --- a/fix-codegen/src/disassembler.rs +++ b/fix-codegen/src/disassembler.rs @@ -281,6 +281,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { } Op::Call => ("Call", String::new()), + Op::DispatchPrimOp => { + todo!(); + } Op::MakeAttrs => { let count = self.read_u32(); diff --git a/fix-codegen/src/lib.rs b/fix-codegen/src/lib.rs index a820834..38dc8aa 100644 --- a/fix-codegen/src/lib.rs +++ b/fix-codegen/src/lib.rs @@ -42,6 +42,7 @@ pub enum Op { MakePatternClosure, Call, + DispatchPrimOp, MakeAttrs, MakeEmptyAttrs, @@ -125,7 +126,11 @@ pub enum Const { Float(f64), Bool(bool), String(StringId), - PrimOp { id: BuiltinId, arity: u8 }, + PrimOp { + id: BuiltinId, + arity: u8, + dispatch_ip: u32, + }, Null, } diff --git a/fix-common/src/lib.rs b/fix-common/src/lib.rs index 8a9e252..15aeda4 100644 --- a/fix-common/src/lib.rs +++ b/fix-common/src/lib.rs @@ -272,9 +272,9 @@ pub enum Value { /// A function (lambda). Func, /// A primitive (built-in) operation. - PrimOp(String), + PrimOp(&'static str), /// A partially applied primitive operation. - PrimOpApp(String), + PrimOpApp(&'static str), /// A marker for a value that has been seen before during serialization, to break cycles. /// This is used to prevent infinite recursion when printing or serializing cyclic data structures. Repeated, diff --git a/fix-vm/src/bytecode_reader.rs b/fix-vm/src/bytecode_reader.rs index 6f7f51e..12091c0 100644 --- a/fix-vm/src/bytecode_reader.rs +++ b/fix-vm/src/bytecode_reader.rs @@ -139,8 +139,4 @@ impl<'a> BytecodeReader<'a> { pub(crate) fn inst_start_pc(&self) -> usize { self.inst_start_pc } - - pub(crate) fn bytecode(&self) -> &'a [u8] { - self.bytecode - } } diff --git a/fix-vm/src/dispatch_tailcall.rs b/fix-vm/src/dispatch_tailcall.rs index 1d6a492..741f59c 100644 --- a/fix-vm/src/dispatch_tailcall.rs +++ b/fix-vm/src/dispatch_tailcall.rs @@ -142,6 +142,7 @@ tail_fn!(op_make_closure, (reader, mc)); tail_fn!(op_make_pattern_closure, (reader, mc)); tail_fn!(op_call, (ctx, reader, mc)); +tail_fn!(op_dispatch_primop, (ctx, reader, mc)); tail_fn!(op_return, (ctx, reader, mc)); tail_fn!(op_make_attrs, (ctx, reader, mc)); @@ -234,6 +235,7 @@ table! { MakePatternClosure => op_make_pattern_closure, Call => op_call, + DispatchPrimOp => op_dispatch_primop, Return => op_return, MakeAttrs => op_make_attrs, diff --git a/fix-vm/src/forced.rs b/fix-vm/src/forced.rs index 81e3bf2..f0f716f 100644 --- a/fix-vm/src/forced.rs +++ b/fix-vm/src/forced.rs @@ -16,6 +16,7 @@ pub(crate) trait Forced<'gc>: Sized { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base_depth: usize, + resume_pc: usize, ) -> Step; /// After `force_and_check` returned `Continue`, pop `WIDTH` slots @@ -33,8 +34,9 @@ impl<'gc> Forced<'gc> for StrictValue<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base_depth: usize, + resume_pc: usize, ) -> Step { - vm.force_slot(base_depth, reader, mc) + vm.force_slot_to_pc(base_depth, reader, mc, resume_pc) } #[inline(always)] @@ -55,8 +57,9 @@ macro_rules! impl_forced_inline { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base_depth: usize, + resume_pc: usize, ) -> Step { - vm.force_slot(base_depth, reader, mc)?; + vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; let v = vm.peek_forced(base_depth); if v.as_inline::<$ty>().is_none() { let _: Step = vm.finish_type_err($nix_ty, v.ty()); @@ -88,8 +91,9 @@ macro_rules! impl_forced_gc { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base_depth: usize, + resume_pc: usize, ) -> Step { - vm.force_slot(base_depth, reader, mc)?; + vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; let v = vm.peek_forced(base_depth); if v.as_gc::<$ty>().is_none() { let _: Step = vm.finish_type_err($nix_ty, v.ty()); @@ -135,8 +139,9 @@ impl<'gc> Forced<'gc> for NixNum { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base_depth: usize, + resume_pc: usize, ) -> Step { - vm.force_slot(base_depth, reader, mc)?; + vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; let v = vm.peek_forced(base_depth); if v.as_num().is_none() { let _: Step = vm.finish_type_err(NixType::Int, v.ty()); @@ -162,8 +167,9 @@ impl<'gc> Forced<'gc> for f64 { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base_depth: usize, + resume_pc: usize, ) -> Step { - vm.force_slot(base_depth, reader, mc)?; + vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; let v = vm.peek_forced(base_depth); if v.as_float().is_none() { let _: Step = vm.finish_type_err(NixType::Float, v.ty()); @@ -189,9 +195,10 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>> Forced<'gc> for (A, B) { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base: usize, + resume_pc: usize, ) -> Step { - A::force_and_check(vm, reader, mc, base + B::WIDTH)?; - B::force_and_check(vm, reader, mc, base) + A::force_and_check(vm, reader, mc, base + B::WIDTH, resume_pc)?; + B::force_and_check(vm, reader, mc, base, resume_pc) } #[inline(always)] @@ -211,10 +218,11 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>> Forced<'gc> for (A, B, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base: usize, + resume_pc: usize, ) -> Step { - A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH)?; - B::force_and_check(vm, reader, mc, base + C::WIDTH)?; - C::force_and_check(vm, reader, mc, base) + A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH, resume_pc)?; + B::force_and_check(vm, reader, mc, base + C::WIDTH, resume_pc)?; + C::force_and_check(vm, reader, mc, base, resume_pc) } #[inline(always)] @@ -237,11 +245,18 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>, D: Forced<'gc>> Forced reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, base: usize, + resume_pc: usize, ) -> Step { - A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH + D::WIDTH)?; - B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH)?; - C::force_and_check(vm, reader, mc, base + D::WIDTH)?; - D::force_and_check(vm, reader, mc, base) + A::force_and_check( + vm, + reader, + mc, + base + B::WIDTH + C::WIDTH + D::WIDTH, + resume_pc, + )?; + B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH, resume_pc)?; + C::force_and_check(vm, reader, mc, base + D::WIDTH, resume_pc)?; + D::force_and_check(vm, reader, mc, base, resume_pc) } #[inline(always)] diff --git a/fix-vm/src/instructions/arithmetic.rs b/fix-vm/src/instructions/arithmetic.rs index 710353d..d44bb43 100644 --- a/fix-vm/src/instructions/arithmetic.rs +++ b/fix-vm/src/instructions/arithmetic.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use gc_arena::{Gc, Mutation}; +use gc_arena::{Gc, Mutation, RefLock}; use crate::value::*; use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _}; @@ -173,9 +173,14 @@ impl<'gc> crate::Vm<'gc> { ) -> Step { let (l, r) = self.try_force::<(Gc, Gc)>(reader, mc)?; let mut items = smallvec::SmallVec::new(); - items.extend_from_slice(&l); - items.extend_from_slice(&r); - self.push(Value::new_gc(Gc::new(mc, crate::List { inner: items }))); + items.extend_from_slice(&l.inner.borrow()); + items.extend_from_slice(&r.inner.borrow()); + self.push(Value::new_gc(Gc::new( + mc, + crate::List { + inner: RefLock::new(items), + }, + ))); Step::Continue(()) } @@ -224,10 +229,10 @@ impl<'gc> crate::Vm<'gc> { return Ok(a == b); } if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { - if a.inner.len() != b.inner.len() { + if a.inner.borrow().len() != b.inner.borrow().len() { return Ok(false); } - for (x, y) in a.inner.iter().zip(b.inner.iter()) { + for (x, y) in a.inner.borrow().iter().zip(b.inner.borrow().iter()) { let lx = x.restrict().expect("forced"); let ly = y.restrict().expect("forced"); if !self.values_equal(ctx, lx, ly)? { diff --git a/fix-vm/src/instructions/builtins.rs b/fix-vm/src/instructions/builtins.rs new file mode 100644 index 0000000..8e95cdc --- /dev/null +++ b/fix-vm/src/instructions/builtins.rs @@ -0,0 +1,111 @@ +use fix_builtins::PrimOpPhase; +use fix_error::Error; +use gc_arena::Mutation; +use num_enum::TryFromPrimitive as _; + +use crate::value::*; +use crate::{BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx}; + +impl<'gc> Vm<'gc> { + pub(crate) fn op_dispatch_primop( + &mut self, + _ctx: &mut impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + use PrimOpPhase::*; + let phase_disc = reader.read_u8(); + let Ok(phase) = PrimOpPhase::try_from_primitive(phase_disc) else { + return self.finish_err(Error::eval_error("invalid primop phase")); + }; + match phase { + FilterForceList => self.primop_filter_force_list(reader, mc), + FilterCallPred => self.primop_filter_call_pred(reader, mc), + FilterCheck => self.primop_filter_check(reader, mc), + _ => todo!("dispatch builtin phase"), + } + } + + fn return_from_primop(&mut self, reader: &mut BytecodeReader<'_>) -> Step { + let Some(CallFrame { + pc: ret_pc, + stack_depth: _, + thunk: _, + env, + with_env, + }) = self.call_stack.pop() + else { + unreachable!() + }; + reader.set_pc(ret_pc); + self.call_depth -= 1; + self.env = env; + self.with_env = with_env; + Step::Continue(()) + } + + pub(crate) fn primop_filter_force_list( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + self.force_slot(0, reader, mc)?; + let list = match self.peek_forced(0).expect_gc::() { + Ok(list) => list, + Err(got) => return self.finish_type_err(NixType::List, got), + }; + if list.inner.borrow().is_empty() { + return self.return_from_primop(reader); + } + // prepare stack layout: [ pred list idx acc ] + self.push(Value::new_inline(0)); + self.push(Value::new_gc(List::new_gc(mc))); + reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize); + Step::Continue(()) + } + + pub(crate) fn primop_filter_call_pred( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + self.force_slot(3, reader, mc)?; + let pred = self.peek_forced(3); + #[allow(clippy::unwrap_used)] + let idx = self.peek(1).as_inline::().unwrap(); + #[allow(clippy::unwrap_used)] + let elem = self.peek_forced(2).as_gc::().unwrap().inner.borrow()[idx as usize]; + self.push(pred.relax()); + self.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize) + } + + pub(crate) fn primop_filter_check( + &mut self, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + let ret = self.try_force::(reader, mc)?; + #[allow(clippy::unwrap_used)] + let idx = self.peek(1).as_inline::().unwrap(); + #[allow(clippy::unwrap_used)] + let list = self.peek_forced(2).as_gc::().unwrap(); + let list = list.inner.borrow(); + #[allow(clippy::unwrap_used)] + let acc = self.peek_forced(0).as_gc::().unwrap(); + if ret { + let mut acc = acc.unlock(mc).borrow_mut(); + acc.push(list[idx as usize]); + } + if idx as usize == list.len() - 1 { + let acc = self.pop(); + let _ = self.pop(); // idx + let _ = self.pop(); // list + let _ = self.pop(); // pred + self.push(acc); + return self.return_from_primop(reader); + } + self.replace(1, Value::new_inline(idx + 1)); + reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize); + Step::Continue(()) + } +} diff --git a/fix-vm/src/instructions/calls.rs b/fix-vm/src/instructions/calls.rs index 9fd9120..fc89c18 100644 --- a/fix-vm/src/instructions/calls.rs +++ b/fix-vm/src/instructions/calls.rs @@ -8,18 +8,18 @@ use crate::{ impl<'gc> crate::Vm<'gc> { #[inline(always)] - pub(crate) fn op_call( + pub(crate) fn call( &mut self, - ctx: &mut impl VmRuntimeCtx, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, + arg: Value<'gc>, + resume_pc: usize, ) -> Step { let func = self.try_force::(reader, mc)?; if self.call_depth > 10000 { return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded")); } self.call_depth += 1; - let arg = reader.read_operand_data(ctx).resolve(mc, self); if let Some(closure) = func.as_gc::() { let ip = closure.ip; let n_locals = closure.n_locals; @@ -29,7 +29,7 @@ impl<'gc> crate::Vm<'gc> { } else { let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); self.call_stack.push(CallFrame { - pc: reader.pc(), + pc: resume_pc, stack_depth: 0, thunk: None, env: self.env, @@ -38,12 +38,66 @@ impl<'gc> crate::Vm<'gc> { 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, + stack_depth: 0, + thunk: None, + env: self.env, + with_env: self.with_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, + stack_depth: 0, + thunk: None, + env: self.env, + with_env: self.with_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 { todo!("call other types: {func:?}") } Step::Continue(()) } + #[inline(always)] + pub(crate) fn op_call( + &mut self, + ctx: &impl VmRuntimeCtx, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + ) -> Step { + let arg = reader.read_operand_data(ctx).resolve(mc, self); + let pc = reader.pc(); + self.call(reader, mc, arg, pc) + } + #[inline(always)] pub(crate) fn op_return( &mut self, @@ -51,16 +105,7 @@ impl<'gc> crate::Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { - self.handle_return(reader, ctx, mc) - } - - pub(crate) fn handle_return( - &mut self, - reader: &mut BytecodeReader<'_>, - ctx: &C, - mc: &Mutation<'gc>, - ) -> Step { - let ret_inst_pc = reader.pc() - 1; + let val = self.try_force::(reader, mc)?; let Some(CallFrame { pc: ret_pc, stack_depth, @@ -69,66 +114,15 @@ impl<'gc> crate::Vm<'gc> { with_env, }) = self.call_stack.pop() else { - let val = self.pop(); - return self.finish_ok(ctx.convert_value(val)); + return self.finish_ok(ctx.convert_value(val.relax())); }; reader.set_pc(ret_pc); if let Some(outer_thunk) = thunk { - let val = self.pop(); - match val.restrict() { - Ok(val) => { - *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); - if reader.bytecode().get(ret_pc).copied() == Some(fix_codegen::Op::Return as u8) - { - self.push(val.relax()); - } - } - Err(inner_thunk) => { - let mut state = inner_thunk.borrow_mut(mc); - match *state { - ThunkState::Pending { - ip: inner_ip, - env: inner_env, - with_env: inner_with_env, - } => { - self.call_stack.push(CallFrame { - pc: ret_pc, - stack_depth, - thunk: Some(outer_thunk), - env, - with_env, - }); - self.call_stack.push(CallFrame { - pc: ret_inst_pc, - stack_depth: 0, - thunk: Some(inner_thunk), - env: inner_env, - with_env: inner_with_env, - }); - *state = ThunkState::Blackhole; - reader.set_pc(inner_ip); - self.env = inner_env; - self.with_env = inner_with_env; - return Step::Continue(()); - } - ThunkState::Evaluated(val) => { - *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); - if reader.bytecode().get(ret_pc).copied() - == Some(fix_codegen::Op::Return as u8) - { - self.push(val.relax()); - } - } - ThunkState::Apply { func: _, arg: _ } => todo!("force Apply thunk"), - ThunkState::Blackhole => { - return self - .finish_err(Error::eval_error("infinite recursion encountered")); - } - } - } - } + *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); + self.replace(stack_depth, val.relax()); } else { self.call_depth -= 1; + self.push(val.relax()) } self.env = env; self.with_env = with_env; diff --git a/fix-vm/src/instructions/collections.rs b/fix-vm/src/instructions/collections.rs index 579bbbb..37be42f 100644 --- a/fix-vm/src/instructions/collections.rs +++ b/fix-vm/src/instructions/collections.rs @@ -1,6 +1,6 @@ use fix_common::StringId; use fix_error::Error; -use gc_arena::Gc; +use gc_arena::{Gc, RefLock}; use smallvec::SmallVec; use crate::value::NixType; @@ -298,7 +298,12 @@ impl<'gc> crate::Vm<'gc> { for _ in 0..count { items.push(reader.read_operand_data(ctx).resolve(mc, self)); } - let list = Gc::new(mc, List { inner: items }); + let list = Gc::new( + mc, + List { + inner: RefLock::new(items), + }, + ); self.push(Value::new_gc(list)); Step::Continue(()) } diff --git a/fix-vm/src/instructions/builtins_misc.rs b/fix-vm/src/instructions/misc.rs similarity index 97% rename from fix-vm/src/instructions/builtins_misc.rs rename to fix-vm/src/instructions/misc.rs index c6e5e4a..f6c3cde 100644 --- a/fix-vm/src/instructions/builtins_misc.rs +++ b/fix-vm/src/instructions/misc.rs @@ -17,6 +17,7 @@ impl<'gc> crate::Vm<'gc> { self.push(Value::new_inline(PrimOp { id, arity: fix_builtins::BUILTINS[id as usize].1, + dispatch_ip: id.entry_phase().ip(), })); Step::Continue(()) } diff --git a/fix-vm/src/instructions/mod.rs b/fix-vm/src/instructions/mod.rs index a4878d2..9a87d84 100644 --- a/fix-vm/src/instructions/mod.rs +++ b/fix-vm/src/instructions/mod.rs @@ -1,9 +1,10 @@ pub(crate) mod arithmetic; -pub(crate) mod builtins_misc; +pub(crate) mod builtins; pub(crate) mod calls; pub(crate) mod closures; pub(crate) mod collections; pub(crate) mod control; pub(crate) mod literals; +pub(crate) mod misc; pub(crate) mod variables; pub(crate) mod with_scope; diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index c5b1076..0b36762 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -142,6 +142,7 @@ impl VmRuntimeCtxExt for T { } else if let Some(list) = val.as_gc::() { let items: Vec<_> = list .inner + .borrow() .iter() .copied() .map(|v| self.convert_value(v)) @@ -151,10 +152,12 @@ impl VmRuntimeCtxExt for T { Value::Func } else if val.is::() { Value::Thunk - } else if val.as_inline::().is_some() { - Value::PrimOp("primop".into()) - } else if val.is::() { - Value::PrimOpApp("primop-app".into()) + } else if let Some(primop) = val.as_inline::() { + let name = fix_builtins::BUILTINS[primop.id as usize].0; + Value::PrimOp(name.strip_prefix("__").unwrap_or(name)) + } else if let Some(app) = val.as_gc::() { + let name = fix_builtins::BUILTINS[app.primop.id as usize].0; + Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name)) } else { Value::Null } @@ -231,7 +234,15 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value< let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible"); let name = name.strip_prefix("__").unwrap_or(name); let name = ctx.intern_string(name); - entries.push((name, Value::new_inline(PrimOp { id, arity }))); + let dispatch_ip = id.entry_phase().ip(); + entries.push(( + name, + Value::new_inline(PrimOp { + id, + arity, + dispatch_ip, + }), + )); } let consts = [ @@ -248,15 +259,7 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value< "__storeDir", Value::new_inline(ctx.intern_string("/nix/store")), ), - ( - "__nixPath", - Value::new_gc(Gc::new( - mc, - List { - inner: SmallVec::new(), - }, - )), - ), + ("__nixPath", Value::new_gc(Gc::new(mc, List::default()))), ("null", Value::new_inline(Null)), ("true", Value::new_inline(true)), ("false", Value::new_inline(false)), @@ -382,16 +385,38 @@ impl<'gc> Vm<'gc> { reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> std::ops::ControlFlow { - T::force_and_check(self, reader, mc, 0)?; + self.try_force_to_pc(reader, mc, reader.inst_start_pc()) + } + + #[inline(always)] + pub(crate) fn try_force_to_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)) } #[inline(always)] + #[allow(unused)] pub(crate) 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()) + } + + #[inline(always)] + pub(crate) fn force_slot_to_pc( + &mut self, + depth: usize, + reader: &mut BytecodeReader<'_>, + mc: &Mutation<'gc>, + resume_pc: usize, ) -> Step { let Some(thunk) = self.peek(depth).as_gc::() else { return Step::Continue(()); @@ -404,7 +429,7 @@ impl<'gc> Vm<'gc> { self.call_stack.push(CallFrame { thunk: Some(thunk), stack_depth: depth, - pc: reader.inst_start_pc(), + pc: resume_pc, env: self.env, with_env: self.with_env, }); @@ -554,6 +579,7 @@ impl<'gc> Vm<'gc> { MakePatternClosure => self.op_make_pattern_closure(&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), MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc), diff --git a/fix-vm/src/value.rs b/fix-vm/src/value.rs index 9f25ded..9578a97 100644 --- a/fix-vm/src/value.rs +++ b/fix-vm/src/value.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] +use std::cell::RefCell; use std::fmt; use std::marker::PhantomData; use std::mem::size_of; @@ -7,6 +8,7 @@ use std::ops::Deref; use fix_builtins::BuiltinId; use fix_common::*; +use gc_arena::barrier::Unlock; use gc_arena::collect::Trace; use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock}; use num_enum::TryFromPrimitive; @@ -345,8 +347,12 @@ impl StaticValue { Self(Value::new_inline(val)) } #[inline] - pub fn new_primop(id: BuiltinId, arity: u8) -> Self { - Self(Value::new_inline(PrimOp { id, arity })) + pub fn new_primop(id: BuiltinId, arity: u8, dispatch_ip: u32) -> Self { + Self(Value::new_inline(PrimOp { + id, + arity, + dispatch_ip, + })) } #[inline] pub fn is_float(self) -> bool { @@ -487,14 +493,22 @@ impl<'gc> AttrSet<'gc> { } #[derive(Collect, Debug, Default)] +#[repr(transparent)] #[collect(no_drop)] pub(crate) struct List<'gc> { - pub(crate) inner: SmallVec<[Value<'gc>; 4]>, + pub(crate) inner: RefLock; 4]>>, } -impl<'gc> Deref for List<'gc> { - type Target = SmallVec<[Value<'gc>; 4]>; - fn deref(&self) -> &Self::Target { - &self.inner + +impl<'gc> List<'gc> { + pub(crate) fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> { + Gc::new(mc, Self::default()) + } +} + +impl<'gc> Unlock for List<'gc> { + type Unlocked = RefCell; 4]>>; + unsafe fn unlock_unchecked(&self) -> &Self::Unlocked { + unsafe { self.inner.unlock_unchecked() } } } @@ -572,22 +586,33 @@ pub(crate) struct PatternInfo { pub(crate) param_spans: Box<[(StringId, u32)]>, } +#[repr(packed, Rust)] #[derive(Clone, Copy, Debug, Collect)] #[collect(require_static)] pub(crate) struct PrimOp { pub(crate) id: BuiltinId, pub(crate) arity: u8, + pub(crate) dispatch_ip: u32, } impl RawStore for PrimOp { fn to_val(self, value: &mut RawValue) { - value.set_data([0, 0, 0, 0, self.id as u8, self.arity]); + let bytes = self.dispatch_ip.to_le_bytes(); + value.set_data([ + self.id as u8, + self.arity, + bytes[0], + bytes[1], + bytes[2], + bytes[3], + ]); } fn from_val(value: &RawValue) -> Self { - let [.., id, arity] = *value.data(); + let [id, arity, bytes @ ..] = *value.data(); Self { id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"), arity, + dispatch_ip: u32::from_le_bytes(bytes), } } } @@ -596,7 +621,8 @@ impl RawStore for PrimOp { #[collect(no_drop)] pub(crate) struct PrimOpApp<'gc> { pub(crate) primop: PrimOp, - pub(crate) args: SmallVec<[Value<'gc>; 2]>, + pub(crate) arity: u8, + pub(crate) args: [Value<'gc>; 3], } #[derive(Copy, Clone, Default, Collect)] diff --git a/fix/Cargo.toml b/fix/Cargo.toml index 8d2cfea..493224e 100644 --- a/fix/Cargo.toml +++ b/fix/Cargo.toml @@ -33,6 +33,7 @@ rnix = { workspace = true } ere = { workspace = true } ghost-cell = { workspace = true } +fix-builtins = { path = "../fix-builtins" } fix-common = { path = "../fix-common" } fix-codegen = { path = "../fix-codegen" } fix-error = { path = "../fix-error" } diff --git a/fix/src/lib.rs b/fix/src/lib.rs index d539585..22b6d4c 100644 --- a/fix/src/lib.rs +++ b/fix/src/lib.rs @@ -2,8 +2,9 @@ #![allow(dead_code)] use bumpalo::Bump; +use fix_builtins::PrimOpPhase; use fix_codegen::disassembler::{Disassembler, DisassemblerContext}; -use fix_codegen::{BytecodeContext, InstructionPtr}; +use fix_codegen::{BytecodeContext, InstructionPtr, Op}; use fix_common::{StringId, Symbol}; use fix_error::{Error, Result, Source}; use fix_ir::downgrade::{Downgrade as _, DowngradeContext}; @@ -47,6 +48,11 @@ impl Evaluator { pub fn new() -> Self { let mut strings = DefaultStringInterner::new(); let global_env = fix_ir::new_global_env(&mut strings); + let mut bytecode = Vec::with_capacity(PrimOpPhase::Illegal as usize * 2); + for phase in 0..=PrimOpPhase::Illegal as u8 { + bytecode.push(Op::DispatchPrimOp as u8); + bytecode.push(phase); + } Self { runtime: RuntimeState { strings, @@ -56,7 +62,7 @@ impl Evaluator { sources: Vec::new(), spans: Vec::new(), thunk_count: 0, - bytecode: Vec::new(), + bytecode, global_env, }, } @@ -239,7 +245,11 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> { Float(x) => StaticValue::new_float(x), Bool(x) => StaticValue::new_inline(x), String(x) => StaticValue::new_inline(x), - PrimOp { id, arity } => StaticValue::new_primop(id, arity), + PrimOp { + id, + arity, + dispatch_ip, + } => StaticValue::new_primop(id, arity, dispatch_ip), Null => StaticValue::default(), }; self.runtime.add_const(val)