implement primop (filter)

This commit is contained in:
2026-04-25 21:08:54 +08:00
parent 4f3cd0ef4c
commit d77dcc8929
18 changed files with 558 additions and 129 deletions
Generated
+1
View File
@@ -449,6 +449,7 @@ dependencies = [
"clap", "clap",
"criterion", "criterion",
"ere", "ere",
"fix-builtins",
"fix-codegen", "fix-codegen",
"fix-common", "fix-common",
"fix-error", "fix-error",
+230 -1
View File
@@ -88,7 +88,6 @@ define_builtins! {
("__mapAttrs", MapAttrs, 2), ("__mapAttrs", MapAttrs, 2),
("__match", Match, 2), ("__match", Match, 2),
("__mul", Mul, 2), ("__mul", Mul, 2),
("null", Null, 0), // constant, not a function
("__parseDrvName", ParseDrvName, 1), ("__parseDrvName", ParseDrvName, 1),
("__partition", Partition, 2), ("__partition", Partition, 2),
("__path", Path, 1), ("__path", Path, 1),
@@ -123,3 +122,233 @@ define_builtins! {
("__warn", Warn, 2), ("__warn", Warn, 2),
("__zipAttrsWith", ZipAttrsWith, 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
}
}
+3
View File
@@ -281,6 +281,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
} }
Op::Call => ("Call", String::new()), Op::Call => ("Call", String::new()),
Op::DispatchPrimOp => {
todo!();
}
Op::MakeAttrs => { Op::MakeAttrs => {
let count = self.read_u32(); let count = self.read_u32();
+6 -1
View File
@@ -42,6 +42,7 @@ pub enum Op {
MakePatternClosure, MakePatternClosure,
Call, Call,
DispatchPrimOp,
MakeAttrs, MakeAttrs,
MakeEmptyAttrs, MakeEmptyAttrs,
@@ -125,7 +126,11 @@ pub enum Const {
Float(f64), Float(f64),
Bool(bool), Bool(bool),
String(StringId), String(StringId),
PrimOp { id: BuiltinId, arity: u8 }, PrimOp {
id: BuiltinId,
arity: u8,
dispatch_ip: u32,
},
Null, Null,
} }
+2 -2
View File
@@ -272,9 +272,9 @@ pub enum Value {
/// A function (lambda). /// A function (lambda).
Func, Func,
/// A primitive (built-in) operation. /// A primitive (built-in) operation.
PrimOp(String), PrimOp(&'static str),
/// A partially applied primitive operation. /// 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. /// 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. /// This is used to prevent infinite recursion when printing or serializing cyclic data structures.
Repeated, Repeated,
-4
View File
@@ -139,8 +139,4 @@ impl<'a> BytecodeReader<'a> {
pub(crate) fn inst_start_pc(&self) -> usize { pub(crate) fn inst_start_pc(&self) -> usize {
self.inst_start_pc self.inst_start_pc
} }
pub(crate) fn bytecode(&self) -> &'a [u8] {
self.bytecode
}
} }
+2
View File
@@ -142,6 +142,7 @@ tail_fn!(op_make_closure, (reader, mc));
tail_fn!(op_make_pattern_closure, (reader, mc)); tail_fn!(op_make_pattern_closure, (reader, mc));
tail_fn!(op_call, (ctx, 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_return, (ctx, reader, mc));
tail_fn!(op_make_attrs, (ctx, reader, mc)); tail_fn!(op_make_attrs, (ctx, reader, mc));
@@ -234,6 +235,7 @@ table! {
MakePatternClosure => op_make_pattern_closure, MakePatternClosure => op_make_pattern_closure,
Call => op_call, Call => op_call,
DispatchPrimOp => op_dispatch_primop,
Return => op_return, Return => op_return,
MakeAttrs => op_make_attrs, MakeAttrs => op_make_attrs,
+29 -14
View File
@@ -16,6 +16,7 @@ pub(crate) trait Forced<'gc>: Sized {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base_depth: usize, base_depth: usize,
resume_pc: usize,
) -> Step; ) -> Step;
/// After `force_and_check` returned `Continue`, pop `WIDTH` slots /// After `force_and_check` returned `Continue`, pop `WIDTH` slots
@@ -33,8 +34,9 @@ impl<'gc> Forced<'gc> for StrictValue<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base_depth: usize, base_depth: usize,
resume_pc: usize,
) -> Step { ) -> Step {
vm.force_slot(base_depth, reader, mc) vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)
} }
#[inline(always)] #[inline(always)]
@@ -55,8 +57,9 @@ macro_rules! impl_forced_inline {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base_depth: usize, base_depth: usize,
resume_pc: usize,
) -> Step { ) -> 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); let v = vm.peek_forced(base_depth);
if v.as_inline::<$ty>().is_none() { if v.as_inline::<$ty>().is_none() {
let _: Step = vm.finish_type_err($nix_ty, v.ty()); let _: Step = vm.finish_type_err($nix_ty, v.ty());
@@ -88,8 +91,9 @@ macro_rules! impl_forced_gc {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base_depth: usize, base_depth: usize,
resume_pc: usize,
) -> Step { ) -> 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); let v = vm.peek_forced(base_depth);
if v.as_gc::<$ty>().is_none() { if v.as_gc::<$ty>().is_none() {
let _: Step = vm.finish_type_err($nix_ty, v.ty()); let _: Step = vm.finish_type_err($nix_ty, v.ty());
@@ -135,8 +139,9 @@ impl<'gc> Forced<'gc> for NixNum {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base_depth: usize, base_depth: usize,
resume_pc: usize,
) -> Step { ) -> 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); let v = vm.peek_forced(base_depth);
if v.as_num().is_none() { if v.as_num().is_none() {
let _: Step = vm.finish_type_err(NixType::Int, v.ty()); let _: Step = vm.finish_type_err(NixType::Int, v.ty());
@@ -162,8 +167,9 @@ impl<'gc> Forced<'gc> for f64 {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base_depth: usize, base_depth: usize,
resume_pc: usize,
) -> Step { ) -> 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); let v = vm.peek_forced(base_depth);
if v.as_float().is_none() { if v.as_float().is_none() {
let _: Step = vm.finish_type_err(NixType::Float, v.ty()); 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<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base: usize, base: usize,
resume_pc: usize,
) -> Step { ) -> Step {
A::force_and_check(vm, reader, mc, base + B::WIDTH)?; A::force_and_check(vm, reader, mc, base + B::WIDTH, resume_pc)?;
B::force_and_check(vm, reader, mc, base) B::force_and_check(vm, reader, mc, base, resume_pc)
} }
#[inline(always)] #[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<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base: usize, base: usize,
resume_pc: usize,
) -> Step { ) -> Step {
A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH)?; A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH, resume_pc)?;
B::force_and_check(vm, reader, mc, base + C::WIDTH)?; B::force_and_check(vm, reader, mc, base + C::WIDTH, resume_pc)?;
C::force_and_check(vm, reader, mc, base) C::force_and_check(vm, reader, mc, base, resume_pc)
} }
#[inline(always)] #[inline(always)]
@@ -237,11 +245,18 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>, D: Forced<'gc>> Forced
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
base: usize, base: usize,
resume_pc: usize,
) -> Step { ) -> Step {
A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH + D::WIDTH)?; A::force_and_check(
B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH)?; vm,
C::force_and_check(vm, reader, mc, base + D::WIDTH)?; reader,
D::force_and_check(vm, reader, mc, base) 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)] #[inline(always)]
+11 -6
View File
@@ -1,6 +1,6 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use gc_arena::{Gc, Mutation}; use gc_arena::{Gc, Mutation, RefLock};
use crate::value::*; use crate::value::*;
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _}; use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _};
@@ -173,9 +173,14 @@ impl<'gc> crate::Vm<'gc> {
) -> Step { ) -> Step {
let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(reader, mc)?; let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(reader, mc)?;
let mut items = smallvec::SmallVec::new(); let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l); items.extend_from_slice(&l.inner.borrow());
items.extend_from_slice(&r); items.extend_from_slice(&r.inner.borrow());
self.push(Value::new_gc(Gc::new(mc, crate::List { inner: items }))); self.push(Value::new_gc(Gc::new(
mc,
crate::List {
inner: RefLock::new(items),
},
)));
Step::Continue(()) Step::Continue(())
} }
@@ -224,10 +229,10 @@ impl<'gc> crate::Vm<'gc> {
return Ok(a == b); return Ok(a == b);
} }
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) { if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
if a.inner.len() != b.inner.len() { if a.inner.borrow().len() != b.inner.borrow().len() {
return Ok(false); 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 lx = x.restrict().expect("forced");
let ly = y.restrict().expect("forced"); let ly = y.restrict().expect("forced");
if !self.values_equal(ctx, lx, ly)? { if !self.values_equal(ctx, lx, ly)? {
+111
View File
@@ -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::<List>() {
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::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let elem = self.peek_forced(2).as_gc::<List>().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::<bool>(reader, mc)?;
#[allow(clippy::unwrap_used)]
let idx = self.peek(1).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = self.peek_forced(2).as_gc::<List>().unwrap();
let list = list.inner.borrow();
#[allow(clippy::unwrap_used)]
let acc = self.peek_forced(0).as_gc::<List>().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(())
}
}
+61 -69
View File
@@ -8,18 +8,18 @@ use crate::{
impl<'gc> crate::Vm<'gc> { impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_call( pub(crate) fn call(
&mut self, &mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
arg: Value<'gc>,
resume_pc: usize,
) -> Step { ) -> Step {
let func = self.try_force::<StrictValue>(reader, mc)?; let func = self.try_force::<StrictValue>(reader, mc)?;
if self.call_depth > 10000 { if self.call_depth > 10000 {
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded")); return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
} }
self.call_depth += 1; self.call_depth += 1;
let arg = reader.read_operand_data(ctx).resolve(mc, self);
if let Some(closure) = func.as_gc::<Closure>() { if let Some(closure) = func.as_gc::<Closure>() {
let ip = closure.ip; let ip = closure.ip;
let n_locals = closure.n_locals; let n_locals = closure.n_locals;
@@ -29,7 +29,7 @@ impl<'gc> crate::Vm<'gc> {
} else { } else {
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame { self.call_stack.push(CallFrame {
pc: reader.pc(), pc: resume_pc,
stack_depth: 0, stack_depth: 0,
thunk: None, thunk: None,
env: self.env, env: self.env,
@@ -38,12 +38,64 @@ impl<'gc> crate::Vm<'gc> {
reader.set_pc(ip as usize); reader.set_pc(ip as usize);
self.env = new_env; self.env = new_env;
} }
} else if let Some(primop) = func.as_inline::<PrimOp>() {
if primop.arity == 1 {
self.push(arg);
self.call_stack.push(CallFrame {
pc: resume_pc,
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::<PrimOpApp>() {
if app.arity == 1 {
for i in 0..app.primop.arity - 1 {
self.push(app.args[i as usize]);
}
self.push(arg);
self.call_stack.push(CallFrame {
pc: resume_pc,
stack_depth: 0,
thunk: None,
env: self.env,
with_env: self.with_env,
});
reader.set_pc(app.primop.dispatch_ip as usize)
} else {
let new_app = PrimOpApp {
arity: app.arity - 1,
..*app
};
self.push(Value::new_gc(Gc::new(mc, new_app)))
}
} else { } else {
todo!("call other types: {func:?}") todo!("call other types: {func:?}")
} }
Step::Continue(()) 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)] #[inline(always)]
pub(crate) fn op_return( pub(crate) fn op_return(
&mut self, &mut self,
@@ -51,16 +103,7 @@ impl<'gc> crate::Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
self.handle_return(reader, ctx, mc) let val = self.try_force::<StrictValue>(reader, mc)?;
}
pub(crate) fn handle_return<C: VmRuntimeCtx>(
&mut self,
reader: &mut BytecodeReader<'_>,
ctx: &C,
mc: &Mutation<'gc>,
) -> Step {
let ret_inst_pc = reader.pc() - 1;
let Some(CallFrame { let Some(CallFrame {
pc: ret_pc, pc: ret_pc,
stack_depth, stack_depth,
@@ -69,66 +112,15 @@ impl<'gc> crate::Vm<'gc> {
with_env, with_env,
}) = self.call_stack.pop() }) = self.call_stack.pop()
else { else {
let val = self.pop(); return self.finish_ok(ctx.convert_value(val.relax()));
return self.finish_ok(ctx.convert_value(val));
}; };
reader.set_pc(ret_pc); reader.set_pc(ret_pc);
if let Some(outer_thunk) = thunk { if let Some(outer_thunk) = thunk {
let val = self.pop(); *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
match val.restrict() { self.replace(stack_depth, val.relax());
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"));
}
}
}
}
} else { } else {
self.call_depth -= 1; self.call_depth -= 1;
self.push(val.relax())
} }
self.env = env; self.env = env;
self.with_env = with_env; self.with_env = with_env;
+7 -2
View File
@@ -1,6 +1,6 @@
use fix_common::StringId; use fix_common::StringId;
use fix_error::Error; use fix_error::Error;
use gc_arena::Gc; use gc_arena::{Gc, RefLock};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::value::NixType; use crate::value::NixType;
@@ -298,7 +298,12 @@ impl<'gc> crate::Vm<'gc> {
for _ in 0..count { for _ in 0..count {
items.push(reader.read_operand_data(ctx).resolve(mc, self)); 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)); self.push(Value::new_gc(list));
Step::Continue(()) Step::Continue(())
} }
@@ -17,6 +17,7 @@ impl<'gc> crate::Vm<'gc> {
self.push(Value::new_inline(PrimOp { self.push(Value::new_inline(PrimOp {
id, id,
arity: fix_builtins::BUILTINS[id as usize].1, arity: fix_builtins::BUILTINS[id as usize].1,
dispatch_ip: id.entry_phase().ip(),
})); }));
Step::Continue(()) Step::Continue(())
} }
+2 -1
View File
@@ -1,9 +1,10 @@
pub(crate) mod arithmetic; pub(crate) mod arithmetic;
pub(crate) mod builtins_misc; pub(crate) mod builtins;
pub(crate) mod calls; pub(crate) mod calls;
pub(crate) mod closures; pub(crate) mod closures;
pub(crate) mod collections; pub(crate) mod collections;
pub(crate) mod control; pub(crate) mod control;
pub(crate) mod literals; pub(crate) mod literals;
pub(crate) mod misc;
pub(crate) mod variables; pub(crate) mod variables;
pub(crate) mod with_scope; pub(crate) mod with_scope;
+42 -16
View File
@@ -142,6 +142,7 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
} else if let Some(list) = val.as_gc::<List>() { } else if let Some(list) = val.as_gc::<List>() {
let items: Vec<_> = list let items: Vec<_> = list
.inner .inner
.borrow()
.iter() .iter()
.copied() .copied()
.map(|v| self.convert_value(v)) .map(|v| self.convert_value(v))
@@ -151,10 +152,12 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
Value::Func Value::Func
} else if val.is::<Thunk>() { } else if val.is::<Thunk>() {
Value::Thunk Value::Thunk
} else if val.as_inline::<PrimOp>().is_some() { } else if let Some(primop) = val.as_inline::<PrimOp>() {
Value::PrimOp("primop".into()) let name = fix_builtins::BUILTINS[primop.id as usize].0;
} else if val.is::<PrimOpApp>() { Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
Value::PrimOpApp("primop-app".into()) } else if let Some(app) = val.as_gc::<PrimOpApp>() {
let name = fix_builtins::BUILTINS[app.primop.id as usize].0;
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
} else { } else {
Value::Null 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 id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
let name = name.strip_prefix("__").unwrap_or(name); let name = name.strip_prefix("__").unwrap_or(name);
let name = ctx.intern_string(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 = [ let consts = [
@@ -248,15 +259,7 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
"__storeDir", "__storeDir",
Value::new_inline(ctx.intern_string("/nix/store")), Value::new_inline(ctx.intern_string("/nix/store")),
), ),
( ("__nixPath", Value::new_gc(Gc::new(mc, List::default()))),
"__nixPath",
Value::new_gc(Gc::new(
mc,
List {
inner: SmallVec::new(),
},
)),
),
("null", Value::new_inline(Null)), ("null", Value::new_inline(Null)),
("true", Value::new_inline(true)), ("true", Value::new_inline(true)),
("false", Value::new_inline(false)), ("false", Value::new_inline(false)),
@@ -382,16 +385,38 @@ impl<'gc> Vm<'gc> {
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> std::ops::ControlFlow<Break, T> { ) -> std::ops::ControlFlow<Break, T> {
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<T: Forced<'gc>>(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
resume_pc: usize,
) -> std::ops::ControlFlow<Break, T> {
T::force_and_check(self, reader, mc, 0, resume_pc)?;
std::ops::ControlFlow::Continue(T::pop_converted(self)) std::ops::ControlFlow::Continue(T::pop_converted(self))
} }
#[inline(always)] #[inline(always)]
#[allow(unused)]
pub(crate) fn force_slot( pub(crate) fn force_slot(
&mut self, &mut self,
depth: usize, depth: usize,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, 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 { ) -> Step {
let Some(thunk) = self.peek(depth).as_gc::<Thunk>() else { let Some(thunk) = self.peek(depth).as_gc::<Thunk>() else {
return Step::Continue(()); return Step::Continue(());
@@ -404,7 +429,7 @@ impl<'gc> Vm<'gc> {
self.call_stack.push(CallFrame { self.call_stack.push(CallFrame {
thunk: Some(thunk), thunk: Some(thunk),
stack_depth: depth, stack_depth: depth,
pc: reader.inst_start_pc(), pc: resume_pc,
env: self.env, env: self.env,
with_env: self.with_env, with_env: self.with_env,
}); });
@@ -554,6 +579,7 @@ impl<'gc> Vm<'gc> {
MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc), MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc),
Call => self.op_call(ctx, &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), Return => self.op_return(ctx, &mut reader, mc),
MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc), MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc),
+36 -10
View File
@@ -1,5 +1,6 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::cell::RefCell;
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::size_of; use std::mem::size_of;
@@ -7,6 +8,7 @@ use std::ops::Deref;
use fix_builtins::BuiltinId; use fix_builtins::BuiltinId;
use fix_common::*; use fix_common::*;
use gc_arena::barrier::Unlock;
use gc_arena::collect::Trace; use gc_arena::collect::Trace;
use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock}; use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock};
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
@@ -345,8 +347,12 @@ impl StaticValue {
Self(Value::new_inline(val)) Self(Value::new_inline(val))
} }
#[inline] #[inline]
pub fn new_primop(id: BuiltinId, arity: u8) -> Self { pub fn new_primop(id: BuiltinId, arity: u8, dispatch_ip: u32) -> Self {
Self(Value::new_inline(PrimOp { id, arity })) Self(Value::new_inline(PrimOp {
id,
arity,
dispatch_ip,
}))
} }
#[inline] #[inline]
pub fn is_float(self) -> bool { pub fn is_float(self) -> bool {
@@ -487,14 +493,22 @@ impl<'gc> AttrSet<'gc> {
} }
#[derive(Collect, Debug, Default)] #[derive(Collect, Debug, Default)]
#[repr(transparent)]
#[collect(no_drop)] #[collect(no_drop)]
pub(crate) struct List<'gc> { pub(crate) struct List<'gc> {
pub(crate) inner: SmallVec<[Value<'gc>; 4]>, pub(crate) inner: RefLock<SmallVec<[Value<'gc>; 4]>>,
} }
impl<'gc> Deref for List<'gc> {
type Target = SmallVec<[Value<'gc>; 4]>; impl<'gc> List<'gc> {
fn deref(&self) -> &Self::Target { pub(crate) fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> {
&self.inner Gc::new(mc, Self::default())
}
}
impl<'gc> Unlock for List<'gc> {
type Unlocked = RefCell<SmallVec<[Value<'gc>; 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)]>, pub(crate) param_spans: Box<[(StringId, u32)]>,
} }
#[repr(packed, Rust)]
#[derive(Clone, Copy, Debug, Collect)] #[derive(Clone, Copy, Debug, Collect)]
#[collect(require_static)] #[collect(require_static)]
pub(crate) struct PrimOp { pub(crate) struct PrimOp {
pub(crate) id: BuiltinId, pub(crate) id: BuiltinId,
pub(crate) arity: u8, pub(crate) arity: u8,
pub(crate) dispatch_ip: u32,
} }
impl RawStore for PrimOp { impl RawStore for PrimOp {
fn to_val(self, value: &mut RawValue) { 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 { fn from_val(value: &RawValue) -> Self {
let [.., id, arity] = *value.data(); let [id, arity, bytes @ ..] = *value.data();
Self { Self {
id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"), id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"),
arity, arity,
dispatch_ip: u32::from_le_bytes(bytes),
} }
} }
} }
@@ -596,7 +621,8 @@ impl RawStore for PrimOp {
#[collect(no_drop)] #[collect(no_drop)]
pub(crate) struct PrimOpApp<'gc> { pub(crate) struct PrimOpApp<'gc> {
pub(crate) primop: PrimOp, 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)] #[derive(Copy, Clone, Default, Collect)]
+1
View File
@@ -33,6 +33,7 @@ rnix = { workspace = true }
ere = { workspace = true } ere = { workspace = true }
ghost-cell = { workspace = true } ghost-cell = { workspace = true }
fix-builtins = { path = "../fix-builtins" }
fix-common = { path = "../fix-common" } fix-common = { path = "../fix-common" }
fix-codegen = { path = "../fix-codegen" } fix-codegen = { path = "../fix-codegen" }
fix-error = { path = "../fix-error" } fix-error = { path = "../fix-error" }
+13 -3
View File
@@ -2,8 +2,9 @@
#![allow(dead_code)] #![allow(dead_code)]
use bumpalo::Bump; use bumpalo::Bump;
use fix_builtins::PrimOpPhase;
use fix_codegen::disassembler::{Disassembler, DisassemblerContext}; use fix_codegen::disassembler::{Disassembler, DisassemblerContext};
use fix_codegen::{BytecodeContext, InstructionPtr}; use fix_codegen::{BytecodeContext, InstructionPtr, Op};
use fix_common::{StringId, Symbol}; use fix_common::{StringId, Symbol};
use fix_error::{Error, Result, Source}; use fix_error::{Error, Result, Source};
use fix_ir::downgrade::{Downgrade as _, DowngradeContext}; use fix_ir::downgrade::{Downgrade as _, DowngradeContext};
@@ -47,6 +48,11 @@ impl Evaluator {
pub fn new() -> Self { pub fn new() -> Self {
let mut strings = DefaultStringInterner::new(); let mut strings = DefaultStringInterner::new();
let global_env = fix_ir::new_global_env(&mut strings); 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 { Self {
runtime: RuntimeState { runtime: RuntimeState {
strings, strings,
@@ -56,7 +62,7 @@ impl Evaluator {
sources: Vec::new(), sources: Vec::new(),
spans: Vec::new(), spans: Vec::new(),
thunk_count: 0, thunk_count: 0,
bytecode: Vec::new(), bytecode,
global_env, global_env,
}, },
} }
@@ -239,7 +245,11 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
Float(x) => StaticValue::new_float(x), Float(x) => StaticValue::new_float(x),
Bool(x) => StaticValue::new_inline(x), Bool(x) => StaticValue::new_inline(x),
String(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(), Null => StaticValue::default(),
}; };
self.runtime.add_const(val) self.runtime.add_const(val)