Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
bc16596dd3
|
|||
|
ac76d4a9e4
|
|||
|
d77dcc8929
|
|||
|
4f3cd0ef4c
|
|||
|
468269c20d
|
Generated
+1
@@ -449,6 +449,7 @@ dependencies = [
|
||||
"clap",
|
||||
"criterion",
|
||||
"ere",
|
||||
"fix-builtins",
|
||||
"fix-codegen",
|
||||
"fix-common",
|
||||
"fix-error",
|
||||
|
||||
+239
-1
@@ -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,242 @@ 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,
|
||||
DeepSeqPush,
|
||||
DeepSeqLoop,
|
||||
|
||||
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,
|
||||
|
||||
ForceResultShallow,
|
||||
ForceResultShallowPush,
|
||||
ForceResultShallowLoop,
|
||||
ForceResultDeepFinish,
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -3,7 +3,7 @@ use fix_common::StringId;
|
||||
use num_enum::TryFromPrimitive;
|
||||
use string_interner::Symbol as _;
|
||||
|
||||
use crate::OperandData;
|
||||
use crate::{OperandData, VmRuntimeCtx};
|
||||
|
||||
pub(crate) struct BytecodeReader<'a> {
|
||||
bytecode: &'a [u8],
|
||||
@@ -94,7 +94,7 @@ impl<'a> BytecodeReader<'a> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn read_operand_data<C: crate::VmContext>(&mut self, ctx: &C) -> OperandData {
|
||||
pub(crate) fn read_operand_data<C: VmRuntimeCtx>(&mut self, ctx: &C) -> OperandData {
|
||||
let tag = self.read_u8();
|
||||
let Ok(ty) = OperandType::try_from_primitive(tag)
|
||||
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
|
||||
@@ -117,10 +117,7 @@ impl<'a> BytecodeReader<'a> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn read_attr_key_data<C: crate::VmContext>(
|
||||
&mut self,
|
||||
ctx: &C,
|
||||
) -> crate::AttrKeyData {
|
||||
pub(crate) fn read_attr_key_data<C: VmRuntimeCtx>(&mut self, ctx: &C) -> crate::AttrKeyData {
|
||||
use fix_codegen::AttrKeyType;
|
||||
let tag = self.read_u8();
|
||||
let ty = AttrKeyType::try_from_primitive(tag)
|
||||
@@ -142,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use gc_arena::Mutation;
|
||||
|
||||
use crate::{Break, BytecodeReader, Step, Vm, VmContext};
|
||||
use crate::{Break, BytecodeReader, Step, Vm, VmRuntimeCtx};
|
||||
|
||||
pub(crate) enum TailResult {
|
||||
YieldFuel(u32),
|
||||
@@ -19,9 +19,9 @@ pub(crate) type OpFn<'gc, C> = extern "rust-preserve-none" fn(
|
||||
u32,
|
||||
) -> TailResult;
|
||||
|
||||
pub(crate) struct DispatchTable<'gc, C: VmContext>(pub(crate) [OpFn<'gc, C>; 256]);
|
||||
pub(crate) struct DispatchTable<'gc, C: VmRuntimeCtx>(pub(crate) [OpFn<'gc, C>; 256]);
|
||||
|
||||
extern "rust-preserve-none" fn op_illegal<'gc, C: VmContext>(
|
||||
extern "rust-preserve-none" fn op_illegal<'gc, C: VmRuntimeCtx>(
|
||||
_vm: &mut Vm<'gc>,
|
||||
_mc: &Mutation<'gc>,
|
||||
_ctx: &mut C,
|
||||
@@ -50,7 +50,7 @@ macro_rules! tail_dispatch_after {
|
||||
|
||||
macro_rules! tail_fn {
|
||||
($name:ident, ()) => {
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmRuntimeCtx>(
|
||||
vm: &mut Vm<'gc>,
|
||||
mc: &Mutation<'gc>,
|
||||
ctx: &mut C,
|
||||
@@ -64,7 +64,7 @@ macro_rules! tail_fn {
|
||||
}
|
||||
};
|
||||
($name:ident, (reader)) => {
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmRuntimeCtx>(
|
||||
vm: &mut Vm<'gc>,
|
||||
mc: &Mutation<'gc>,
|
||||
ctx: &mut C,
|
||||
@@ -79,7 +79,7 @@ macro_rules! tail_fn {
|
||||
}
|
||||
};
|
||||
($name:ident, (reader, mc)) => {
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmRuntimeCtx>(
|
||||
vm: &mut Vm<'gc>,
|
||||
mc: &Mutation<'gc>,
|
||||
ctx: &mut C,
|
||||
@@ -94,7 +94,7 @@ macro_rules! tail_fn {
|
||||
}
|
||||
};
|
||||
($name:ident, (ctx, reader, mc)) => {
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmRuntimeCtx>(
|
||||
vm: &mut Vm<'gc>,
|
||||
mc: &Mutation<'gc>,
|
||||
ctx: &mut C,
|
||||
@@ -109,7 +109,7 @@ macro_rules! tail_fn {
|
||||
}
|
||||
};
|
||||
($name:ident, (ctx)) => {
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmContext>(
|
||||
extern "rust-preserve-none" fn $name<'gc, C: VmRuntimeCtx>(
|
||||
vm: &mut Vm<'gc>,
|
||||
mc: &Mutation<'gc>,
|
||||
ctx: &mut C,
|
||||
@@ -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));
|
||||
@@ -198,7 +199,7 @@ tail_fn!(op_load_scoped_binding, (reader));
|
||||
|
||||
macro_rules! table {
|
||||
($($variant:ident => $fn:ident),* $(,)?) => {
|
||||
impl<'gc, C: VmContext> DispatchTable<'gc, C> {
|
||||
impl<'gc, C: VmRuntimeCtx> DispatchTable<'gc, C> {
|
||||
pub(crate) const NEW: Self = {
|
||||
let mut arr: [OpFn<'gc, C>; 256] = [op_illegal; 256];
|
||||
$( arr[fix_codegen::Op::$variant as usize] = $fn; )*
|
||||
@@ -234,6 +235,7 @@ table! {
|
||||
MakePatternClosure => op_make_pattern_closure,
|
||||
|
||||
Call => op_call,
|
||||
DispatchPrimOp => op_dispatch_primop,
|
||||
Return => op_return,
|
||||
|
||||
MakeAttrs => op_make_attrs,
|
||||
@@ -291,7 +293,7 @@ table! {
|
||||
Illegal => op_illegal,
|
||||
}
|
||||
|
||||
pub(crate) fn run_tailcall<'gc, C: VmContext>(
|
||||
pub(crate) fn run_tailcall<'gc, C: VmRuntimeCtx>(
|
||||
vm: &mut Vm<'gc>,
|
||||
mc: &Mutation<'gc>,
|
||||
ctx: &mut C,
|
||||
|
||||
+29
-14
@@ -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)]
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use gc_arena::{Gc, Mutation};
|
||||
use gc_arena::{Gc, Mutation, RefLock};
|
||||
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, NixNum, Step, VmContextExt, VmError};
|
||||
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_add(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -61,7 +61,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
|
||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
match (get_num(rhs), get_num(lhs)) {
|
||||
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",
|
||||
@@ -82,7 +82,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_eq(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -98,7 +98,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_neq(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -114,7 +114,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_lt(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -124,7 +124,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_gt(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -134,7 +134,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_leq(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -144,7 +144,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_geq(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -153,7 +153,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
|
||||
fn compare_values(
|
||||
&mut self,
|
||||
ctx: &impl crate::VmContext,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
pred: fn(Ordering) -> bool,
|
||||
@@ -173,9 +173,14 @@ impl<'gc> crate::Vm<'gc> {
|
||||
) -> Step {
|
||||
let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(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(())
|
||||
}
|
||||
|
||||
@@ -202,7 +207,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
|
||||
pub(crate) fn values_equal(
|
||||
&mut self,
|
||||
ctx: &impl crate::VmContext,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
lhs: StrictValue<'gc>,
|
||||
rhs: StrictValue<'gc>,
|
||||
) -> crate::VmResult<bool> {
|
||||
@@ -224,10 +229,10 @@ impl<'gc> crate::Vm<'gc> {
|
||||
return Ok(a == b);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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)? {
|
||||
@@ -257,7 +262,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
|
||||
fn compare_values_inner(
|
||||
&mut self,
|
||||
ctx: &impl crate::VmContext,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
pred: fn(Ordering) -> bool,
|
||||
lhs: StrictValue<'gc>,
|
||||
rhs: StrictValue<'gc>,
|
||||
@@ -276,10 +281,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.push(Value::new_inline(pred(ord)));
|
||||
return Ok(());
|
||||
}
|
||||
if let (Some(a), Some(b)) = (
|
||||
VmContextExt::get_string(ctx, lhs),
|
||||
VmContextExt::get_string(ctx, rhs),
|
||||
) {
|
||||
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(());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Gc, Mutation};
|
||||
use num_enum::TryFromPrimitive as _;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||
|
||||
impl<'gc> Vm<'gc> {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
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 {
|
||||
Abort => self.primop_abort(ctx, reader, mc),
|
||||
|
||||
DeepSeq => self.primop_deep_seq_force_top(reader, mc),
|
||||
DeepSeqPush => self.primop_deep_seq_push(reader, mc),
|
||||
DeepSeqLoop => self.primop_deep_seq_loop(reader, mc),
|
||||
Seq => self.primop_seq(reader, mc),
|
||||
|
||||
FilterForceList => self.primop_filter_force_list(reader, mc),
|
||||
FilterCallPred => self.primop_filter_call_pred(reader, mc),
|
||||
FilterCheck => self.primop_filter_check(reader, mc),
|
||||
|
||||
ForceResultShallow => self.primop_force_result_shallow(ctx, reader, mc),
|
||||
ForceResultShallowPush => self.primop_force_result_shallow_push(ctx, reader, mc),
|
||||
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
|
||||
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
|
||||
|
||||
phase => todo!("primop phase {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(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_seq(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
self.force_slot(1, reader, mc)?;
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
self.return_from_primop(reader)
|
||||
}
|
||||
|
||||
pub(crate) fn primop_abort(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [msg] - force msg, then abort with it
|
||||
self.force_slot(0, reader, mc)?;
|
||||
let msg_val = self.peek_forced(0);
|
||||
let msg = ctx.get_string(msg_val).unwrap_or("<non-string-value>");
|
||||
self.finish_err(Error::eval_error(format!(
|
||||
"evaluation aborted with the following error message: '{msg}'"
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_force_top(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
self.force_slot(1, reader, mc)?;
|
||||
|
||||
let e1 = self.peek_forced(1);
|
||||
|
||||
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
||||
if attrs.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
attrs.iter().map(|&(_, v)| v).collect()
|
||||
}
|
||||
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
|
||||
let inner = list.inner.borrow();
|
||||
if inner.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
inner.iter().copied().collect()
|
||||
}
|
||||
} else {
|
||||
SmallVec::new()
|
||||
};
|
||||
|
||||
if children.is_empty() {
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
return self.return_from_primop(reader);
|
||||
}
|
||||
|
||||
let count = children.len() as i32;
|
||||
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default());
|
||||
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children);
|
||||
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
self.push(Value::new_gc(seen));
|
||||
self.push(Value::new_gc(worklist));
|
||||
self.push(Value::new_inline(count));
|
||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_push(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e2, seen, worklist, counter]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
||||
if counter == 0 {
|
||||
let _ = self.pop(); // counter
|
||||
let _ = self.pop(); // worklist
|
||||
let _ = self.pop(); // seen
|
||||
return self.return_from_primop(reader);
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
|
||||
self.replace(0, Value::new_inline(counter - 1));
|
||||
self.push(item);
|
||||
|
||||
// force item at TOS, resume at DeepSeqLoop after force
|
||||
self.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
|
||||
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_loop(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack after pop: [e2, seen, worklist, counter]
|
||||
let item = self.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
||||
|
||||
let mut added: usize = 0;
|
||||
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||
if !self.is_value_in_seen(seen, item) {
|
||||
self.add_value_to_seen(seen, mc, item);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
{
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &(_, v) in attrs.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = attrs.len();
|
||||
}
|
||||
}
|
||||
} else if let Some(list) = item.as_gc::<List<'gc>>() {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||
if !self.is_value_in_seen(seen, item) {
|
||||
self.add_value_to_seen(seen, mc, item);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
{
|
||||
let inner = list.inner.borrow();
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &v in inner.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = inner.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.replace(0, Value::new_inline(counter + added as i32));
|
||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_force_result_shallow(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
self.force_slot(0, reader, mc)?;
|
||||
let val = self.peek_forced(0);
|
||||
|
||||
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
let len = attrs.len();
|
||||
(len, len > 0)
|
||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||
let len = list.inner.borrow().len();
|
||||
(len, len > 0)
|
||||
} else {
|
||||
(0, false)
|
||||
};
|
||||
|
||||
if !has_children {
|
||||
let val = self.pop();
|
||||
return self.finish_ok(ctx.convert_value(val));
|
||||
}
|
||||
|
||||
self.push(Value::new_inline(0i32));
|
||||
self.push(Value::new_inline(count as i32));
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_force_result_shallow_push(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let len = self.peek(0).as_inline::<i32>().unwrap();
|
||||
|
||||
if idx == len {
|
||||
let _ = self.pop(); // len
|
||||
let _ = self.pop(); // idx
|
||||
let val = self.pop();
|
||||
return self.finish_ok(ctx.convert_value(val));
|
||||
}
|
||||
|
||||
let val = self.peek_forced(2);
|
||||
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
attrs.get(idx as usize).map(|&(_, v)| v)
|
||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||
list.inner.borrow().get(idx as usize).copied()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(child) = child {
|
||||
self.replace(1, Value::new_inline(idx + 1));
|
||||
self.push(child);
|
||||
self.force_slot_to_pc(
|
||||
0,
|
||||
reader,
|
||||
mc,
|
||||
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
|
||||
)?;
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
|
||||
}
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_force_result_shallow_loop(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let _ = self.pop(); // forced child
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_force_result_deep_finish(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = self.try_force::<StrictValue>(reader, mc)?;
|
||||
self.finish_ok(ctx.convert_value(val.relax()))
|
||||
}
|
||||
|
||||
fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
|
||||
if !is_container(val) {
|
||||
return false;
|
||||
}
|
||||
let target = val.to_bits();
|
||||
for &v in seen.inner.borrow().iter() {
|
||||
if v.to_bits() == target {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn add_value_to_seen(&self, seen: Gc<'gc, List<'gc>>, mc: &Mutation<'gc>, val: Value<'gc>) {
|
||||
if is_container(val) {
|
||||
seen.unlock(mc).borrow_mut().push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_container(val: Value<'_>) -> bool {
|
||||
val.is::<AttrSet>() || val.is::<List<'_>>()
|
||||
}
|
||||
@@ -1,23 +1,27 @@
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Gc, Mutation, RefLock};
|
||||
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, CallFrame, Closure, Env, Step, ThunkState, VmContextExt};
|
||||
use crate::{
|
||||
BytecodeReader, CallFrame, Closure, Env, ForceMode, Step, ThunkState, VmRuntimeCtx,
|
||||
VmRuntimeCtxExt,
|
||||
};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_call(
|
||||
pub(crate) fn call(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
arg: Value<'gc>,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
let func = self.try_force::<StrictValue>(reader, mc)?;
|
||||
if self.call_depth > 10000 {
|
||||
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
||||
}
|
||||
self.call_depth += 1;
|
||||
let arg = reader.read_operand_data(ctx).resolve(mc, self);
|
||||
if let Some(closure) = func.as_gc::<Closure>() {
|
||||
let ip = closure.ip;
|
||||
let n_locals = closure.n_locals;
|
||||
@@ -27,7 +31,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,
|
||||
@@ -36,6 +40,46 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader.set_pc(ip as usize);
|
||||
self.env = new_env;
|
||||
}
|
||||
} else if let Some(primop) = func.as_inline::<PrimOp>() {
|
||||
if primop.arity == 1 {
|
||||
self.push(arg);
|
||||
self.call_stack.push(CallFrame {
|
||||
pc: resume_pc,
|
||||
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 {
|
||||
todo!("call other types: {func:?}")
|
||||
}
|
||||
@@ -43,22 +87,25 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_return(
|
||||
pub(crate) fn op_call(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
self.handle_return(reader, ctx, mc)
|
||||
let arg = reader.read_operand_data(ctx).resolve(mc, self);
|
||||
let pc = reader.pc();
|
||||
self.call(reader, mc, arg, pc)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_return<C: crate::VmContext>(
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_return(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
ctx: &C,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let ret_inst_pc = reader.pc() - 1;
|
||||
let val = self.try_force::<StrictValue>(reader, mc)?;
|
||||
let Some(CallFrame {
|
||||
pc: ret_pc,
|
||||
stack_depth,
|
||||
@@ -67,66 +114,36 @@ impl<'gc> crate::Vm<'gc> {
|
||||
with_env,
|
||||
}) = self.call_stack.pop()
|
||||
else {
|
||||
let val = self.pop();
|
||||
return self.finish_ok(ctx.convert_value(val));
|
||||
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,
|
||||
stack_depth: 0,
|
||||
thunk: None,
|
||||
env: self.env,
|
||||
with_env: self.with_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 {
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.replace(stack_depth, val.relax());
|
||||
} else {
|
||||
self.call_depth -= 1;
|
||||
self.push(val.relax())
|
||||
}
|
||||
self.env = env;
|
||||
self.with_env = with_env;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use gc_arena::Gc;
|
||||
use gc_arena::{Gc, RefLock};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::value::NixType;
|
||||
use crate::{
|
||||
AttrKeyData, AttrSet, BytecodeReader, List, OperandData, Step, StrictValue, Value, VmContextExt,
|
||||
AttrKeyData, AttrSet, BytecodeReader, List, OperandData, Step, StrictValue, Value,
|
||||
VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_make_attrs(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -52,7 +53,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_select_static(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -73,7 +74,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_select_dynamic(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -102,7 +103,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
fn select_skip(
|
||||
&mut self,
|
||||
key: StringId,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
) -> Step {
|
||||
use fix_codegen::Op::*;
|
||||
@@ -168,7 +169,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_has_attr_path_static(
|
||||
&mut self,
|
||||
_ctx: &mut impl crate::VmContext,
|
||||
_ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -192,7 +193,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_has_attr_path_dynamic(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -255,7 +256,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_has_attr_dynamic(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -288,7 +289,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_make_list(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -297,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(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use fix_builtins::BuiltinId;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::{BytecodeReader, PrimOp, Step, Value};
|
||||
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -42,7 +43,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_concat_strings(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
_mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -57,7 +58,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl crate::VmContext) -> Step {
|
||||
pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl VmRuntimeCtx) -> Step {
|
||||
todo!("implement ResolvePath");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -3,13 +3,13 @@ use fix_error::Error;
|
||||
use gc_arena::Gc;
|
||||
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, CallFrame, Step, WithEnv};
|
||||
use crate::{BytecodeReader, CallFrame, Step, VmRuntimeCtx, WithEnv};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_push_with(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
@@ -49,7 +49,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_lookup_with(
|
||||
&mut self,
|
||||
ctx: &mut impl crate::VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
|
||||
+106
-38
@@ -13,7 +13,7 @@ use fix_common::StringId;
|
||||
use fix_error::{Error, Result, Source};
|
||||
use gc_arena::arena::CollectionPhase;
|
||||
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
||||
use hashbrown::HashMap;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@@ -64,15 +64,26 @@ pub enum ForceMode {
|
||||
}
|
||||
|
||||
pub trait VmContext {
|
||||
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId;
|
||||
fn resolve_string(&self, id: StringId) -> &str;
|
||||
fn bytecode(&self) -> &[u8];
|
||||
fn get_const(&self, id: u32) -> StaticValue;
|
||||
|
||||
fn compile(&mut self, source: Source);
|
||||
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx);
|
||||
}
|
||||
|
||||
pub(crate) trait VmContextExt: VmContext {
|
||||
pub trait VmRuntimeCtx {
|
||||
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId;
|
||||
fn resolve_string(&self, id: StringId) -> &str;
|
||||
fn get_const(&self, id: u32) -> StaticValue;
|
||||
fn add_const(&mut self, val: StaticValue) -> u32;
|
||||
}
|
||||
|
||||
pub trait VmCode {
|
||||
fn bytecode(&self) -> &[u8];
|
||||
fn compile(
|
||||
&mut self,
|
||||
source: Source,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
) -> fix_error::Result<InstructionPtr>;
|
||||
}
|
||||
|
||||
pub(crate) trait VmRuntimeCtxExt: VmRuntimeCtx {
|
||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
||||
fn get_string_id<'a, 'gc: 'a>(
|
||||
&'a mut self,
|
||||
@@ -81,7 +92,7 @@ pub(crate) trait VmContextExt: VmContext {
|
||||
fn convert_value(&self, val: Value) -> fix_common::Value;
|
||||
}
|
||||
|
||||
impl<T: VmContext> VmContextExt for T {
|
||||
impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
||||
if let Some(sid) = val.as_inline::<StringId>() {
|
||||
Some(self.resolve_string(sid))
|
||||
@@ -104,6 +115,16 @@ impl<T: VmContext> VmContextExt for T {
|
||||
}
|
||||
|
||||
fn convert_value(&self, val: Value) -> fix_common::Value {
|
||||
self.convert_value_with_seen(val, &mut HashSet::new())
|
||||
}
|
||||
}
|
||||
|
||||
trait ConvertValueWithSeen: VmRuntimeCtx {
|
||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value;
|
||||
}
|
||||
|
||||
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value {
|
||||
use fix_common::Value;
|
||||
if let Some(i) = val.as_inline::<i32>() {
|
||||
Value::Int(i as i64)
|
||||
@@ -121,29 +142,50 @@ impl<T: VmContext> VmContextExt for T {
|
||||
} else if let Some(ns) = val.as_gc::<NixString>() {
|
||||
Value::String(ns.as_str().to_owned())
|
||||
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
let bits = val.to_bits();
|
||||
if attrs.is_empty() {
|
||||
return Value::AttrSet(Default::default());
|
||||
}
|
||||
if !seen.insert(bits) {
|
||||
return Value::Repeated;
|
||||
}
|
||||
let mut map = std::collections::BTreeMap::new();
|
||||
for &(key, val) in attrs.iter() {
|
||||
let key = self.resolve_string(key).to_owned();
|
||||
let converted = self.convert_value(val);
|
||||
let converted = self.convert_value_with_seen(val, seen);
|
||||
map.insert(fix_common::Symbol::from(key), converted);
|
||||
}
|
||||
Value::AttrSet(fix_common::AttrSet::new(map))
|
||||
} else if let Some(list) = val.as_gc::<List>() {
|
||||
let bits = val.to_bits();
|
||||
if list.inner.borrow().is_empty() {
|
||||
return Value::List(Default::default());
|
||||
}
|
||||
if !seen.insert(bits) {
|
||||
return Value::Repeated;
|
||||
}
|
||||
let items: Vec<_> = list
|
||||
.inner
|
||||
.borrow()
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|v| self.convert_value(v))
|
||||
.map(|v| self.convert_value_with_seen(v, seen))
|
||||
.collect();
|
||||
Value::List(fix_common::List::new(items))
|
||||
} else if val.is::<Closure>() {
|
||||
Value::Func
|
||||
} else if val.is::<Thunk>() {
|
||||
} else if let Some(thunk) = val.as_gc::<Thunk>() {
|
||||
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
||||
self.convert_value_with_seen(v.relax(), seen)
|
||||
} else {
|
||||
Value::Thunk
|
||||
} else if val.as_inline::<PrimOp>().is_some() {
|
||||
Value::PrimOp("primop".into())
|
||||
} else if val.is::<PrimOpApp>() {
|
||||
Value::PrimOpApp("primop-app".into())
|
||||
}
|
||||
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
||||
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::<PrimOpApp>() {
|
||||
let name = fix_builtins::BUILTINS[app.primop.id as usize].0;
|
||||
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
|
||||
} else {
|
||||
Value::Null
|
||||
}
|
||||
@@ -213,14 +255,22 @@ pub(crate) enum AttrKeyData {
|
||||
Dynamic(OperandData),
|
||||
}
|
||||
|
||||
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc> {
|
||||
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
||||
let mut entries = SmallVec::with_capacity(BUILTINS.len());
|
||||
|
||||
for (idx, &(name, arity)) in BUILTINS.iter().enumerate() {
|
||||
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 = [
|
||||
@@ -237,15 +287,7 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc
|
||||
"__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)),
|
||||
@@ -264,11 +306,14 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Value<'gc
|
||||
entries.sort_by_key(|(k, _)| *k);
|
||||
|
||||
let builtins_set = Gc::new(mc, AttrSet::from_sorted_unchecked(entries));
|
||||
Value::new_gc(builtins_set)
|
||||
let builtins_value = Value::new_gc(builtins_set);
|
||||
*self_ref_thunk.borrow_mut(mc) =
|
||||
ThunkState::Evaluated(builtins_value.restrict().expect("builtins is not a thunk"));
|
||||
builtins_value
|
||||
}
|
||||
|
||||
impl<'gc> Vm<'gc> {
|
||||
fn new(force_mode: ForceMode, mc: &Mutation<'gc>, ctx: &mut impl VmContext) -> Self {
|
||||
fn new(force_mode: ForceMode, mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Self {
|
||||
let builtins = init_builtins(mc, ctx);
|
||||
Vm {
|
||||
stack: Vec::with_capacity(8192),
|
||||
@@ -371,16 +416,38 @@ impl<'gc> Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> 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))
|
||||
}
|
||||
|
||||
#[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::<Thunk>() else {
|
||||
return Step::Continue(());
|
||||
@@ -393,7 +460,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,
|
||||
});
|
||||
@@ -444,19 +511,19 @@ pub(crate) enum NixNum {
|
||||
|
||||
impl Vm<'_> {
|
||||
pub fn run<C: VmContext>(
|
||||
mut ctx: C,
|
||||
ctx: &mut C,
|
||||
ip: InstructionPtr,
|
||||
force_mode: ForceMode,
|
||||
) -> Result<fix_common::Value> {
|
||||
let mut arena: Arena<Rootable![Vm<'_>]> =
|
||||
Arena::new(|mc| Vm::new(force_mode, mc, &mut ctx));
|
||||
let (code, runtime) = ctx.split();
|
||||
let mut arena: Arena<Rootable![Vm<'_>]> = Arena::new(|mc| Vm::new(force_mode, mc, runtime));
|
||||
|
||||
const COLLECTOR_GRANULARITY: f64 = 1024.0;
|
||||
|
||||
let mut pc = ip.0;
|
||||
let bytecode: Vec<u8> = ctx.bytecode().to_vec();
|
||||
loop {
|
||||
match arena.mutate_root(|mc, root| root.dispatch_batch(&bytecode, &mut ctx, pc, mc)) {
|
||||
let bytecode = code.bytecode();
|
||||
match arena.mutate_root(|mc, root| root.dispatch_batch(bytecode, runtime, pc, mc)) {
|
||||
Action::Continue { pc: new_pc } => {
|
||||
pc = new_pc;
|
||||
if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
|
||||
@@ -477,7 +544,7 @@ impl<'gc> Vm<'gc> {
|
||||
const DEFAULT_FUEL_AMOUNT: u32 = 1024;
|
||||
|
||||
#[inline(always)]
|
||||
fn dispatch_batch<C: VmContext>(
|
||||
fn dispatch_batch<C: VmRuntimeCtx>(
|
||||
&mut self,
|
||||
bytecode: &[u8],
|
||||
ctx: &mut C,
|
||||
@@ -507,7 +574,7 @@ impl<'gc> Vm<'gc> {
|
||||
fn execute_batch(
|
||||
&mut self,
|
||||
bytecode: &[u8],
|
||||
ctx: &mut impl VmContext,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
pc: usize,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Action {
|
||||
@@ -543,6 +610,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),
|
||||
|
||||
+50
-10
@@ -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;
|
||||
@@ -246,6 +248,11 @@ impl<'gc> Value<'gc> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_bits(self) -> u64 {
|
||||
self.raw.to_bits()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_num(self) -> Option<NixNum> {
|
||||
if let Some(i) = self.as_inline::<i32>() {
|
||||
@@ -345,8 +352,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 +498,31 @@ 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<SmallVec<[Value<'gc>; 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(mc: &Mutation<'gc>, data: SmallVec<[Value<'gc>; 4]>) -> Gc<'gc, Self> {
|
||||
Gc::new(
|
||||
mc,
|
||||
Self {
|
||||
inner: RefLock::new(data),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
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<SmallVec<[Value<'gc>; 4]>>;
|
||||
unsafe fn unlock_unchecked(&self) -> &Self::Unlocked {
|
||||
unsafe { self.inner.unlock_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,22 +600,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 +635,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)]
|
||||
|
||||
@@ -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" }
|
||||
|
||||
+174
-144
@@ -2,39 +2,40 @@
|
||||
#![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};
|
||||
use fix_ir::{Ir, IrRef, MaybeThunk, RawIrRef, ThunkId};
|
||||
use fix_vm::{ForceMode, StaticValue, Vm, VmContext};
|
||||
use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx};
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use string_interner::{DefaultStringInterner, Symbol as _};
|
||||
|
||||
// mod fetcher;
|
||||
// mod nar;
|
||||
// mod nix_utils;
|
||||
// mod store;
|
||||
// mod string_context;
|
||||
mod derivation;
|
||||
pub mod logging;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
pub struct RuntimeState {
|
||||
pub strings: DefaultStringInterner,
|
||||
pub constants: Constants,
|
||||
}
|
||||
|
||||
pub struct CodeState {
|
||||
pub bytecode: Vec<u8>,
|
||||
pub sources: Vec<Source>,
|
||||
pub spans: Vec<(usize, rnix::TextRange)>,
|
||||
pub thunk_count: usize,
|
||||
pub global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
}
|
||||
|
||||
pub struct Evaluator {
|
||||
bytecode: Vec<u8>,
|
||||
constants: Constants,
|
||||
strings: DefaultStringInterner,
|
||||
|
||||
sources: Vec<Source>,
|
||||
spans: Vec<(usize, rnix::TextRange)>,
|
||||
// FIXME: remove?
|
||||
thunk_count: usize,
|
||||
|
||||
global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
pub runtime: RuntimeState,
|
||||
pub code: CodeState,
|
||||
}
|
||||
|
||||
impl Default for Evaluator {
|
||||
@@ -47,15 +48,23 @@ 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,
|
||||
constants: Constants::default(),
|
||||
},
|
||||
code: CodeState {
|
||||
sources: Vec::new(),
|
||||
spans: Vec::new(),
|
||||
strings,
|
||||
thunk_count: 0,
|
||||
bytecode: Vec::new(),
|
||||
constants: Constants::default(),
|
||||
|
||||
bytecode,
|
||||
global_env,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +94,13 @@ impl Evaluator {
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
force_mode: ForceMode,
|
||||
) -> Result<fix_common::Value> {
|
||||
let root = self.downgrade(source, extra_scope)?;
|
||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||
let ip = {
|
||||
let mut compiler = CompilerCtx {
|
||||
code: &mut self.code,
|
||||
runtime: &mut self.runtime,
|
||||
};
|
||||
compiler.compile_bytecode(source, extra_scope)?
|
||||
};
|
||||
Vm::run(self, ip, force_mode)
|
||||
}
|
||||
|
||||
@@ -100,55 +114,81 @@ impl Evaluator {
|
||||
}
|
||||
|
||||
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
|
||||
let root = self.downgrade(source, None)?;
|
||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||
Ok(ip)
|
||||
let mut compiler = CompilerCtx {
|
||||
code: &mut self.code,
|
||||
runtime: &mut self.runtime,
|
||||
};
|
||||
compiler.compile_bytecode(source, None)
|
||||
}
|
||||
|
||||
pub fn disassemble_colored(&self, ip: InstructionPtr) -> String {
|
||||
Disassembler::new(ip, self).disassemble_colored()
|
||||
}
|
||||
}
|
||||
|
||||
fn downgrade_ctx<'a, 'bump, 'id>(
|
||||
&'a mut self,
|
||||
bump: &'bump Bump,
|
||||
token: GhostToken<'id>,
|
||||
extra_scope: Option<Scope<'a>>,
|
||||
) -> DowngradeCtx<'a, 'id, 'bump> {
|
||||
let Self {
|
||||
global_env,
|
||||
sources,
|
||||
thunk_count,
|
||||
strings,
|
||||
..
|
||||
} = self;
|
||||
DowngradeCtx {
|
||||
bump,
|
||||
token,
|
||||
strings,
|
||||
source: sources.last().expect("no current source").clone(),
|
||||
scopes: [Scope::Global(global_env)]
|
||||
.into_iter()
|
||||
.chain(extra_scope)
|
||||
.collect(),
|
||||
with_scope_count: 0,
|
||||
arg_count: 0,
|
||||
thunk_count,
|
||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||
impl VmRuntimeCtx for RuntimeState {
|
||||
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId {
|
||||
StringId(self.strings.get_or_intern(s))
|
||||
}
|
||||
fn resolve_string(&self, id: StringId) -> &str {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.strings.resolve(id.0).unwrap()
|
||||
}
|
||||
fn get_const(&self, id: u32) -> StaticValue {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.constants.get(id).unwrap()
|
||||
}
|
||||
fn add_const(&mut self, val: StaticValue) -> u32 {
|
||||
self.constants.insert(val)
|
||||
}
|
||||
}
|
||||
|
||||
fn downgrade<'a>(
|
||||
&'a mut self,
|
||||
impl VmCode for CodeState {
|
||||
fn bytecode(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
fn compile(
|
||||
&mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope<'a>>,
|
||||
) -> Result<OwnedIr> {
|
||||
runtime: &mut impl VmRuntimeCtx,
|
||||
) -> Result<InstructionPtr> {
|
||||
let mut compiler = CompilerCtx {
|
||||
code: self,
|
||||
runtime,
|
||||
};
|
||||
compiler.compile_bytecode(source, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl VmContext for Evaluator {
|
||||
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx) {
|
||||
(&mut self.code, &mut self.runtime)
|
||||
}
|
||||
}
|
||||
|
||||
struct CompilerCtx<'a, R: VmRuntimeCtx> {
|
||||
code: &'a mut CodeState,
|
||||
runtime: &'a mut R,
|
||||
}
|
||||
|
||||
impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
||||
fn compile_bytecode(
|
||||
&mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope>,
|
||||
) -> Result<InstructionPtr> {
|
||||
let root = self.downgrade(source, extra_scope)?;
|
||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||
Ok(ip)
|
||||
}
|
||||
|
||||
fn downgrade(&mut self, source: Source, extra_scope: Option<Scope>) -> Result<OwnedIr> {
|
||||
tracing::debug!("Parsing Nix expression");
|
||||
|
||||
self.sources.push(source.clone());
|
||||
self.code.sources.push(source.clone());
|
||||
|
||||
let root = rnix::Root::parse(&source.src);
|
||||
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
||||
handle_parse_error(root.errors(), source.clone()).map_or(Ok(()), Err)?;
|
||||
|
||||
tracing::debug!("Downgrading Nix expression");
|
||||
let expr = root
|
||||
@@ -157,38 +197,67 @@ impl Evaluator {
|
||||
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
||||
let bump = Bump::new();
|
||||
GhostToken::new(|token| {
|
||||
let ir = self
|
||||
.downgrade_ctx(&bump, token, extra_scope)
|
||||
.downgrade_toplevel(expr)?;
|
||||
let downgrade_ctx = DowngradeCtx::new(
|
||||
&bump,
|
||||
token,
|
||||
self.runtime,
|
||||
&self.code.global_env,
|
||||
extra_scope,
|
||||
&mut self.code.thunk_count,
|
||||
source,
|
||||
);
|
||||
let ir = downgrade_ctx.downgrade_toplevel(expr)?;
|
||||
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
||||
Ok(OwnedIr { _bump: bump, ir })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl VmContext for &mut Evaluator {
|
||||
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId {
|
||||
StringId(self.strings.get_or_intern(s))
|
||||
}
|
||||
fn resolve_string(&self, id: StringId) -> &str {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.strings.resolve(id.0).unwrap()
|
||||
}
|
||||
fn bytecode(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
fn get_const(&self, id: u32) -> StaticValue {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.constants.get(id).unwrap()
|
||||
impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
||||
fn intern_string(&mut self, s: &str) -> StringId {
|
||||
self.runtime.intern_string(s)
|
||||
}
|
||||
|
||||
fn compile(&mut self, _source: Source) {
|
||||
todo!();
|
||||
fn register_span(&mut self, range: rnix::TextRange) -> u32 {
|
||||
let id = self.code.spans.len();
|
||||
let source_id = self
|
||||
.code
|
||||
.sources
|
||||
.len()
|
||||
.checked_sub(1)
|
||||
.expect("current_source not set");
|
||||
self.code.spans.push((source_id, range));
|
||||
id as u32
|
||||
}
|
||||
|
||||
fn get_code(&self) -> &[u8] {
|
||||
&self.code.bytecode
|
||||
}
|
||||
|
||||
fn get_code_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.code.bytecode
|
||||
}
|
||||
|
||||
fn add_constant(&mut self, val: fix_codegen::Const) -> u32 {
|
||||
use fix_codegen::Const::*;
|
||||
let val = match val {
|
||||
Smi(x) => StaticValue::new_inline(x),
|
||||
Float(x) => StaticValue::new_float(x),
|
||||
Bool(x) => StaticValue::new_inline(x),
|
||||
String(x) => StaticValue::new_inline(x),
|
||||
PrimOp {
|
||||
id,
|
||||
arity,
|
||||
dispatch_ip,
|
||||
} => StaticValue::new_primop(id, arity, dispatch_ip),
|
||||
Null => StaticValue::default(),
|
||||
};
|
||||
self.runtime.add_const(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Constants {
|
||||
pub struct Constants {
|
||||
data: Vec<StaticValue>,
|
||||
dedup: HashMap<u64, u32>,
|
||||
}
|
||||
@@ -236,10 +305,10 @@ fn handle_parse_error<'a>(
|
||||
None
|
||||
}
|
||||
|
||||
struct DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
||||
bump: &'ir Bump,
|
||||
token: GhostToken<'id>,
|
||||
strings: &'ctx mut DefaultStringInterner,
|
||||
runtime: &'ctx mut R,
|
||||
source: Source,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
with_scope_count: u32,
|
||||
@@ -262,11 +331,11 @@ fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
fn new(
|
||||
bump: &'ir Bump,
|
||||
token: GhostToken<'id>,
|
||||
symbols: &'ctx mut DefaultStringInterner,
|
||||
runtime: &'ctx mut R,
|
||||
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
thunk_count: &'ctx mut usize,
|
||||
@@ -275,7 +344,7 @@ impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
Self {
|
||||
bump,
|
||||
token,
|
||||
strings: symbols,
|
||||
runtime,
|
||||
source,
|
||||
scopes: std::iter::once(Scope::Global(global))
|
||||
.chain(extra_scope)
|
||||
@@ -288,7 +357,9 @@ impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
for DowngradeCtx<'ctx, 'id, 'ir, R>
|
||||
{
|
||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||
}
|
||||
@@ -317,11 +388,11 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
}
|
||||
|
||||
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
|
||||
StringId(self.strings.get_or_intern(sym))
|
||||
self.runtime.intern_string(sym)
|
||||
}
|
||||
|
||||
fn resolve_sym(&self, id: StringId) -> Symbol<'_> {
|
||||
self.strings.resolve(id.0).expect("no symbol found").into()
|
||||
self.runtime.resolve_string(id).into()
|
||||
}
|
||||
|
||||
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<MaybeThunk> {
|
||||
@@ -383,9 +454,9 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
self.source.clone()
|
||||
}
|
||||
|
||||
fn with_let_scope<F, R>(&mut self, keys: &[StringId], f: F) -> Result<R>
|
||||
fn with_let_scope<F, Ret>(&mut self, keys: &[StringId], f: F) -> Result<Ret>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
|
||||
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, Ret)>,
|
||||
{
|
||||
let base = *self.thunk_count;
|
||||
*self.thunk_count = self
|
||||
@@ -407,9 +478,9 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn with_param_scope<F, R>(&mut self, sym: StringId, f: F) -> R
|
||||
fn with_param_scope<F, Ret>(&mut self, sym: StringId, f: F) -> Ret
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
F: FnOnce(&mut Self) -> Ret,
|
||||
{
|
||||
self.scopes.push(Scope::Param {
|
||||
sym,
|
||||
@@ -419,9 +490,9 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
f(guard.as_ctx())
|
||||
}
|
||||
|
||||
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||
fn with_with_scope<F, Ret>(&mut self, f: F) -> Ret
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
F: FnOnce(&mut Self) -> Ret,
|
||||
{
|
||||
self.with_scope_count += 1;
|
||||
let ret = f(self);
|
||||
@@ -429,15 +500,15 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
ret
|
||||
}
|
||||
|
||||
fn with_thunk_scope<F, R>(
|
||||
fn with_thunk_scope<F, Ret>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> (
|
||||
R,
|
||||
Ret,
|
||||
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||
)
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
F: FnOnce(&mut Self) -> Ret,
|
||||
{
|
||||
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
||||
let ret = f(self);
|
||||
@@ -455,7 +526,7 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
||||
let body = root.downgrade(&mut self)?;
|
||||
let thunks = self
|
||||
@@ -496,18 +567,18 @@ enum Scope<'ctx> {
|
||||
Param { sym: StringId, abs_layer: usize },
|
||||
}
|
||||
|
||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
|
||||
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
|
||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
||||
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>,
|
||||
}
|
||||
|
||||
impl Drop for ScopeGuard<'_, '_, '_, '_> {
|
||||
impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> Drop for ScopeGuard<'a, 'ctx, 'id, 'ir, R> {
|
||||
fn drop(&mut self) {
|
||||
self.ctx.scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
|
||||
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> ScopeGuard<'a, 'ctx, 'id, 'ir, R> {
|
||||
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
@@ -518,9 +589,6 @@ struct OwnedIr {
|
||||
}
|
||||
|
||||
impl OwnedIr {
|
||||
/// # Safety
|
||||
///
|
||||
/// `ir` must be allocated from `bump`.
|
||||
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
||||
Self {
|
||||
_bump: bump,
|
||||
@@ -533,52 +601,14 @@ impl OwnedIr {
|
||||
}
|
||||
}
|
||||
|
||||
impl BytecodeContext for Evaluator {
|
||||
fn intern_string(&mut self, s: &str) -> StringId {
|
||||
StringId(self.strings.get_or_intern(s))
|
||||
}
|
||||
|
||||
fn register_span(&mut self, range: rnix::TextRange) -> u32 {
|
||||
let id = self.spans.len();
|
||||
let source_id = self
|
||||
.sources
|
||||
.len()
|
||||
.checked_sub(1)
|
||||
.expect("current_source not set");
|
||||
self.spans.push((source_id, range));
|
||||
id as u32
|
||||
}
|
||||
|
||||
fn get_code(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
|
||||
fn get_code_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.bytecode
|
||||
}
|
||||
|
||||
fn add_constant(&mut self, val: fix_codegen::Const) -> u32 {
|
||||
use fix_codegen::Const::*;
|
||||
let val = match val {
|
||||
Smi(x) => StaticValue::new_inline(x),
|
||||
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),
|
||||
Null => StaticValue::default(),
|
||||
};
|
||||
self.constants.insert(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisassemblerContext for Evaluator {
|
||||
fn get_code(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
&self.code.bytecode
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn resolve_string(&self, id: u32) -> &str {
|
||||
let id = string_interner::symbol::SymbolU32::try_from_usize(id as usize).unwrap();
|
||||
self.strings.resolve(id).unwrap()
|
||||
self.runtime.strings.resolve(id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+18
-18
@@ -46,16 +46,16 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776182890,
|
||||
"narHash": "sha256-+/VOe8XGq5klpU+I19D+3TcaR7o+Cwbq67KNF7mcFak=",
|
||||
"owner": "Mic92",
|
||||
"lastModified": 1776192490,
|
||||
"narHash": "sha256-5gYQNEs0/vDkHhg63aHS5g0IwG/8HNvU1Vr00cElofk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "bun2nix",
|
||||
"rev": "648d293c51e981aec9cb07ba4268bc19e7a8c575",
|
||||
"rev": "6ef9f144616eedea90b364bb408ef2e1de7b310a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Mic92",
|
||||
"ref": "catalog-support",
|
||||
"owner": "nix-community",
|
||||
"ref": "staging-2.1.0",
|
||||
"repo": "bun2nix",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -68,11 +68,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776413252,
|
||||
"narHash": "sha256-ZQhyB2vnFsE1KcWJlWle1UujEDVjTJVL3oMIHUvnzuo=",
|
||||
"lastModified": 1777018861,
|
||||
"narHash": "sha256-l+dfxHtTq1jQM53xgYudV8ciECFmJ72PcRAqRS4ys04=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "a318c3c6120e91375eea1d7c57a0cd101a81b14a",
|
||||
"rev": "7b33c6466f781cd699fe250c5b69dc4193da67a7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -143,11 +143,11 @@
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776437995,
|
||||
"narHash": "sha256-wcV5CIe5s2IsSCGJdPqy/Q+gcBSR76JMaIQDNpLXZAk=",
|
||||
"lastModified": 1777019177,
|
||||
"narHash": "sha256-YjPvucTsKmGO9QVNz07x7sSsK11PB0jtMniRkTolbq4=",
|
||||
"owner": "numtide",
|
||||
"repo": "llm-agents.nix",
|
||||
"rev": "c4a2f76e29485eaafc90eebec5ef12b50f4dc8a1",
|
||||
"rev": "8ff0f2a7fcd176b4547da6879ad549de2bbded41",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -158,11 +158,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1776169885,
|
||||
"narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=",
|
||||
"lastModified": 1776548001,
|
||||
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
|
||||
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -183,11 +183,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1776343166,
|
||||
"narHash": "sha256-ZiHQPWwuUZk44epAZRbyFz23Kd4CYaq8WlBgAmCqAzQ=",
|
||||
"lastModified": 1776800521,
|
||||
"narHash": "sha256-f8YJfwAOsLFpIoqZuX3yF69UvMLrkx7iVzMH1pJU7cM=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "b8458013c217be4fccefc4e4f194026fa04ab4ca",
|
||||
"rev": "8954b66d43225e62c92e8bbcc8500191b5cceb1e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
Reference in New Issue
Block a user