Compare commits

..
9 Commits
65 changed files with 3547 additions and 3531 deletions
Generated
+77 -88
View File
@@ -445,22 +445,18 @@ name = "fix"
version = "0.1.0"
dependencies = [
"anyhow",
"bumpalo",
"clap",
"criterion",
"ere",
"fix-abstract-vm",
"fix-builtins",
"fix-codegen",
"fix-common",
"fix-bytecode",
"fix-compiler",
"fix-error",
"fix-ir",
"fix-lang",
"fix-runtime",
"fix-vm",
"ghost-cell",
"hashbrown 0.16.1",
"miette",
"mimalloc",
"rnix",
"rustyline",
"string-interner",
"tempfile",
@@ -471,51 +467,31 @@ dependencies = [
]
[[package]]
name = "fix-abstract-vm"
version = "0.1.0"
dependencies = [
"fix-builtins",
"fix-codegen",
"fix-common",
"fix-error",
"gc-arena",
"hashbrown 0.16.1",
"likely_stable",
"num_enum",
"smallvec",
"sptr",
"string-interner",
]
[[package]]
name = "fix-builtins"
version = "0.1.0"
dependencies = [
"gc-arena",
"num_enum",
]
[[package]]
name = "fix-codegen"
name = "fix-bytecode"
version = "0.1.0"
dependencies = [
"colored",
"fix-builtins",
"fix-common",
"fix-ir",
"hashbrown 0.16.1",
"fix-lang",
"num_enum",
"rnix",
"string-interner",
]
[[package]]
name = "fix-common"
name = "fix-compiler"
version = "0.1.0"
dependencies = [
"ere",
"gc-arena",
"bumpalo",
"colored",
"fix-bytecode",
"fix-error",
"fix-lang",
"fix-runtime",
"ghost-cell",
"hashbrown 0.16.1",
"rnix",
"rowan",
"string-interner",
"tracing",
]
[[package]]
@@ -528,34 +504,37 @@ dependencies = [
]
[[package]]
name = "fix-ir"
name = "fix-lang"
version = "0.1.0"
dependencies = [
"bumpalo",
"fix-builtins",
"fix-common",
"fix-error",
"ghost-cell",
"hashbrown 0.16.1",
"ere",
"gc-arena",
"num_enum",
"rnix",
"rowan",
"string-interner",
]
[[package]]
name = "fix-primops"
name = "fix-macros"
version = "0.1.0"
dependencies = [
"fix-abstract-vm",
"fix-builtins",
"fix-codegen",
"fix-common",
"manyhow",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "fix-runtime"
version = "0.1.0"
dependencies = [
"fix-bytecode",
"fix-error",
"fix-lang",
"fix-macros",
"gc-arena",
"hashbrown 0.16.1",
"num_enum",
"smallvec",
"sptr",
"string-interner",
]
@@ -563,19 +542,13 @@ dependencies = [
name = "fix-vm"
version = "0.1.0"
dependencies = [
"fix-abstract-vm",
"fix-builtins",
"fix-codegen",
"fix-common",
"fix-bytecode",
"fix-error",
"fix-primops",
"fix-lang",
"fix-runtime",
"gc-arena",
"hashbrown 0.16.1",
"likely_stable",
"num_enum",
"smallvec",
"sptr",
"string-interner",
"sysinfo",
]
@@ -773,15 +746,6 @@ dependencies = [
"libc",
]
[[package]]
name = "likely_stable"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61f7017d8abea1fc23ff7f01a8147b2656dea3aeb24d519aab6e2177eaf671c"
dependencies = [
"rustc_version",
]
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@@ -794,6 +758,29 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "manyhow"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587"
dependencies = [
"manyhow-macros",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "manyhow-macros"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495"
dependencies = [
"proc-macro-utils",
"proc-macro2",
"quote",
]
[[package]]
name = "matchers"
version = "0.2.0"
@@ -1042,6 +1029,17 @@ dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro-utils"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071"
dependencies = [
"proc-macro2",
"quote",
"smallvec",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -1053,9 +1051,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
@@ -1158,15 +1156,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.1.4"
@@ -1331,9 +1320,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
+24 -23
View File
@@ -1,42 +1,43 @@
[workspace]
resolver = "3"
members = [
"fix",
"fix-abstract-vm",
"fix-builtins",
"fix-codegen",
"fix-common",
"fix-error",
"fix-ir",
"fix-primops",
"fix-vm",
"fix",
"fix-bytecode",
"fix-compiler",
"fix-error",
"fix-lang",
"fix-macros",
"fix-runtime",
"fix-vm",
]
[profile.profiling]
inherits = "release"
debug = true
[profile.lto]
inherits = "release"
lto = true
[workspace.dependencies]
bumpalo = { version = "3.20", features = [
bumpalo = {
version = "3.20",
features = [
"allocator-api2",
"boxed",
"collections",
] }
]
}
ere = "0.2"
ghost-cell = "0.2"
hashbrown = "0.16"
num_enum = "0.7.5"
smallvec = { version = "1.15", features = ["const_new", "const_generics"] }
ere = "0.2"
string-interner = "0.19"
rnix = "0.14"
rowan = "0.16"
likely_stable = "0.1"
smallvec = { version = "1.15", features = ["const_generics", "const_new"] }
string-interner = "0.19"
[workspace.dependencies.gc-arena]
git = "https://github.com/kyren/gc-arena"
rev = "75671ae03f53718357b741ed4027560f14e90836"
features = ["allocator-api2", "hashbrown", "smallvec"]
[profile.lto]
inherits = "release"
lto = true
[profile.profiling]
inherits = "release"
debug = true
-142
View File
@@ -1,142 +0,0 @@
#![allow(dead_code)]
use fix_codegen::OperandType;
use fix_common::StringId;
use num_enum::TryFromPrimitive;
use string_interner::Symbol as _;
use crate::{OperandData, VmRuntimeCtx};
pub struct BytecodeReader<'a> {
bytecode: &'a [u8],
pc: usize,
inst_start_pc: usize,
}
impl<'a> BytecodeReader<'a> {
pub fn new(bytecode: &'a [u8], pc: usize) -> Self {
Self {
bytecode,
pc,
inst_start_pc: pc,
}
}
#[inline(always)]
pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
Self {
bytecode,
pc: inst_start_pc + 1,
inst_start_pc,
}
}
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
fn read_array<const N: usize>(&mut self) -> [u8; N] {
let ret = self.bytecode[self.pc..self.pc + N]
.try_into()
.expect("read_array failed");
self.pc += N;
ret
}
#[inline(always)]
pub fn read_op(&mut self) -> fix_codegen::Op {
use fix_codegen::Op;
self.inst_start_pc = self.pc;
let byte = self.bytecode[self.pc];
if !likely_stable::likely((0..Op::Illegal as u8).contains(&byte)) {
panic!("unknown opcode: {byte:#04x}")
}
self.pc += 1;
unsafe { std::mem::transmute::<u8, Op>(byte) }
}
#[inline(always)]
pub fn read_u8(&mut self) -> u8 {
let val = self.bytecode[self.pc];
self.pc += 1;
val
}
#[inline(always)]
pub fn read_u16(&mut self) -> u16 {
u16::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_u32(&mut self) -> u32 {
u32::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_i32(&mut self) -> i32 {
i32::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_i64(&mut self) -> i64 {
i64::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_f64(&mut self) -> f64 {
f64::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_string_id(&mut self) -> StringId {
let raw = self.read_u32();
#[allow(clippy::unwrap_used)]
StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap())
}
#[inline(always)]
pub 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));
match ty {
OperandType::Const => {
let id = self.read_u32();
OperandData::Const(ctx.get_const(id))
}
OperandType::BigInt => {
let val = self.read_i64();
OperandData::BigInt(val)
}
OperandType::Local => {
let layer = self.read_u8();
let idx = self.read_u32();
OperandData::Local { layer, idx }
}
OperandType::BuiltinConst => {
let id = self.read_string_id();
OperandData::BuiltinConst(id)
}
OperandType::Builtins => OperandData::Builtins,
OperandType::ReplBinding => {
let id = self.read_string_id();
OperandData::ReplBinding(id)
}
OperandType::ScopedImportBinding => {
let slot_id = self.read_u32();
let name = self.read_string_id();
OperandData::ScopedImportBinding { slot_id, name }
}
}
}
pub fn pc(&self) -> usize {
self.pc
}
pub fn set_pc(&mut self, pc: usize) {
self.pc = pc;
}
pub fn inst_start_pc(&self) -> usize {
self.inst_start_pc
}
}
-8
View File
@@ -1,8 +0,0 @@
[package]
name = "fix-builtins"
version = "0.1.0"
edition = "2024"
[dependencies]
num_enum = { workspace = true }
gc-arena = { workspace = true }
-399
View File
@@ -1,399 +0,0 @@
use gc_arena::Collect;
use num_enum::TryFromPrimitive;
macro_rules! define_builtins {
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
/// Builtin function registry.
/// Array index IS the PrimOp id. (name, arity) pairs.
pub const BUILTINS: &[(&str, u8)] = &[
$(($name, $arity),)*
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)]
#[repr(u8)]
#[collect(require_static)]
pub enum BuiltinId {
$($variant,)*
}
};
}
define_builtins! {
("abort", Abort, 1),
("__add", Add, 2),
("__addErrorContext", AddErrorContext, 2),
("__all", All, 2),
("__any", Any, 2),
("__appendContext", AppendContext, 2),
("__attrNames", AttrNames, 1),
("__attrValues", AttrValues, 1),
("baseNameOf", BaseNameOf, 1),
("__bitAnd", BitAnd, 2),
("__bitOr", BitOr, 2),
("__bitXor", BitXor, 2),
("break", Break, 1),
("__catAttrs", CatAttrs, 2),
("__ceil", Ceil, 1),
("__compareVersions", CompareVersions, 2),
("__concatLists", ConcatLists, 1),
("__concatMap", ConcatMap, 2),
("__concatStringsSep", ConcatStringsSep, 2),
("__convertHash", ConvertHash, 1),
("__deepSeq", DeepSeq, 2),
("derivation", Derivation, 1),
("derivationStrict", DerivationStrict, 1),
("dirOf", DirOf, 1),
("__div", Div, 2),
("__elem", Elem, 2),
("__elemAt", ElemAt, 2),
("fetchGit", FetchGit, 1),
("fetchMercurial", FetchMercurial, 1),
("fetchTarball", FetchTarball, 1),
("fetchTree", FetchTree, 1),
("__fetchurl", FetchUrl, 1),
("__filter", Filter, 2),
("__filterSource", FilterSource, 2),
("__findFile", FindFile, 2),
("__floor", Floor, 1),
("__foldl'", FoldlStrict, 3),
("__fromJSON", FromJSON, 1),
("fromTOML", FromTOML, 1),
("__functionArgs", FunctionArgs, 1),
("__genList", GenList, 2),
("__genericClosure", GenericClosure, 1),
("__getAttr", GetAttr, 2),
("__getContext", GetContext, 1),
("__getEnv", GetEnv, 1),
("__groupBy", GroupBy, 2),
("__hasAttr", HasAttr, 2),
("__hasContext", HasContext, 1),
("__hashFile", HashFile, 2),
("__hashString", HashString, 2),
("__head", Head, 1),
("import", Import, 1),
("__intersectAttrs", IntersectAttrs, 2),
("__isAttrs", IsAttrs, 1),
("__isBool", IsBool, 1),
("__isFloat", IsFloat, 1),
("__isFunction", IsFunction, 1),
("__isInt", IsInt, 1),
("__isList", IsList, 1),
("isNull", IsNull, 1),
("__isPath", IsPath, 1),
("__isString", IsString, 1),
("__length", Length, 1),
("__lessThan", LessThan, 2),
("__listToAttrs", ListToAttrs, 1),
("map", Map, 2),
("__mapAttrs", MapAttrs, 2),
("__match", Match, 2),
("__mul", Mul, 2),
("__parseDrvName", ParseDrvName, 1),
("__partition", Partition, 2),
("__path", Path, 1),
("__pathExists", PathExists, 1),
("placeholder", Placeholder, 1),
("__readDir", ReadDir, 1),
("__readFile", ReadFile, 1),
("__readFileType", ReadFileType, 1),
("removeAttrs", RemoveAttrs, 2),
("__replaceStrings", ReplaceStrings, 3),
("scopedImport", ScopedImport, 2),
("__seq", Seq, 2),
("__sort", Sort, 2),
("__split", Split, 2),
("__splitVersion", SplitVersion, 1),
("__storePath", StorePath, 1),
("__stringLength", StringLength, 1),
("__sub", Sub, 2),
("__substring", Substring, 3),
("__tail", Tail, 1),
("throw", Throw, 1),
("__toFile", ToFile, 2),
("__toJSON", ToJSON, 1),
("__toPath", ToPath, 1),
("toString", ToString, 1),
("__toXML", ToXML, 1),
("__trace", Trace, 2),
("__tryEval", TryEval, 1),
("__typeOf", TypeOf, 1),
("__unsafeDiscardStringContext", UnsafeDiscardStringContext, 1),
("__unsafeDiscardOutputDependency", UnsafeDiscardOutputDependency, 1),
("__unsafeGetAttrPos", UnsafeGetAttrPos, 2),
("__warn", Warn, 2),
("__zipAttrsWith", ZipAttrsWith, 2),
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
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,
FoldlStrictEmpty,
FoldlStrictCall1,
FoldlStrictCall2,
FoldlStrictUpdate,
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,
EqStep,
EqForce,
// TODO: split into separate enums
CallPattern,
CallFunctor1,
CallFunctor2,
ImportFinalize,
ScopedImportFinalize,
AppendContextLoop,
AppendContextEntryForced,
AppendContextOutputsForced,
AppendContextOutputElementLoop,
AppendContextOutputElementForced,
UnsafeDiscardOutputDependency,
Illegal,
}
impl TryFrom<u8> for PrimOpPhase {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if (0..Self::Illegal as u8).contains(&value) {
Ok(unsafe { std::mem::transmute::<u8, Self>(value) })
} else {
Err(value)
}
}
}
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,
UnsafeDiscardOutputDependency => PrimOpPhase::UnsafeDiscardOutputDependency,
UnsafeGetAttrPos => PrimOpPhase::UnsafeGetAttrPos,
Warn => PrimOpPhase::Warn,
ZipAttrsWith => PrimOpPhase::ZipAttrsWith,
}
}
}
impl PrimOpPhase {
pub fn ip(self) -> u32 {
self as u32 * 2
}
}
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "fix-bytecode"
version = "0.1.0"
edition = "2024"
[dependencies]
colored = "3.1.1"
num_enum = { workspace = true }
string-interner = { workspace = true }
fix-lang = { path = "../fix-lang" }
@@ -1,10 +1,8 @@
use std::fmt::Write;
use colored::Colorize;
use fix_builtins::BuiltinId;
use num_enum::TryFromPrimitive;
use colored::Colorize as _;
use crate::{InstructionPtr, Op, OperandType};
use crate::{Continuation, InstructionPtr, Op, OperandType};
pub trait DisassemblerContext {
fn resolve_string(&self, id: u32) -> &str;
@@ -82,7 +80,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
fn read_operand_data(&mut self) {
use OperandType::*;
let tag = self.read_u8();
let ty = OperandType::try_from_primitive(tag).expect("invalid operand type");
let ty = OperandType::try_from(tag).expect("invalid operand type");
match ty {
Const => {
self.read_u32();
@@ -202,7 +200,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
}
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
let op = Op::try_from_primitive(op_byte).expect("invalid op code");
let op = Op::try_from(op_byte).expect("invalid op code");
match op {
Op::PushSmi => {
@@ -296,9 +294,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
self.read_operand_data();
("Call", "arg=?".into())
}
Op::DispatchPrimOp => {
let id = BuiltinId::try_from_primitive(self.read_u8()).expect("invalid builtin id");
("DispatchPrimOp", format!("id={id:?}"))
Op::DispatchCont => {
let phase = Continuation::try_from(self.read_u8()).expect("invalid primop phase");
("DispatchPrimOp", format!("phase={phase:?}"))
}
Op::MakeAttrs => {
+533
View File
@@ -0,0 +1,533 @@
#![allow(dead_code)]
use fix_lang::{BuiltinId, StringId};
use num_enum::TryFromPrimitive;
use string_interner::Symbol as _;
pub mod disassembler;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct InstructionPtr(pub usize);
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
#[allow(clippy::enum_variant_names)]
pub enum Op {
PushSmi,
PushBigInt,
PushFloat,
PushString,
PushNull,
PushTrue,
PushFalse,
LoadLocal,
LoadOuter,
StoreLocal,
AllocLocals,
MakeThunk,
MakeClosure,
MakePatternClosure,
Call,
DispatchCont,
MakeAttrs,
MakeEmptyAttrs,
SelectStatic,
SelectDynamic,
HasAttrPathStatic,
HasAttrPathDynamic,
HasAttrStatic,
HasAttrDynamic,
HasAttrResolve,
JumpIfSelectSucceeded,
JumpIfSelectFailed,
MakeList,
MakeEmptyList,
OpAdd,
OpSub,
OpMul,
OpDiv,
OpEq,
OpNeq,
OpLt,
OpGt,
OpLeq,
OpGeq,
OpConcat,
OpUpdate,
OpNeg,
OpNot,
JumpIfFalse,
JumpIfTrue,
Jump,
CoerceToString,
ConcatStrings,
ResolvePath,
Assert,
LookupWith,
LoadBuiltins,
LoadBuiltin,
LoadReplBinding,
LoadScopedBinding,
Return,
Illegal,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum OperandType {
Const,
BigInt,
Local,
BuiltinConst,
Builtins,
ReplBinding,
ScopedImportBinding,
}
pub enum Const {
Smi(i32),
Float(f64),
Bool(bool),
String(StringId),
Path(StringId),
PrimOp {
id: BuiltinId,
arity: u8,
dispatch_ip: u32,
},
Null,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum AttrKeyType {
Static,
Dynamic,
}
pub enum OperandData {
Const(u32),
BigInt(i64),
Local { layer: u8, idx: u32 },
BuiltinConst(StringId),
Builtins,
ReplBinding(StringId),
ScopedImportBinding { slot_id: u32, name: StringId },
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Continuation {
// primops
PAbort,
PAdd,
PAddErrorContext,
PAll,
PAllCallPred,
PAllCheck,
PAny,
PAnyCallPred,
PAnyCheck,
PAppendContext,
PAppendContextLoop,
PAppendContextEntryForced,
PAppendContextOutputsForced,
PAppendContextOutputElementLoop,
PAppendContextOutputElementForced,
PAttrNames,
PAttrValues,
PBaseNameOf,
PBitAnd,
PBitOr,
PBitXor,
PBreak,
PCatAttrs,
PCeil,
PCompareVersions,
PConcatLists,
PConcatMap,
PConcatStringsSep,
PConvertHash,
PDeepSeq,
PDeepSeqPush,
PDeepSeqLoop,
PDerivation,
PDerivationStrict,
PDirOf,
PDiv,
PElem,
PElemAt,
PFetchGit,
PFetchMercurial,
PFetchTarball,
PFetchTree,
PFetchUrl,
PFilterForceList,
PFilterCallPred,
PFilterCheck,
PFilterSource,
PFindFile,
PFloor,
PFoldlStrict,
PFoldlStrictEmpty,
PFoldlStrictCall1,
PFoldlStrictCall2,
PFoldlStrictUpdate,
PFromJSON,
PFromTOML,
PFunctionArgs,
PGenList,
PGenericClosure,
PGetAttr,
PGetContext,
PGetEnv,
PGroupBy,
PHasAttr,
PHasContext,
PHashFile,
PHashString,
PHead,
PImport,
PImportFinalize,
PScopedImport,
PScopedImportFinalize,
PIntersectAttrs,
PIsAttrs,
PIsBool,
PIsFloat,
PIsFunction,
PIsInt,
PIsList,
PIsNull,
PIsPath,
PIsString,
PLength,
PLessThan,
PListToAttrs,
PMap,
PMapAttrs,
PMatch,
PMul,
PParseDrvName,
PPartition,
PPath,
PPathExists,
PPlaceholder,
PReadDir,
PReadFile,
PReadFileType,
PRemoveAttrs,
PReplaceStrings,
PSeq,
PSort,
PSplit,
PSplitVersion,
PStorePath,
PStringLength,
PSub,
PSubstring,
PTail,
PThrow,
PToFile,
PToJSON,
PToPath,
PToString,
PToXML,
PTrace,
PTryEval,
PTypeOf,
PUnsafeDiscardStringContext,
PUnsafeGetAttrPos,
PWarn,
PZipAttrsWith,
PUnsafeDiscardOutputDependency,
ForceResultShallow,
ForceResultShallowPush,
ForceResultShallowLoop,
ForceResultDeepFinish,
EqStep,
EqForce,
CallPattern,
CallFunctor1,
CallFunctor2,
Illegal,
}
impl TryFrom<u8> for Continuation {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if (0..Self::Illegal as u8).contains(&value) {
Ok(unsafe { std::mem::transmute::<u8, Self>(value) })
} else {
Err(value)
}
}
}
impl Continuation {
pub fn entry_for_builtin(id: BuiltinId) -> Self {
use BuiltinId::*;
match id {
Abort => Self::PAbort,
Add => Self::PAdd,
AddErrorContext => Self::PAddErrorContext,
All => Self::PAll,
Any => Self::PAny,
AppendContext => Self::PAppendContext,
AttrNames => Self::PAttrNames,
AttrValues => Self::PAttrValues,
BaseNameOf => Self::PBaseNameOf,
BitAnd => Self::PBitAnd,
BitOr => Self::PBitOr,
BitXor => Self::PBitXor,
Break => Self::PBreak,
CatAttrs => Self::PCatAttrs,
Ceil => Self::PCeil,
CompareVersions => Self::PCompareVersions,
ConcatLists => Self::PConcatLists,
ConcatMap => Self::PConcatMap,
ConcatStringsSep => Self::PConcatStringsSep,
ConvertHash => Self::PConvertHash,
DeepSeq => Self::PDeepSeq,
Derivation => Self::PDerivation,
DerivationStrict => Self::PDerivationStrict,
DirOf => Self::PDirOf,
Div => Self::PDiv,
Elem => Self::PElem,
ElemAt => Self::PElemAt,
FetchGit => Self::PFetchGit,
FetchMercurial => Self::PFetchMercurial,
FetchTarball => Self::PFetchTarball,
FetchTree => Self::PFetchTree,
FetchUrl => Self::PFetchUrl,
Filter => Self::PFilterForceList,
FilterSource => Self::PFilterSource,
FindFile => Self::PFindFile,
Floor => Self::PFloor,
FoldlStrict => Self::PFoldlStrict,
FromJSON => Self::PFromJSON,
FromTOML => Self::PFromTOML,
FunctionArgs => Self::PFunctionArgs,
GenList => Self::PGenList,
GenericClosure => Self::PGenericClosure,
GetAttr => Self::PGetAttr,
GetContext => Self::PGetContext,
GetEnv => Self::PGetEnv,
GroupBy => Self::PGroupBy,
HasAttr => Self::PHasAttr,
HasContext => Self::PHasContext,
HashFile => Self::PHashFile,
HashString => Self::PHashString,
Head => Self::PHead,
Import => Self::PImport,
IntersectAttrs => Self::PIntersectAttrs,
IsAttrs => Self::PIsAttrs,
IsBool => Self::PIsBool,
IsFloat => Self::PIsFloat,
IsFunction => Self::PIsFunction,
IsInt => Self::PIsInt,
IsList => Self::PIsList,
IsNull => Self::PIsNull,
IsPath => Self::PIsPath,
IsString => Self::PIsString,
Length => Self::PLength,
LessThan => Self::PLessThan,
ListToAttrs => Self::PListToAttrs,
Map => Self::PMap,
MapAttrs => Self::PMapAttrs,
Match => Self::PMatch,
Mul => Self::PMul,
ParseDrvName => Self::PParseDrvName,
Partition => Self::PPartition,
Path => Self::PPath,
PathExists => Self::PPathExists,
Placeholder => Self::PPlaceholder,
ReadDir => Self::PReadDir,
ReadFile => Self::PReadFile,
ReadFileType => Self::PReadFileType,
RemoveAttrs => Self::PRemoveAttrs,
ReplaceStrings => Self::PReplaceStrings,
ScopedImport => Self::PScopedImport,
Seq => Self::PSeq,
Sort => Self::PSort,
Split => Self::PSplit,
SplitVersion => Self::PSplitVersion,
StorePath => Self::PStorePath,
StringLength => Self::PStringLength,
Sub => Self::PSub,
Substring => Self::PSubstring,
Tail => Self::PTail,
Throw => Self::PThrow,
ToFile => Self::PToFile,
ToJSON => Self::PToJSON,
ToPath => Self::PToPath,
ToString => Self::PToString,
ToXML => Self::PToXML,
Trace => Self::PTrace,
TryEval => Self::PTryEval,
TypeOf => Self::PTypeOf,
UnsafeDiscardStringContext => Self::PUnsafeDiscardStringContext,
UnsafeDiscardOutputDependency => Self::PUnsafeDiscardOutputDependency,
UnsafeGetAttrPos => Self::PUnsafeGetAttrPos,
Warn => Self::PWarn,
ZipAttrsWith => Self::PZipAttrsWith,
}
}
pub fn ip(self) -> u32 {
self as u32 * 2
}
}
pub struct BytecodeReader<'a> {
bytecode: &'a [u8],
pc: usize,
inst_start_pc: usize,
}
impl<'a> BytecodeReader<'a> {
pub fn new(bytecode: &'a [u8], pc: usize) -> Self {
Self {
bytecode,
pc,
inst_start_pc: pc,
}
}
#[inline(always)]
pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
Self {
bytecode,
pc: inst_start_pc + 1,
inst_start_pc,
}
}
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
fn read_array<const N: usize>(&mut self) -> [u8; N] {
let ret = self.bytecode[self.pc..self.pc + N]
.try_into()
.expect("read_array failed");
self.pc += N;
ret
}
#[inline(always)]
pub fn read_op(&mut self) -> Op {
self.inst_start_pc = self.pc;
let byte = self.bytecode[self.pc];
if !(0..Op::Illegal as u8).contains(&byte) {
std::hint::cold_path();
panic!("unknown opcode: {byte:#04x}")
}
self.pc += 1;
unsafe { std::mem::transmute::<u8, Op>(byte) }
}
#[inline(always)]
pub fn read_u8(&mut self) -> u8 {
let val = self.bytecode[self.pc];
self.pc += 1;
val
}
#[inline(always)]
pub fn read_u16(&mut self) -> u16 {
u16::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_u32(&mut self) -> u32 {
u32::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_i32(&mut self) -> i32 {
i32::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_i64(&mut self) -> i64 {
i64::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_f64(&mut self) -> f64 {
f64::from_le_bytes(self.read_array())
}
#[inline(always)]
pub fn read_string_id(&mut self) -> StringId {
let raw = self.read_u32();
#[allow(clippy::unwrap_used)]
StringId(string_interner::symbol::SymbolU32::try_from_usize(raw as usize).unwrap())
}
#[inline(always)]
pub fn read_operand_data(&mut self) -> OperandData {
let tag = self.read_u8();
let Ok(ty) = OperandType::try_from_primitive(tag)
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
match ty {
OperandType::Const => OperandData::Const(self.read_u32()),
OperandType::BigInt => OperandData::BigInt(self.read_i64()),
OperandType::Local => {
let layer = self.read_u8();
let idx = self.read_u32();
OperandData::Local { layer, idx }
}
OperandType::BuiltinConst => OperandData::BuiltinConst(self.read_string_id()),
OperandType::Builtins => OperandData::Builtins,
OperandType::ReplBinding => OperandData::ReplBinding(self.read_string_id()),
OperandType::ScopedImportBinding => {
let slot_id = self.read_u32();
let name = self.read_string_id();
OperandData::ScopedImportBinding { slot_id, name }
}
}
}
pub fn pc(&self) -> usize {
self.pc
}
pub fn set_pc(&mut self, pc: usize) {
self.pc = pc;
}
pub fn inst_start_pc(&self) -> usize {
self.inst_start_pc
}
}
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "fix-codegen"
version = "0.1.0"
edition = "2024"
[dependencies]
hashbrown = { workspace = true }
num_enum = { workspace = true }
rnix = { workspace = true }
string-interner = { workspace = true }
colored = "3.1.1"
fix-builtins = { path = "../fix-builtins" }
fix-common = { path = "../fix-common" }
fix-ir = { path = "../fix-ir" }
@@ -1,17 +1,19 @@
[package]
name = "fix-ir"
name = "fix-compiler"
version = "0.1.0"
edition = "2024"
[dependencies]
bumpalo = { workspace = true }
colored = "3.1.1"
ghost-cell = { workspace = true }
hashbrown = { workspace = true }
rnix = { workspace = true }
rowan = { workspace = true }
string-interner = { workspace = true }
hashbrown = { workspace = true }
num_enum = { workspace = true }
fix-builtins = { path = "../fix-builtins" }
fix-common = { path = "../fix-common" }
fix-bytecode = { path = "../fix-bytecode" }
fix-error = { path = "../fix-error" }
fix-lang = { path = "../fix-lang" }
fix-runtime = { path = "../fix-runtime" }
tracing = "0.1"
+542
View File
@@ -0,0 +1,542 @@
use bumpalo::Bump;
use fix_bytecode::{Const, Continuation, InstructionPtr, Op};
use fix_error::{Error, Result, Source};
use fix_lang::{StringId, Symbol};
use fix_runtime::{StaticValue, VmCode, VmRuntimeCtx};
use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{HashMap, HashSet};
use string_interner::DefaultStringInterner;
use crate::BytecodeContext;
use crate::ir::downgrade::{Downgrade as _, DowngradeContext};
use crate::ir::{
GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef,
ThunkId,
};
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, MaybeThunk>,
}
impl CodeState {
pub fn new(strings: &mut DefaultStringInterner) -> Self {
let global_env = crate::ir::new_global_env(strings);
let mut bytecode = Vec::with_capacity(Continuation::Illegal as usize * 2);
for phase in 0..=Continuation::Illegal as u8 {
bytecode.push(Op::DispatchCont as u8);
bytecode.push(phase);
}
Self {
sources: Vec::new(),
spans: Vec::new(),
thunk_count: 0,
bytecode,
global_env,
}
}
pub fn compile_bytecode<'ctx>(
&'ctx mut self,
source: Source,
extra_scope: Option<ExtraScope<'ctx>>,
runtime: &'ctx mut impl VmRuntimeCtx,
) -> Result<InstructionPtr> {
let mut compiler = CompilerCtx {
code: self,
runtime,
};
compiler.compile_bytecode(source, extra_scope)
}
}
impl VmCode for CodeState {
fn bytecode(&self) -> &[u8] {
&self.bytecode
}
fn compile_with_scope(
&mut self,
source: Source,
extra_scope: Option<fix_runtime::ExtraScope>,
runtime: &mut impl VmRuntimeCtx,
) -> Result<InstructionPtr> {
let extra = extra_scope.map(|s| match s {
fix_runtime::ExtraScope::ScopedImport { keys, slot_id } => {
ExtraScope::ScopedImport { keys, slot_id }
}
});
CodeState::compile_bytecode(self, source, extra, 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<ExtraScope>,
) -> Result<InstructionPtr> {
let root = self.downgrade(source, extra_scope)?;
let ip = crate::compile_bytecode(root.as_ref(), self);
Ok(ip)
}
fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
tracing::debug!("Parsing Nix expression");
self.code.sources.push(source.clone());
let root = rnix::Root::parse(&source.src);
handle_parse_error(root.errors(), source.clone()).map_or(Ok(()), Err)?;
tracing::debug!("Downgrading Nix expression");
let expr = root
.tree()
.expr()
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
let bump = Bump::new();
GhostToken::new(|token| {
let downgrade_ctx = DowngradeCtx::new(
&bump,
token,
self.runtime,
&self.code.global_env,
extra_scope.map(Into::into),
&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<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
fn intern_string(&mut self, s: &str) -> StringId {
self.runtime.intern_string(s)
}
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: Const) -> u32 {
use Const::*;
let val = match val {
Smi(x) => StaticValue::new(x),
Float(x) => StaticValue::new_float(x),
Bool(x) => StaticValue::new(x),
String(x) => StaticValue::new(x),
Path(x) => StaticValue::new(fix_runtime::Path(x)),
PrimOp {
id,
arity,
dispatch_ip,
} => StaticValue::new_primop(id, arity, dispatch_ip),
Null => StaticValue::default(),
};
self.runtime.add_const(val)
}
fn current_source_dir(&mut self) -> StringId {
let dir = self
.code
.sources
.last()
.expect("current_source not set")
.get_dir()
.to_string_lossy()
.into_owned();
self.runtime.intern_string(dir)
}
}
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
use rnix::ParseError::*;
match error {
Unexpected(range)
| UnexpectedExtra(range)
| UnexpectedWanted(_, range, _)
| UnexpectedDoubleBind(range)
| DuplicatedArgs(range, _) => Some(*range),
_ => None,
}
}
fn handle_parse_error<'a>(
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
source: Source,
) -> Option<Box<Error>> {
for err in errors {
if let Some(span) = parse_error_span(err) {
return Some(
Error::parse_error(err.to_string())
.with_source(source)
.with_span(span),
);
}
}
None
}
struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
bump: &'ir Bump,
token: GhostToken<'id>,
runtime: &'ctx mut R,
source: Source,
scopes: Vec<Scope<'ctx, 'id, 'ir>>,
with_stack: Vec<GhostRoMaybeThunkRef<'id, 'ir>>,
thunk_count: &'ctx mut usize,
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
}
impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
fn new(
bump: &'ir Bump,
token: GhostToken<'id>,
runtime: &'ctx mut R,
global: &'ctx HashMap<StringId, MaybeThunk>,
extra_scope: Option<Scope<'ctx, 'id, 'ir>>,
thunk_count: &'ctx mut usize,
source: Source,
) -> Self {
Self {
bump,
token,
runtime,
source,
scopes: std::iter::once(Scope::Global(global))
.chain(extra_scope)
.collect(),
thunk_count,
with_stack: Vec::new(),
thunk_scopes: vec![ThunkScope::new_in(bump)],
}
}
}
impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
for DowngradeCtx<'ctx, 'id, 'ir, R>
{
fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir> {
self.bump.alloc(GhostCell::new(expr).into())
}
fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir> {
use MaybeThunk::*;
let expr = (|| {
let expr = match *ir.borrow(&self.token) {
Ir::Builtin(x) => Builtin(x),
Ir::Int(x) => Int(x),
Ir::Float(x) => Float(x),
Ir::Bool(x) => Bool(x),
Ir::Str(x) => Str(x),
Ir::Arg { layer } => Arg { layer },
Ir::Builtins => Builtins,
Ir::Null => Null,
Ir::MaybeThunk(thunk) => return Some(thunk),
_ => return None,
};
Some(self.bump.alloc(GhostCell::new(expr).into()))
})();
if let Some(thunk) = expr {
return thunk;
}
let id = ThunkId(*self.thunk_count);
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
self.thunk_scopes
.last_mut()
.expect("no active cache scope")
.add_binding(id, ir);
self.bump.alloc(GhostCell::new(Thunk(id)).into())
}
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
self.runtime.intern_string(sym)
}
fn resolve_sym(&self, id: StringId) -> Symbol<'_> {
self.runtime.resolve_string(id).into()
}
fn lookup(
&mut self,
sym: StringId,
span: rnix::TextRange,
) -> Result<GhostRoMaybeThunkRef<'id, 'ir>> {
for scope in self.scopes.iter().rev() {
match scope {
&Scope::Global(global_scope) => {
if let Some(expr) = global_scope.get(&sym) {
return Ok(expr.into());
}
}
&Scope::Repl(repl_bindings) => {
if repl_bindings.contains(&sym) {
return Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into()));
}
}
&Scope::ScopedImport { ref keys, slot_id } => {
if keys.contains(&sym) {
return Ok(self.bump.alloc(
GhostCell::new(MaybeThunk::ScopedImportBinding { sym, slot_id }).into(),
));
}
}
Scope::Let(let_scope) => {
if let Some(&expr) = let_scope.get(&sym) {
return Ok(expr.into());
}
}
&Scope::Param {
sym: param_sym,
abs_layer,
} => {
if param_sym == sym {
let layers: u8 =
self.thunk_scopes.len().try_into().expect("scope too deep!");
let layer = layers - abs_layer;
return Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::Arg { layer }).into()));
}
}
}
}
if !self.with_stack.is_empty() {
let id = ThunkId(*self.thunk_count);
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
let mut namespaces =
bumpalo::collections::Vec::with_capacity_in(self.with_stack.len(), self.bump);
namespaces.extend(self.with_stack.iter().rev().copied());
let body = self
.bump
.alloc(GhostCell::new(Ir::WithLookup { sym, namespaces }).into());
self.thunk_scopes
.last_mut()
.expect("no active thunk scope")
.add_binding(id, body);
Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::Thunk(id)).into()))
} else {
Err(Error::downgrade_error(
format!("'{}' not found", self.resolve_sym(sym)),
self.get_current_source(),
span,
))
}
}
fn get_current_source(&self) -> Source {
self.source.clone()
}
fn with_let_scope<F, Ret>(&mut self, keys: &[StringId], f: F) -> Result<Ret>
where
F: FnOnce(
&mut Self,
) -> Result<(
bumpalo::collections::Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>,
Ret,
)>,
{
let base = *self.thunk_count;
*self.thunk_count = self
.thunk_count
.checked_add(keys.len())
.expect("thunk id overflow");
let handles = (base..base + keys.len())
.map(|id| {
&*self
.bump
.alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(id))))
})
.collect::<Vec<_>>();
let scope = keys.iter().copied().zip(handles.iter().copied()).collect();
self.scopes.push(Scope::Let(scope));
let (vals, ret) = { f(self)? };
self.scopes.pop();
assert_eq!(keys.len(), vals.len());
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
for (i, (val, handle)) in vals.into_iter().zip(handles).enumerate() {
let thunk = *val.borrow(&self.token);
*handle.borrow_mut(&mut self.token) = thunk;
let id = ThunkId(base + i);
let ir_ref = self
.bump
.alloc(GhostCell::new(Ir::MaybeThunk(handle.into())).into());
scope.add_binding(id, ir_ref);
}
Ok(ret)
}
fn with_param_scope<F, Ret>(&mut self, sym: StringId, f: F) -> Ret
where
F: FnOnce(&mut Self) -> Ret,
{
self.scopes.push(Scope::Param {
sym,
abs_layer: self.thunk_scopes.len().try_into().expect("scope too deep!"),
});
let mut guard = ScopeGuard { ctx: self };
f(guard.as_ctx())
}
fn with_with_scope<F, Ret>(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> Ret
where
F: FnOnce(&mut Self) -> Ret,
{
self.with_stack.push(namespace);
let ret = f(self);
self.with_stack.pop();
ret
}
fn with_thunk_scope<F, Ret>(
&mut self,
f: F,
) -> (
Ret,
bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
)
where
F: FnOnce(&mut Self) -> Ret,
{
if self.thunk_scopes.len() == u8::MAX as usize {
panic!("scope too deep!");
}
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
let ret = f(self);
(
ret,
self.thunk_scopes
.pop()
.expect("no thunk scope left???")
.bindings,
)
}
fn bump(&self) -> &'ir bumpalo::Bump {
self.bump
}
}
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
.thunk_scopes
.pop()
.expect("no thunk scope left???")
.bindings;
Ok(Ir::freeze(
self.new_expr(Ir::TopLevel { body, thunks }),
self.token,
))
}
}
struct ThunkScope<'id, 'ir> {
bindings: bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
}
impl<'id, 'ir> ThunkScope<'id, 'ir> {
fn new_in(bump: &'ir Bump) -> Self {
Self {
bindings: bumpalo::collections::Vec::new_in(bump),
}
}
fn add_binding(&mut self, id: ThunkId, ir: GhostRoIrRef<'id, 'ir>) {
self.bindings.push((id, ir));
}
}
enum Scope<'ctx, 'id, 'ir> {
Global(&'ctx HashMap<StringId, MaybeThunk>),
Repl(&'ctx HashSet<StringId>),
ScopedImport {
keys: HashSet<StringId>,
#[allow(dead_code)]
slot_id: u32,
},
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
Param {
sym: StringId,
abs_layer: u8,
},
}
pub enum ExtraScope<'ctx> {
Repl(&'ctx HashSet<StringId>),
ScopedImport {
keys: HashSet<StringId>,
slot_id: u32,
},
}
impl<'ctx> From<ExtraScope<'ctx>> for Scope<'ctx, '_, '_> {
fn from(value: ExtraScope<'ctx>) -> Self {
use ExtraScope::*;
match value {
ScopedImport { keys, slot_id } => Scope::ScopedImport { keys, slot_id },
Repl(scope) => Scope::Repl(scope),
}
}
}
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>,
}
impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> Drop for ScopeGuard<'a, 'ctx, 'id, 'ir, R> {
fn drop(&mut self) {
self.ctx.scopes.pop();
}
}
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
}
}
struct OwnedIr {
_bump: Bump,
ir: RawIrRef<'static>,
}
impl OwnedIr {
fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
}
}
+7 -10
View File
@@ -3,10 +3,8 @@ use std::marker::PhantomData;
use bumpalo::Bump;
use bumpalo::collections::Vec;
use fix_builtins::{BUILTINS, BuiltinId};
use fix_common::StringId;
use fix_lang::{BUILTINS, BuiltinId, StringId};
use ghost_cell::{GhostCell, GhostToken};
use num_enum::TryFromPrimitive as _;
use rnix::{TextRange, ast};
use string_interner::DefaultStringInterner;
@@ -92,7 +90,7 @@ pub enum MaybeThunk {
BuiltinConst(StringId),
Builtins,
ReplBinding(StringId),
ScopedImportBinding(StringId),
ScopedImportBinding { slot_id: u32, sym: StringId },
}
pub trait Ref<'ir> {
@@ -213,17 +211,16 @@ pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
},
MaybeThunk(R::MaybeThunkRef),
ReplBinding(StringId),
ScopedImportBinding(StringId),
ScopedImportBinding {
sym: StringId,
slot_id: u32,
},
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ThunkId(pub usize);
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SpanId(pub u32);
/// Represents a key in an attribute path.
#[allow(unused)]
#[derive(Debug)]
@@ -295,7 +292,7 @@ pub fn new_global_env(
global_env.insert(builtins_sym, MaybeThunk::Builtins);
for (idx, &(name, _)) in BUILTINS.iter().enumerate() {
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
let id = BuiltinId::try_from(idx as u8).expect("infallible");
let name = StringId(strings.get_or_intern(name));
global_env.insert(name, MaybeThunk::Builtin(id));
}
@@ -1,7 +1,6 @@
use bumpalo::collections::{CollectIn, Vec};
use fix_builtins::BuiltinId;
use fix_common::Symbol;
use fix_error::{Error, Result, Source};
use fix_lang::{BuiltinId, Symbol};
use hashbrown::HashSet;
use hashbrown::hash_map::Entry;
use rnix::TextRange;
@@ -1,14 +1,14 @@
use fix_builtins::{BUILTINS, BuiltinId};
use fix_common::StringId;
use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
use fix_bytecode::{Const, Continuation, InstructionPtr, Op, OperandType};
use fix_lang::{BUILTINS, StringId};
use hashbrown::HashMap;
use num_enum::TryFromPrimitive;
use rnix::TextRange;
use string_interner::Symbol as _;
pub mod disassembler;
pub struct InstructionPtr(pub usize);
mod context;
pub mod ir;
pub use context::{CodeState, ExtraScope};
pub use fix_bytecode::disassembler;
pub use ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
pub trait BytecodeContext {
fn intern_string(&mut self, s: &str) -> StringId;
@@ -17,86 +17,6 @@ pub trait BytecodeContext {
fn get_code_mut(&mut self) -> &mut Vec<u8>;
fn add_constant(&mut self, val: Const) -> u32;
fn current_source_dir(&mut self) -> StringId;
fn current_scope_slot(&self) -> Option<u32>;
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
#[allow(clippy::enum_variant_names)]
pub enum Op {
PushSmi,
PushBigInt,
PushFloat,
PushString,
PushNull,
PushTrue,
PushFalse,
LoadLocal,
LoadOuter,
StoreLocal,
AllocLocals,
MakeThunk,
MakeClosure,
MakePatternClosure,
Call,
DispatchPrimOp,
MakeAttrs,
MakeEmptyAttrs,
SelectStatic,
SelectDynamic,
HasAttrPathStatic,
HasAttrPathDynamic,
HasAttrStatic,
HasAttrDynamic,
HasAttrResolve,
JumpIfSelectSucceeded,
JumpIfSelectFailed,
MakeList,
MakeEmptyList,
OpAdd,
OpSub,
OpMul,
OpDiv,
OpEq,
OpNeq,
OpLt,
OpGt,
OpLeq,
OpGeq,
OpConcat,
OpUpdate,
OpNeg,
OpNot,
JumpIfFalse,
JumpIfTrue,
Jump,
CoerceToString,
ConcatStrings,
ResolvePath,
Assert,
LookupWith,
LoadBuiltins,
LoadBuiltin,
LoadReplBinding,
LoadScopedBinding,
Return,
Illegal,
}
struct ScopeInfo {
@@ -109,39 +29,6 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
scope_stack: Vec<ScopeInfo>,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum OperandType {
Const,
BigInt,
Local,
BuiltinConst,
Builtins,
ReplBinding,
ScopedImportBinding,
}
pub enum Const {
Smi(i32),
Float(f64),
Bool(bool),
String(StringId),
Path(StringId),
PrimOp {
id: BuiltinId,
arity: u8,
dispatch_ip: u32,
},
Null,
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum AttrKeyType {
Static,
Dynamic,
}
pub enum InlineOperand {
Const(Const),
BigInt(i64),
@@ -149,7 +36,7 @@ pub enum InlineOperand {
BuiltinConst(StringId),
Builtins,
ReplBinding(StringId),
ScopedImportBinding(StringId),
ScopedImportBinding { id: StringId, slot_id: u32 },
}
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
@@ -193,13 +80,15 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
InlineOperand::Const(Const::PrimOp {
id,
arity,
dispatch_ip: id.entry_phase().ip(),
dispatch_ip: Continuation::entry_for_builtin(id).ip(),
})
}
BuiltinConst(id) => InlineOperand::BuiltinConst(id),
Builtins => InlineOperand::Builtins,
ReplBinding(id) => InlineOperand::ReplBinding(id),
ScopedImportBinding(id) => InlineOperand::ScopedImportBinding(id),
ScopedImportBinding { slot_id, sym: id } => {
InlineOperand::ScopedImportBinding { slot_id, id }
}
}
}
@@ -232,13 +121,9 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_u8(OperandType::ReplBinding as u8);
self.emit_str_id(id);
}
ScopedImportBinding(id) => {
ScopedImportBinding { id, slot_id } => {
self.emit_u8(OperandType::ScopedImportBinding as u8);
let slot = self
.ctx
.current_scope_slot()
.expect("ScopedImportBinding outside scoped compilation");
self.emit_u32(slot);
self.emit_u32(slot_id);
self.emit_str_id(id);
}
}
@@ -555,14 +440,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::LoadReplBinding);
self.emit_str_id(name);
}
&Ir::ScopedImportBinding(name) => {
&Ir::ScopedImportBinding { sym, slot_id } => {
self.emit_op(Op::LoadScopedBinding);
let slot = self
.ctx
.current_scope_slot()
.expect("ScopedImportBinding outside scoped compilation");
self.emit_u32(slot);
self.emit_str_id(name);
self.emit_u32(slot_id);
self.emit_str_id(sym);
}
Ir::WithLookup { sym, namespaces } => {
// counter
@@ -629,14 +510,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::LoadReplBinding);
self.emit_str_id(name);
}
ScopedImportBinding(name) => {
ScopedImportBinding { slot_id, sym } => {
self.emit_op(Op::LoadScopedBinding);
let slot = self
.ctx
.current_scope_slot()
.expect("ScopedImportBinding outside scoped compilation");
self.emit_u32(slot);
self.emit_str_id(name);
self.emit_u32(slot_id);
self.emit_str_id(sym);
}
}
}
@@ -775,7 +652,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_attrset(
&mut self,
stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>,
stcs: &ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>,
dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)],
) {
if stcs.is_empty() && dyns.is_empty() {
+1 -1
View File
@@ -5,5 +5,5 @@ edition = "2024"
[dependencies]
miette = { version = "7.6", features = ["fancy"] }
thiserror = "2.0"
rnix = { workspace = true }
thiserror = "2.0"
@@ -1,9 +1,10 @@
[package]
name = "fix-common"
name = "fix-lang"
version = "0.1.0"
edition = "2024"
[dependencies]
gc-arena = { workspace = true }
string-interner = { workspace = true }
ere = { workspace = true }
gc-arena = { workspace = true }
num_enum = { workspace = true }
string-interner = { workspace = true }
@@ -4,6 +4,128 @@ use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::ops::{Deref, DerefMut};
use gc_arena::Collect;
use num_enum::TryFromPrimitive;
macro_rules! define_builtins {
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
pub const BUILTINS: &[(&str, u8)] = &[
$(($name, $arity),)*
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)]
#[repr(u8)]
#[collect(require_static)]
pub enum BuiltinId {
$($variant,)*
}
};
}
define_builtins! {
("abort", Abort, 1),
("__add", Add, 2),
("__addErrorContext", AddErrorContext, 2),
("__all", All, 2),
("__any", Any, 2),
("__appendContext", AppendContext, 2),
("__attrNames", AttrNames, 1),
("__attrValues", AttrValues, 1),
("baseNameOf", BaseNameOf, 1),
("__bitAnd", BitAnd, 2),
("__bitOr", BitOr, 2),
("__bitXor", BitXor, 2),
("break", Break, 1),
("__catAttrs", CatAttrs, 2),
("__ceil", Ceil, 1),
("__compareVersions", CompareVersions, 2),
("__concatLists", ConcatLists, 1),
("__concatMap", ConcatMap, 2),
("__concatStringsSep", ConcatStringsSep, 2),
("__convertHash", ConvertHash, 1),
("__deepSeq", DeepSeq, 2),
("derivation", Derivation, 1),
("derivationStrict", DerivationStrict, 1),
("dirOf", DirOf, 1),
("__div", Div, 2),
("__elem", Elem, 2),
("__elemAt", ElemAt, 2),
("fetchGit", FetchGit, 1),
("fetchMercurial", FetchMercurial, 1),
("fetchTarball", FetchTarball, 1),
("fetchTree", FetchTree, 1),
("__fetchurl", FetchUrl, 1),
("__filter", Filter, 2),
("__filterSource", FilterSource, 2),
("__findFile", FindFile, 2),
("__floor", Floor, 1),
("__foldl'", FoldlStrict, 3),
("__fromJSON", FromJSON, 1),
("fromTOML", FromTOML, 1),
("__functionArgs", FunctionArgs, 1),
("__genList", GenList, 2),
("__genericClosure", GenericClosure, 1),
("__getAttr", GetAttr, 2),
("__getContext", GetContext, 1),
("__getEnv", GetEnv, 1),
("__groupBy", GroupBy, 2),
("__hasAttr", HasAttr, 2),
("__hasContext", HasContext, 1),
("__hashFile", HashFile, 2),
("__hashString", HashString, 2),
("__head", Head, 1),
("import", Import, 1),
("__intersectAttrs", IntersectAttrs, 2),
("__isAttrs", IsAttrs, 1),
("__isBool", IsBool, 1),
("__isFloat", IsFloat, 1),
("__isFunction", IsFunction, 1),
("__isInt", IsInt, 1),
("__isList", IsList, 1),
("isNull", IsNull, 1),
("__isPath", IsPath, 1),
("__isString", IsString, 1),
("__length", Length, 1),
("__lessThan", LessThan, 2),
("__listToAttrs", ListToAttrs, 1),
("map", Map, 2),
("__mapAttrs", MapAttrs, 2),
("__match", Match, 2),
("__mul", Mul, 2),
("__parseDrvName", ParseDrvName, 1),
("__partition", Partition, 2),
("__path", Path, 1),
("__pathExists", PathExists, 1),
("placeholder", Placeholder, 1),
("__readDir", ReadDir, 1),
("__readFile", ReadFile, 1),
("__readFileType", ReadFileType, 1),
("removeAttrs", RemoveAttrs, 2),
("__replaceStrings", ReplaceStrings, 3),
("scopedImport", ScopedImport, 2),
("__seq", Seq, 2),
("__sort", Sort, 2),
("__split", Split, 2),
("__splitVersion", SplitVersion, 1),
("__storePath", StorePath, 1),
("__stringLength", StringLength, 1),
("__sub", Sub, 2),
("__substring", Substring, 3),
("__tail", Tail, 1),
("throw", Throw, 1),
("__toFile", ToFile, 2),
("__toJSON", ToJSON, 1),
("__toPath", ToPath, 1),
("toString", ToString, 1),
("__toXML", ToXML, 1),
("__trace", Trace, 2),
("__tryEval", TryEval, 1),
("__typeOf", TypeOf, 1),
("__unsafeDiscardStringContext", UnsafeDiscardStringContext, 1),
("__unsafeDiscardOutputDependency", UnsafeDiscardOutputDependency, 1),
("__unsafeGetAttrPos", UnsafeGetAttrPos, 2),
("__warn", Warn, 2),
("__zipAttrsWith", ZipAttrsWith, 2),
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)]
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "fix-macros"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
manyhow = "0.11"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full", "visit"] }
+36
View File
@@ -0,0 +1,36 @@
extern crate proc_macro;
#[proc_macro]
pub fn unelide_lifetimes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::visit_mut::VisitMut;
struct Input {
lt: syn::Lifetime,
ty: syn::Type,
}
impl Parse for Input {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lt: syn::Lifetime = input.parse()?;
let _: syn::Token!(;) = input.parse()?;
let ty: syn::Type = input.parse()?;
Ok(Self { lt, ty })
}
}
struct UnelideLifetimes(syn::Lifetime);
impl VisitMut for UnelideLifetimes {
fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) {
if i.ident == "_" {
*i = self.0.clone();
}
}
}
let mut input = syn::parse_macro_input!(input as Input);
UnelideLifetimes(input.lt).visit_type_mut(&mut input.ty);
input.ty.to_token_stream().into()
}
-17
View File
@@ -1,17 +0,0 @@
[package]
name = "fix-primops"
version = "0.1.0"
edition = "2024"
[dependencies]
gc-arena = { workspace = true }
hashbrown = { workspace = true }
num_enum = { workspace = true }
smallvec = { workspace = true }
string-interner = { workspace = true }
fix-abstract-vm = { path = "../fix-abstract-vm" }
fix-builtins = { path = "../fix-builtins" }
fix-codegen = { path = "../fix-codegen" }
fix-common = { path = "../fix-common" }
fix-error = { path = "../fix-error" }
-87
View File
@@ -1,87 +0,0 @@
mod context;
mod control;
mod conv;
mod eq;
mod io;
mod list;
mod path;
pub use context::*;
pub use control::*;
pub use conv::*;
pub use eq::*;
use fix_abstract_vm::{BytecodeReader, Machine, Step, VmRuntimeCtx};
use fix_builtins::PrimOpPhase;
use fix_error::Error;
use gc_arena::Mutation;
pub use io::*;
pub use list::*;
pub use path::*;
#[allow(clippy::too_many_lines)]
pub fn dispatch_primop<'gc, M: Machine<'gc>>(
m: &mut M,
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(phase_disc) else {
return m.finish_err(Error::eval_error("invalid primop phase"));
};
match phase {
Abort => abort(m, ctx, reader, mc),
DeepSeq => deep_seq_force_top(m, reader, mc),
DeepSeqPush => deep_seq_push(m, reader, mc),
DeepSeqLoop => deep_seq_loop(m, reader, mc),
Seq => seq(m, reader, mc),
FilterForceList => filter_force_list(m, reader, mc),
FilterCallPred => filter_call_pred(m, reader, mc),
FilterCheck => filter_check(m, reader, mc),
FoldlStrict => foldl_strict_entry(m, reader, mc),
FoldlStrictEmpty => foldl_strict_empty(m, reader, mc),
FoldlStrictCall1 => foldl_strict_call1(m, reader, mc),
FoldlStrictCall2 => foldl_strict_call2(m, reader, mc),
FoldlStrictUpdate => foldl_strict_update(m, reader, mc),
ForceResultShallow => force_result_shallow(m, ctx, reader, mc),
ForceResultShallowPush => force_result_shallow_push(m, ctx, reader, mc),
ForceResultShallowLoop => force_result_shallow_loop(m, reader, mc),
ForceResultDeepFinish => force_result_deep_finish(m, ctx, reader, mc),
EqStep => eq_step(m, reader, mc),
EqForce => eq_force(m, ctx, reader, mc),
CallPattern => call_pattern(m, ctx, reader, mc),
CallFunctor1 => call_functor_1(m, reader, mc),
CallFunctor2 => call_functor_2(m, reader, mc),
Import => import(m, ctx, reader, mc),
ImportFinalize => import_finalize(m, ctx, reader),
ScopedImport => scoped_import(m, ctx, reader, mc),
ScopedImportFinalize => scoped_import_finalize(m, ctx, reader, mc),
PathExists => path_exists(m, ctx, reader, mc),
ToPath => to_path(m, ctx, reader, mc),
IsPath => is_path(m, reader, mc),
ToString => to_string(m, ctx, reader, mc),
TypeOf => type_of(m, ctx, reader, mc),
HasContext => has_context(m, ctx, reader, mc),
GetContext => get_context(m, ctx, reader, mc),
AppendContext => append_context(m, ctx, reader, mc),
AppendContextLoop => append_context_loop(m, ctx, reader, mc),
AppendContextEntryForced => append_context_entry_forced(m, ctx, reader, mc),
AppendContextOutputsForced => append_context_outputs_forced(m, ctx, reader, mc),
AppendContextOutputElementLoop => append_context_output_element_loop(m, ctx, reader, mc),
AppendContextOutputElementForced => append_context_output_element_forced(m, ctx, reader, mc),
UnsafeDiscardStringContext => unsafe_discard_string_context(m, ctx, reader, mc),
UnsafeDiscardOutputDependency => unsafe_discard_output_dependency(m, ctx, reader, mc),
phase => todo!("primop phase {phase:?}"),
}
}
-166
View File
@@ -1,166 +0,0 @@
use fix_abstract_vm::{
BytecodeReader, List, Machine, MachineExt, NixType, Step, StrictValue, Value,
};
use fix_builtins::PrimOpPhase;
use gc_arena::Mutation;
pub fn filter_force_list<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(0, reader, mc)?;
let list = match m.peek_forced(0).expect_gc::<List>() {
Ok(list) => list,
Err(got) => return m.finish_type_err(NixType::List, got),
};
if list.inner.borrow().is_empty() {
let val = m.pop();
return m.return_from_primop(val, reader);
}
// prepare stack layout: [ pred list idx acc ]
m.push(Value::new_inline(0));
m.push(Value::new_gc(List::new_gc(mc)));
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
Step::Continue(())
}
pub fn filter_call_pred<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(3, reader, mc)?;
let pred = m.peek_forced(3);
#[allow(clippy::unwrap_used)]
let idx = m.peek(1).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let elem = m.peek_forced(2).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
m.push(pred.relax());
m.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize)
}
pub fn filter_check<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let ret = m.force_and_retry::<bool>(reader, mc)?;
#[allow(clippy::unwrap_used)]
let idx = m.peek(1).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(2).as_gc::<List>().unwrap();
let list = list.inner.borrow();
#[allow(clippy::unwrap_used)]
let acc = m.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 = m.pop();
let _ = m.pop(); // idx
let _ = m.pop(); // list
let _ = m.pop(); // pred
return m.return_from_primop(acc, reader);
}
m.replace(1, Value::new_inline(idx + 1));
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
Step::Continue(())
}
// foldl' op nul list
//
// Stack layouts across phases:
// Entry: [op, nul, list]
// Empty: [op, nul]
// Call1: [op, list, idx, acc]
// Call2: [op, list, idx, acc, intermediate]
// Update: [op, list, idx, acc, result]
pub fn foldl_strict_entry<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(0, reader, mc)?;
let list_val = m.peek_forced(0);
let Some(list) = list_val.as_gc::<List>() else {
return m.finish_type_err(NixType::List, list_val.ty());
};
if list.inner.borrow().is_empty() {
let _ = m.pop(); // list
reader.set_pc(PrimOpPhase::FoldlStrictEmpty.ip() as usize);
return Step::Continue(());
}
let list_val = m.pop();
let nul_val = m.pop();
m.push(list_val);
m.push(Value::new_inline(0i32));
m.push(nul_val);
reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize);
Step::Continue(())
}
pub fn foldl_strict_empty<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let nul = m.force_and_retry::<StrictValue>(reader, mc)?;
let _ = m.pop(); // op
m.return_from_primop(nul.relax(), reader)
}
pub fn foldl_strict_call1<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(3, reader, mc)?;
let op = m.peek_forced(3);
let acc = m.peek(0);
m.push(op.relax());
m.call(reader, mc, acc, PrimOpPhase::FoldlStrictCall2.ip() as usize)
}
pub fn foldl_strict_call2<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
#[allow(clippy::unwrap_used)]
let idx = m.peek(2).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(3).as_gc::<List>().unwrap();
let elem = list.inner.borrow()[idx as usize];
m.call(
reader,
mc,
elem,
PrimOpPhase::FoldlStrictUpdate.ip() as usize,
)
}
pub fn foldl_strict_update<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
_mc: &Mutation<'gc>,
) -> Step {
let result = m.pop();
m.replace(0, result);
#[allow(clippy::unwrap_used)]
let idx = m.peek(1).as_inline::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(2).as_gc::<List>().unwrap();
let len = list.inner.borrow().len();
if (idx as usize) + 1 == len {
let acc = m.pop();
let _ = m.pop(); // idx
let _ = m.pop(); // list
let _ = m.pop(); // op
return m.return_from_primop(acc, reader);
}
m.replace(1, Value::new_inline(idx + 1));
reader.set_pc(PrimOpPhase::FoldlStrictCall1.ip() as usize);
Step::Continue(())
}
@@ -1,18 +1,16 @@
[package]
name = "fix-abstract-vm"
name = "fix-runtime"
version = "0.1.0"
edition = "2024"
[dependencies]
gc-arena = { workspace = true }
hashbrown = { workspace = true }
num_enum = { workspace = true }
smallvec = { workspace = true }
string-interner = { workspace = true }
likely_stable = { workspace = true }
sptr = "0.3"
string-interner = { workspace = true }
fix-builtins = { path = "../fix-builtins" }
fix-codegen = { path = "../fix-codegen" }
fix-common = { path = "../fix-common" }
fix-bytecode = { path = "../fix-bytecode" }
fix-error = { path = "../fix-error" }
fix-lang = { path = "../fix-lang" }
fix-macros = { path = "../fix-macros" }
@@ -1,4 +1,4 @@
use fix_common::StringId;
use fix_lang::StringId;
use gc_arena::{Gc, Mutation};
use crate::{
@@ -63,7 +63,7 @@ macro_rules! impl_forced_inline {
) -> Step {
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
let v = m.peek_forced(base_depth);
if v.as_inline::<$ty>().is_none() {
if v.downcast::<$ty>().is_none() {
let _: Step = m.finish_type_err($nix_ty, v.ty());
return Step::Break(Break::Done);
}
@@ -73,7 +73,7 @@ macro_rules! impl_forced_inline {
#[inline(always)]
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
m.pop_forced()
.as_inline::<$ty>()
.downcast::<$ty>()
.expect("type checked in force_and_check")
}
}
@@ -97,7 +97,7 @@ macro_rules! impl_forced_gc {
) -> Step {
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
let v = m.peek_forced(base_depth);
if v.as_gc::<$ty>().is_none() {
if v.downcast::<$ty>().is_none() {
let _: Step = m.finish_type_err($nix_ty, v.ty());
return Step::Break(Break::Done);
}
@@ -107,7 +107,7 @@ macro_rules! impl_forced_gc {
#[inline(always)]
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
m.pop_forced()
.as_gc::<$ty>()
.downcast::<$ty>()
.expect("type checked in force_and_check")
}
}
@@ -173,7 +173,7 @@ impl<'gc> Forced<'gc> for f64 {
) -> Step {
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
let v = m.peek_forced(base_depth);
if v.as_float().is_none() {
if v.downcast_float().is_none() {
let _: Step = m.finish_type_err(NixType::Float, v.ty());
return Step::Break(Break::Done);
}
@@ -183,7 +183,7 @@ impl<'gc> Forced<'gc> for f64 {
#[inline(always)]
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
m.pop_forced()
.as_float()
.downcast_float()
.expect("type checked in force_and_check")
}
}
@@ -1,10 +1,11 @@
use fix_codegen::InstructionPtr;
use fix_common::StringId;
use fix_bytecode::InstructionPtr;
use fix_error::Source;
use fix_lang::{self, BUILTINS, StringId};
use hashbrown::HashSet;
use crate::{
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp, StaticValue, StrictValue, StringContext, Thunk, ThunkState, Value
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp,
StaticValue, StrictValue, StringContext, Thunk, ThunkState, Value,
};
pub trait VmContext {
@@ -38,15 +39,15 @@ pub trait VmRuntimeCtxExt: VmRuntimeCtx {
/// Returns the string context attached to `val`, or `&[]` if `val` is
/// either a non-string or a string without context.
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext;
fn convert_value(&self, val: Value) -> fix_common::Value;
fn convert_value(&self, val: Value) -> fix_lang::Value;
}
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>() {
if let Some(sid) = val.downcast::<StringId>() {
Some(self.resolve_string(sid))
} else {
val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str())
val.downcast::<NixString>().map(|ns| ns.as_ref().as_str())
}
}
@@ -55,7 +56,7 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
/// would coerce a path to a string (string interpolation, file IO
/// builtins, etc.).
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
if let Some(p) = val.as_inline::<Path>() {
if let Some(p) = val.downcast::<Path>() {
Some(self.resolve_string(p.0))
} else {
self.get_string(val)
@@ -66,9 +67,9 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
&'a mut self,
val: StrictValue<'gc>,
) -> std::result::Result<StringId, NixType> {
if let Some(sid) = val.as_inline::<StringId>() {
if let Some(sid) = val.downcast::<StringId>() {
Ok(sid)
} else if let Some(s) = val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str()) {
} else if let Some(s) = val.downcast::<NixString>().map(|ns| ns.as_ref().as_str()) {
Ok(self.intern_string(s))
} else {
Err(val.ty())
@@ -76,43 +77,43 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
}
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext {
if let Some(ns) = val.as_gc::<NixString>() {
if let Some(ns) = val.downcast::<NixString>() {
ns.as_ref().context()
} else {
StringContext::empty()
}
}
fn convert_value(&self, val: Value) -> fix_common::Value {
fn convert_value(&self, val: Value) -> fix_lang::Value {
self.convert_value_with_seen(val, &mut HashSet::new())
}
}
pub(crate) trait ConvertValueWithSeen: VmRuntimeCtx {
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value;
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_lang::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>() {
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_lang::Value {
use fix_lang::Value;
if let Some(i) = val.downcast::<i32>() {
Value::Int(i as i64)
} else if let Some(gc_i) = val.as_gc::<i64>() {
} else if let Some(gc_i) = val.downcast::<i64>() {
Value::Int(*gc_i)
} else if let Some(f) = val.as_float() {
} else if let Some(f) = val.downcast_float() {
Value::Float(f)
} else if let Some(b) = val.as_inline::<bool>() {
} else if let Some(b) = val.downcast::<bool>() {
Value::Bool(b)
} else if val.is::<Null>() {
Value::Null
} else if let Some(sid) = val.as_inline::<StringId>() {
} else if let Some(sid) = val.downcast::<StringId>() {
let s = self.resolve_string(sid).to_owned();
Value::String(s)
} else if let Some(ns) = val.as_gc::<NixString>() {
} else if let Some(ns) = val.downcast::<NixString>() {
Value::String(ns.as_str().to_owned())
} else if let Some(p) = val.as_inline::<Path>() {
} else if let Some(p) = val.downcast::<Path>() {
Value::Path(self.resolve_string(p.0).to_owned())
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
} else if let Some(attrs) = val.downcast::<AttrSet>() {
let bits = val.to_bits();
if attrs.entries.is_empty() {
return Value::AttrSet(Default::default());
@@ -124,10 +125,10 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
for &(key, val) in attrs.entries.iter() {
let key = self.resolve_string(key).to_owned();
let converted = self.convert_value_with_seen(val, seen);
map.insert(fix_common::Symbol::from(key), converted);
map.insert(fix_lang::Symbol::from(key), converted);
}
Value::AttrSet(fix_common::AttrSet::new(map))
} else if let Some(list) = val.as_gc::<List>() {
Value::AttrSet(fix_lang::AttrSet::new(map))
} else if let Some(list) = val.downcast::<List>() {
let bits = val.to_bits();
if list.inner.borrow().is_empty() {
return Value::List(Default::default());
@@ -142,20 +143,20 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
.copied()
.map(|v| self.convert_value_with_seen(v, seen))
.collect();
Value::List(fix_common::List::new(items))
Value::List(fix_lang::List::new(items))
} else if val.is::<Closure>() {
Value::Func
} else if let Some(thunk) = val.as_gc::<Thunk>() {
} else if let Some(thunk) = val.downcast::<Thunk>() {
if let ThunkState::Evaluated(v) = *thunk.borrow() {
self.convert_value_with_seen(v.relax(), seen)
} else {
Value::Thunk
}
} else if let Some(primop) = val.as_inline::<PrimOp>() {
let name = fix_builtins::BUILTINS[primop.id as usize].0;
} else if let Some(primop) = val.downcast::<PrimOp>() {
let name = 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;
} else if let Some(app) = val.downcast::<PrimOpApp>() {
let name = BUILTINS[app.primop.id as usize].0;
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
} else {
Value::Null
@@ -1,5 +1,4 @@
mod boxing;
mod bytecode_reader;
mod forced;
mod host;
mod machine;
@@ -9,7 +8,7 @@ mod state;
mod string_context;
mod value;
pub use bytecode_reader::*;
pub use fix_bytecode::{BytecodeReader, OperandData};
pub use forced::*;
pub use host::*;
pub use machine::*;
@@ -1,8 +1,8 @@
use std::ops::ControlFlow;
use std::path::{Path, PathBuf};
use fix_common::StringId;
use fix_error::Error;
use fix_lang::{self, StringId};
use gc_arena::Mutation;
use crate::{
@@ -26,11 +26,15 @@ use crate::{
/// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`)
pub trait Machine<'gc> {
fn push(&mut self, val: Value<'gc>);
#[must_use]
fn pop(&mut self) -> Value<'gc>;
#[must_use]
fn peek(&self, depth: usize) -> Value<'gc>;
#[must_use]
fn peek_forced(&self, depth: usize) -> StrictValue<'gc>;
fn pop_forced(&mut self) -> StrictValue<'gc>;
fn replace(&mut self, depth: usize, val: Value<'gc>);
fn drop_n(&mut self, depth: usize);
fn stack_len(&self) -> usize;
fn force_slot_to_pc(
@@ -96,7 +100,7 @@ pub trait Machine<'gc> {
cur.borrow().locals[idx as usize]
}
fn finish_ok(&mut self, val: fix_common::Value) -> Step;
fn finish_ok(&mut self, val: fix_lang::Value) -> Step;
fn finish_err(&mut self, err: Box<Error>) -> Step;
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step;
@@ -1,6 +1,7 @@
use fix_bytecode::OperandData;
use gc_arena::{Gc, Mutation};
use crate::{AttrSet, Machine, OperandData, Value};
use crate::{AttrSet, Machine, Value, VmRuntimeCtx};
/// Resolve a decoded operand into a runtime [`Value`].
///
@@ -11,21 +12,27 @@ use crate::{AttrSet, Machine, OperandData, Value};
pub fn resolve_operand<'gc, M: Machine<'gc>>(
op: &OperandData,
mc: &Mutation<'gc>,
ctx: &impl VmRuntimeCtx,
m: &M,
) -> Value<'gc> {
use OperandData::*;
match *op {
Const(sv) => sv.into(),
BigInt(val) => Value::new_gc(Gc::new(mc, val)),
Const(id) => ctx.get_const(id).into(),
BigInt(val) => Value::new(Gc::new(mc, val)),
Local { layer, idx } => m.local(layer, idx),
#[allow(clippy::unwrap_used)]
BuiltinConst(id) => m.builtins().as_gc::<AttrSet>().unwrap().lookup(id).unwrap(),
BuiltinConst(id) => m
.builtins()
.downcast::<AttrSet>()
.unwrap()
.lookup(id)
.unwrap(),
Builtins => m.builtins(),
ReplBinding(_id) => todo!(),
ScopedImportBinding { slot_id, name } => {
let scope = m.scope_slot(slot_id);
#[allow(clippy::unwrap_used)]
let attrs = scope.as_gc::<AttrSet>().expect("scope must be attrset");
let attrs = scope.downcast::<AttrSet>().expect("scope must be attrset");
#[allow(clippy::unwrap_used)]
attrs.lookup(name).expect("scoped binding not found")
}
@@ -1,12 +1,12 @@
use std::ops::ControlFlow;
use std::path::PathBuf;
use fix_common::StringId;
use fix_error::Error;
use fix_lang::StringId;
use gc_arena::{Collect, Gc};
use hashbrown::HashSet;
use crate::{GcEnv, StaticValue, Thunk};
use crate::{GcEnv, Thunk};
#[allow(dead_code)]
pub enum VmError {
@@ -87,13 +87,3 @@ pub enum ExtraScope {
slot_id: u32,
},
}
pub enum OperandData {
Const(StaticValue),
BigInt(i64),
Local { layer: u8, idx: u32 },
BuiltinConst(StringId),
Builtins,
ReplBinding(StringId),
ScopedImportBinding { slot_id: u32, name: StringId },
}
@@ -89,7 +89,7 @@ impl<'a> IntoIterator for &'a mut StringContext {
impl FromIterator<StringContextElem> for StringContext {
fn from_iter<T: IntoIterator<Item = StringContextElem>>(iter: T) -> Self {
Self {
data: iter.into_iter().collect()
data: iter.into_iter().collect(),
}
}
}
@@ -1,17 +1,13 @@
#![allow(dead_code)]
use std::cell::RefCell;
use std::fmt;
use std::marker::PhantomData;
use std::mem::size_of;
use std::ops::Deref;
use fix_builtins::BuiltinId;
use fix_common::*;
use fix_lang::*;
use gc_arena::barrier::Unlock;
use gc_arena::collect::Trace;
use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock};
use num_enum::TryFromPrimitive;
use smallvec::SmallVec;
use string_interner::Symbol;
use string_interner::symbol::SymbolU32;
@@ -23,37 +19,81 @@ mod private {
pub trait Cealed {}
}
pub trait ValueVariant: private::Cealed {
type Ty<'gc>: 'gc;
fn is_value(value: &Value<'_>) -> bool;
/// # Safety
///
/// [`Self::is_value`] must hold for `value`.
unsafe fn from_raw<'gc>(value: &Value<'gc>) -> Self::Ty<'gc>;
}
/// # Safety
///
/// TAG must be unique among all implementors.
#[allow(private_interfaces)]
pub unsafe trait Storable: private::Cealed {
const TAG: RawTag;
/// Each implementor must round-trip through [`Self::to_raw_box`] /
/// [`Self::from_raw_box`] and must be the sole owner of its NaN-boxed
/// representation (tagged payload or float).
pub(crate) unsafe trait Storable: private::Cealed {
fn to_raw_box(self) -> RawBox;
/// # Safety
///
/// `raw` must represent a valid `Self`.
unsafe fn from_raw_box(raw: &RawBox) -> Self;
}
#[allow(private_bounds)]
pub trait InlineStorable: Storable + RawStore {}
pub trait GcStorable: Storable {}
macro_rules! define_value_types {
(
inline { $($itype:ty => $itag:expr, $iname:literal;)* }
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* }
inline { $($itype:ty => $itag:path, $iname:literal;)* }
gc { $($gtype:ty => $gtag:path, $gname:literal;)* }
) => {
$(
#[allow(private_interfaces)]
unsafe impl Storable for $itype {
const TAG: RawTag = $itag;
fn to_raw_box(self) -> RawBox {
RawBox::from_value(RawValue::store($itag, self))
}
unsafe fn from_raw_box(raw: &RawBox) -> Self {
unsafe { <Self as RawStore>::from_val(raw.value().unwrap_unchecked()) }
}
}
impl InlineStorable for $itype {}
impl private::Cealed for $itype {}
impl ValueVariant for $itype {
type Ty<'gc> = $itype;
#[inline(always)]
fn is_value(value: &Value<'_>) -> bool {
value.raw.tag() == Some($itag)
}
#[inline(always)]
unsafe fn from_raw<'gc>(value: &Value<'gc>) -> Self::Ty<'gc> {
unsafe { <$itype as Storable>::from_raw_box(&value.raw) }
}
}
)*
$(
#[allow(private_interfaces)]
unsafe impl Storable for $gtype {
const TAG: RawTag = $gtag;
unsafe impl Storable for Gc<'_, $gtype> {
fn to_raw_box(self) -> RawBox {
RawBox::from_value(RawValue::store($gtag, Gc::as_ptr(self)))
}
unsafe fn from_raw_box(raw: &RawBox) -> Self {
unsafe { Gc::from_ptr(<*mut $gtype as RawStore>::from_val(raw.value().unwrap_unchecked())) }
}
}
impl GcStorable for $gtype {}
impl private::Cealed for Gc<'_, $gtype> {}
impl private::Cealed for $gtype {}
impl ValueVariant for $gtype {
type Ty<'gc> = Gc<'gc, fix_macros::unelide_lifetimes!('gc; $gtype)>;
#[inline(always)]
fn is_value(value: &Value<'_>) -> bool {
value.raw.tag() == Some($gtag)
}
#[inline(always)]
unsafe fn from_raw<'gc>(value: &Value<'gc>) -> Self::Ty<'gc> {
unsafe {
<Gc<'gc, fix_macros::unelide_lifetimes!('gc; $gtype)> as Storable>::from_raw_box(
&value.raw,
)
}
}
}
)*
const _: () = assert!(size_of::<Value<'static>>() == 8);
@@ -83,10 +123,10 @@ macro_rules! define_value_types {
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
let Some(tag) = self.raw.tag() else { return };
match tag {
$(<$gtype as Storable>::TAG => unsafe {
self.load_gc::<$gtype>().trace(cc)
$($gtag => unsafe {
self.downcast::<$gtype>().unwrap_unchecked().trace(cc)
},)*
$(<$itype as Storable>::TAG => (),)*
$($itag => (),)*
_ => unreachable!("invalid value tag"),
}
}
@@ -98,10 +138,10 @@ macro_rules! define_value_types {
None => write!(f, "Float({:?})", unsafe {
self.raw.float().unwrap_unchecked()
}),
$(Some(<$itype as Storable>::TAG) => write!(f, "{}({:?})", $iname, unsafe {
self.as_inline::<$itype>().unwrap_unchecked()
$(Some($itag) => write!(f, "{}({:?})", $iname, unsafe {
self.downcast::<$itype>().unwrap_unchecked()
}),)*
$(Some(<$gtype as Storable>::TAG) =>
$(Some($gtag) =>
write!(f, "{}(..)", $gname),)*
_ => unreachable!("invalid value tag"),
}
@@ -117,16 +157,39 @@ define_value_types! {
Null => RawTag::P3, "Null";
StringId => RawTag::P4, "SmallString";
PrimOp => RawTag::P5, "PrimOp";
Path => RawTag::N6, "Path";
Path => RawTag::P6, "Path";
}
gc {
i64 => RawTag::P6, "BigInt";
NixString => RawTag::P7, "String";
AttrSet<'_> => RawTag::N1, "AttrSet";
List<'_> => RawTag::N2, "List";
Thunk<'_> => RawTag::N3, "Thunk";
Closure<'_> => RawTag::N4, "Closure";
PrimOpApp<'_> => RawTag::N5, "PrimOpApp";
i64 => RawTag::P7, "BigInt";
NixString => RawTag::N1, "String";
AttrSet<'_> => RawTag::N2, "AttrSet";
List<'_> => RawTag::N3, "List";
Thunk<'_> => RawTag::N4, "Thunk";
Closure<'_> => RawTag::N5, "Closure";
PrimOpApp<'_> => RawTag::N6, "PrimOpApp";
}
}
impl private::Cealed for f64 {}
unsafe impl Storable for f64 {
fn to_raw_box(self) -> RawBox {
RawBox::from_float(self)
}
unsafe fn from_raw_box(raw: &RawBox) -> Self {
unsafe { raw.float().copied().unwrap_unchecked() }
}
}
impl ValueVariant for f64 {
type Ty<'gc> = f64;
#[inline(always)]
fn is_value(value: &Value<'_>) -> bool {
value.raw.is_float()
}
#[inline(always)]
unsafe fn from_raw<'gc>(value: &Value<'gc>) -> Self::Ty<'gc> {
unsafe { <f64 as Storable>::from_raw_box(&value.raw) }
}
}
@@ -143,33 +206,11 @@ pub struct Value<'gc> {
impl Default for Value<'_> {
#[inline(always)]
fn default() -> Self {
Self::new_inline(Null)
Self::new(Null)
}
}
impl<'gc> Value<'gc> {
#[inline(always)]
fn from_raw_value(rv: RawValue) -> Self {
Self {
raw: RawBox::from_value(rv),
_marker: PhantomData,
}
}
/// Load a GC pointer from a value with a negative tag.
///
/// # Safety
///
/// The value must actually store a `Gc<'gc, T>` with the matching type.
#[inline(always)]
unsafe fn load_gc<T: GcStorable>(self) -> Gc<'gc, T> {
unsafe {
let rv = self.raw.value().unwrap_unchecked();
let ptr: *const T = <*const T as RawStore>::from_val(rv);
Gc::from_ptr(ptr)
}
}
#[inline(always)]
const fn tag(self) -> Option<RawTag> {
self.raw.tag()
@@ -186,22 +227,20 @@ impl<'gc> Value<'gc> {
}
#[inline]
pub fn new_inline<T: InlineStorable>(val: T) -> Self {
Self::from_raw_value(RawValue::store(T::TAG, val))
}
#[inline]
pub fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
let ptr = Gc::as_ptr(gc);
Self::from_raw_value(RawValue::store(T::TAG, ptr))
#[allow(private_bounds)]
pub fn new<T: Storable>(val: T) -> Self {
Self {
raw: val.to_raw_box(),
_marker: PhantomData,
}
}
#[inline]
pub fn make_int(val: i64, mc: &Mutation<'gc>) -> Self {
if val >= i32::MIN as i64 && val <= i32::MAX as i64 {
Value::new_inline(val as i32)
Value::new(val as i32)
} else {
Value::new_gc(Gc::new(mc, val))
Value::new(Gc::new(mc, val))
}
}
}
@@ -213,40 +252,20 @@ impl<'gc> Value<'gc> {
}
#[inline]
pub fn is<T: Storable>(self) -> bool {
self.tag() == Some(T::TAG)
pub fn is<T: ValueVariant>(self) -> bool {
T::is_value(&self)
}
}
impl<'gc> Value<'gc> {
#[inline]
pub fn as_float(self) -> Option<f64> {
pub fn downcast_float(self) -> Option<f64> {
self.raw.float().copied()
}
#[inline]
pub fn as_inline<T: InlineStorable>(self) -> Option<T> {
if self.is::<T>() {
Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
T::from_val(rv)
})
} else {
None
}
}
#[inline]
pub fn as_gc<T: GcStorable>(self) -> Option<Gc<'gc, T>> {
if self.is::<T>() {
Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
let ptr: *const T = <*const T as RawStore>::from_val(rv);
Gc::from_ptr(ptr)
})
} else {
None
}
pub fn downcast<T: ValueVariant>(self) -> Option<T::Ty<'gc>> {
self.is::<T>().then(|| unsafe { T::from_raw(&self) })
}
#[inline]
@@ -256,18 +275,18 @@ impl<'gc> Value<'gc> {
#[inline]
pub fn as_num(self) -> Option<NixNum> {
if let Some(i) = self.as_inline::<i32>() {
if let Some(i) = self.downcast::<i32>() {
Some(NixNum::Int(i as i64))
} else if let Some(gc_i) = self.as_gc::<i64>() {
} else if let Some(gc_i) = self.downcast::<i64>() {
Some(NixNum::Int(*gc_i))
} else {
self.as_float().map(NixNum::Float)
self.downcast_float().map(NixNum::Float)
}
}
#[inline]
pub fn restrict(self) -> Result<StrictValue<'gc>, Gc<'gc, Thunk<'gc>>> {
if let Some(thunk) = self.as_gc::<Thunk<'gc>>() {
if let Some(thunk) = self.downcast::<Thunk>() {
Err(thunk)
} else {
Ok(StrictValue(self))
@@ -308,13 +327,8 @@ impl<'gc> Value<'gc> {
}
#[inline]
pub fn expect_inline<T: InlineStorable>(self) -> Result<T, NixType> {
self.as_inline::<T>().ok_or_else(|| self.ty())
}
#[inline]
pub fn expect_gc<T: GcStorable>(self) -> Result<Gc<'gc, T>, NixType> {
self.as_gc::<T>().ok_or_else(|| self.ty())
pub fn expect<T: ValueVariant>(self) -> Result<T::Ty<'gc>, NixType> {
self.downcast::<T>().ok_or_else(|| self.ty())
}
#[inline]
@@ -322,14 +336,9 @@ impl<'gc> Value<'gc> {
self.as_num().ok_or_else(|| self.ty())
}
#[inline]
pub fn expect_bool(self) -> Result<bool, NixType> {
self.as_inline::<bool>().ok_or_else(|| self.ty())
}
#[inline]
pub fn expect_float(self) -> Result<f64, NixType> {
self.as_float().ok_or_else(|| self.ty())
self.downcast_float().ok_or_else(|| self.ty())
}
}
@@ -351,33 +360,36 @@ impl StaticValue {
Self(Value::new_float(val))
}
#[inline]
pub fn new_inline<T: InlineStorable>(val: T) -> Self {
Self(Value::new_inline(val))
#[allow(private_bounds)]
pub fn new<T: Storable + 'static>(val: T) -> Self {
Self(Value::new(val))
}
#[inline]
pub fn new_primop(id: BuiltinId, arity: u8, dispatch_ip: u32) -> Self {
Self(Value::new_inline(PrimOp {
Self::new(PrimOp {
id,
arity,
dispatch_ip,
}))
})
}
#[inline]
pub fn is_float(self) -> bool {
self.0.is_float()
}
#[inline]
pub fn is<T: InlineStorable>(self) -> bool {
pub fn is<T: ValueVariant>(self) -> bool {
self.0.is::<T>()
}
#[inline]
pub fn as_float(self) -> Option<f64> {
self.0.as_float()
pub fn downcast<T: ValueVariant>(self) -> Option<T::Ty<'static>> {
self.0.downcast::<T>()
}
#[inline]
pub fn as_inline<T: InlineStorable>(self) -> Option<T> {
self.0.as_inline::<T>()
pub fn downcast_float(self) -> Option<f64> {
self.0.downcast_float()
}
#[inline]
pub fn to_bits(self) -> u64 {
self.0.raw.to_bits()
@@ -638,7 +650,7 @@ impl RawStore for PrimOp {
fn from_val(value: &RawValue) -> Self {
let [id, arity, bytes @ ..] = *value.data();
Self {
id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"),
id: BuiltinId::try_from(id).expect("invalid BuiltinId"),
arity,
dispatch_ip: u32::from_le_bytes(bytes),
}
+6 -12
View File
@@ -3,22 +3,16 @@ name = "fix-vm"
version = "0.1.0"
edition = "2024"
[features]
tailcall = []
[dependencies]
gc-arena = { workspace = true }
hashbrown = { workspace = true }
num_enum = { workspace = true }
smallvec = { workspace = true }
string-interner = { workspace = true }
likely_stable = { workspace = true }
sptr = "0.3"
sysinfo = { version = "0.38", default-features = false, features = ["system"] }
fix-builtins = { path = "../fix-builtins" }
fix-codegen = { path = "../fix-codegen" }
fix-common = { path = "../fix-common" }
fix-bytecode = { path = "../fix-bytecode" }
fix-error = { path = "../fix-error" }
fix-abstract-vm = { path = "../fix-abstract-vm" }
fix-primops = { path = "../fix-primops" }
fix-lang = { path = "../fix-lang" }
fix-runtime = { path = "../fix-runtime" }
[features]
tailcall = []
+11 -11
View File
@@ -61,7 +61,7 @@ macro_rules! tail_fn {
pc: u32,
fuel: u32,
) -> TailResult {
let result = vm.$name();
let result = crate::instructions::$name(vm);
tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel)
}
};
@@ -76,7 +76,7 @@ macro_rules! tail_fn {
fuel: u32,
) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
let result = vm.$name(&mut reader);
let result = crate::instructions::$name(vm, &mut reader);
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
}
};
@@ -91,7 +91,7 @@ macro_rules! tail_fn {
fuel: u32,
) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
let result = vm.$name(&mut reader, mc);
let result = crate::instructions::$name(vm, &mut reader, mc);
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
}
};
@@ -106,7 +106,7 @@ macro_rules! tail_fn {
fuel: u32,
) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize);
let result = vm.$name(ctx, &mut reader, mc);
let result = crate::instructions::$name(vm, ctx, &mut reader, mc);
tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
}
};
@@ -120,7 +120,7 @@ macro_rules! tail_fn {
pc: u32,
fuel: u32,
) -> TailResult {
let result = vm.$name(ctx);
let result = crate::instructions::$name(vm, ctx);
tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel)
}
};
@@ -144,7 +144,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_dispatch_cont, (ctx, reader, mc));
tail_fn!(op_return, (ctx, reader, mc));
tail_fn!(op_make_attrs, (ctx, reader, mc));
@@ -202,16 +202,16 @@ macro_rules! table {
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; )*
$( arr[fix_bytecode::Op::$variant as usize] = $fn; )*
DispatchTable(arr)
};
}
// Exhaustiveness check: fails to compile if `fix_codegen::Op` gains,
// Exhaustiveness check: fails to compile if `fix_bytecode::Op` gains,
// loses, or renames a variant that isn't wired up above.
#[allow(dead_code)]
const _: fn(fix_codegen::Op) = |op| match op {
$( fix_codegen::Op::$variant => (), )*
const _: fn(fix_bytecode::Op) = |op| match op {
$( fix_bytecode::Op::$variant => (), )*
};
};
}
@@ -235,7 +235,7 @@ table! {
MakePatternClosure => op_make_pattern_closure,
Call => op_call,
DispatchPrimOp => op_dispatch_primop,
DispatchCont => op_dispatch_cont,
Return => op_return,
MakeAttrs => op_make_attrs,
+277 -255
View File
@@ -1,274 +1,296 @@
use std::cmp::Ordering;
use fix_abstract_vm::*;
use fix_runtime::*;
use gc_arena::{Gc, Mutation, RefLock};
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_add(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
// if the LHS is a path, the result is a path obtained by
// canonicalizing the concatenated string. RHS may be a path or a
// string. (A `string + path` keeps the string-typed result, handled
// by the next branch.)
if lhs.is::<Path>() {
let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs))
else {
return self.finish_err(fix_error::Error::eval_error(format!(
"cannot append {} to a path",
rhs.ty()
)));
};
let combined = format!("{ls}{rs}");
let canon = canon_path_str(&combined);
let sid = ctx.intern_string(canon);
self.push(Value::new_inline(fix_abstract_vm::Path(sid)));
return Step::Continue(());
}
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
let merged = ctx
.get_string_context(lhs)
.merge(ctx.get_string_context(rhs));
let ns = Gc::new(
mc,
crate::NixString::with_context(format!("{ls}{rs}"), merged),
);
self.push(Value::new_gc(ns));
return Step::Continue(());
}
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
match res {
Ok(val) => {
self.push(val);
Step::Continue(())
}
Err(e) => self.finish_vm_err(e),
}
#[inline(always)]
pub(crate) fn op_add<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
// if the LHS is a path, the result is a path obtained by
// canonicalizing the concatenated string. RHS may be a path or a
// string. (A `string + path` keeps the string-typed result, handled
// by the next branch.)
if lhs.is::<Path>() {
let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs))
else {
return m.finish_err(fix_error::Error::eval_error(format!(
"cannot append {} to a path",
rhs.ty()
)));
};
let combined = format!("{ls}{rs}");
let canon = canon_path_str(&combined);
let sid = ctx.intern_string(canon);
m.push(Value::new(fix_runtime::Path(sid)));
return Step::Continue(());
}
#[inline(always)]
pub(crate) fn op_sub(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
self.op_arith(reader, mc, i64::wrapping_sub, |a, b| a - b)
}
#[inline(always)]
pub(crate) fn op_mul(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
self.op_arith(reader, mc, i64::wrapping_mul, |a, b| a * b)
}
#[inline(always)]
fn op_arith(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
int_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
match res {
Ok(val) => {
self.push(val);
Step::Continue(())
}
Err(e) => self.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match (get_num(lhs), get_num(rhs)) {
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
"division by zero",
)));
}
_ => {}
}
let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b);
match res {
Ok(val) => {
self.push(val);
Step::Continue(())
}
Err(e) => self.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_eq(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, false)
}
#[inline(always)]
pub(crate) fn op_neq(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, true)
}
#[inline(always)]
pub(crate) fn op_lt(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_lt)
}
#[inline(always)]
pub(crate) fn op_gt(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_gt)
}
#[inline(always)]
pub(crate) fn op_leq(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_le)
}
#[inline(always)]
pub(crate) fn op_geq(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_ge)
}
fn compare_values(
&mut self,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
pred: fn(Ordering) -> bool,
) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match self.compare_values_inner(ctx, pred, lhs, rhs) {
Ok(()) => Step::Continue(()),
Err(e) => self.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_concat(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (l, r) = self.force_and_retry::<(Gc<List>, Gc<List>)>(reader, mc)?;
let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l.inner.borrow());
items.extend_from_slice(&r.inner.borrow());
self.push(Value::new_gc(Gc::new(
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
let merged = ctx
.get_string_context(lhs)
.merge(ctx.get_string_context(rhs));
let ns = Gc::new(
mc,
crate::List {
inner: RefLock::new(items),
},
)));
Step::Continue(())
crate::NixString::with_context(format!("{ls}{rs}"), merged),
);
m.push(Value::new(ns));
return Step::Continue(());
}
#[inline(always)]
pub(crate) fn op_update(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (l, r) = self.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
self.push(Value::new_gc(l.merge(&r, mc)));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_neg(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let rhs = self.force_and_retry::<NixNum>(reader, mc)?;
match rhs {
NixNum::Int(int) => self.push(Value::make_int(-int, mc)),
NixNum::Float(float) => self.push(Value::new_float(-float)),
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
match res {
Ok(val) => {
m.push(val);
Step::Continue(())
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_not(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
let rhs = self.force_and_retry::<bool>(reader, mc)?;
self.push(Value::new_inline(!rhs));
Step::Continue(())
}
fn compare_values_inner(
&mut self,
ctx: &impl VmRuntimeCtx,
pred: fn(Ordering) -> bool,
lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>,
) -> crate::VmResult<()> {
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
let ord = match (a, b) {
(NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b),
(NixNum::Float(a), NixNum::Float(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Less),
(NixNum::Int(a), NixNum::Float(b)) => {
(a as f64).partial_cmp(&b).unwrap_or(Ordering::Less)
}
(NixNum::Float(a), NixNum::Int(b)) => {
a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less)
}
};
self.push(Value::new_inline(pred(ord)));
return Ok(());
}
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
self.push(Value::new_inline(pred(a.cmp(b))));
return Ok(());
}
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
let a = ctx.resolve_string(a.0);
let b = ctx.resolve_string(b.0);
self.push(Value::new_inline(pred(a.cmp(b))));
return Ok(());
}
// TODO: compare other types
Err(crate::vm_err("cannot compare these types"))
Err(e) => m.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_sub<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
op_arith(m, reader, mc, i64::wrapping_sub, |a, b| a - b)
}
#[inline(always)]
pub(crate) fn op_mul<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
op_arith(m, reader, mc, i64::wrapping_mul, |a, b| a * b)
}
#[inline(always)]
fn op_arith<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
int_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
match res {
Ok(val) => {
m.push(val);
Step::Continue(())
}
Err(e) => m.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_div<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match (get_num(lhs), get_num(rhs)) {
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
return m.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
"division by zero",
)));
}
_ => {}
}
let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b);
match res {
Ok(val) => {
m.push(val);
Step::Continue(())
}
Err(e) => m.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_eq<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
crate::primops::start_eq(m, ctx, reader, mc, lhs, rhs, false)
}
#[inline(always)]
pub(crate) fn op_neq<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
crate::primops::start_eq(m, ctx, reader, mc, lhs, rhs, true)
}
#[inline(always)]
pub(crate) fn op_lt<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
compare_values(m, ctx, reader, mc, Ordering::is_lt)
}
#[inline(always)]
pub(crate) fn op_gt<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
compare_values(m, ctx, reader, mc, Ordering::is_gt)
}
#[inline(always)]
pub(crate) fn op_leq<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
compare_values(m, ctx, reader, mc, Ordering::is_le)
}
#[inline(always)]
pub(crate) fn op_geq<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
compare_values(m, ctx, reader, mc, Ordering::is_ge)
}
fn compare_values<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
pred: fn(Ordering) -> bool,
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match compare_values_inner(m, ctx, pred, lhs, rhs) {
Ok(()) => Step::Continue(()),
Err(e) => m.finish_vm_err(e),
}
}
#[inline(always)]
pub(crate) fn op_concat<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (l, r) = m.force_and_retry::<(Gc<List>, Gc<List>)>(reader, mc)?;
let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l.inner.borrow());
items.extend_from_slice(&r.inner.borrow());
m.push(Value::new(Gc::new(
mc,
crate::List {
inner: RefLock::new(items),
},
)));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_update<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let (l, r) = m.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
m.push(Value::new(l.merge(&r, mc)));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_neg<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let rhs = m.force_and_retry::<NixNum>(reader, mc)?;
match rhs {
NixNum::Int(int) => m.push(Value::make_int(-int, mc)),
NixNum::Float(float) => m.push(Value::new_float(-float)),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_not<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let rhs = m.force_and_retry::<bool>(reader, mc)?;
m.push(Value::new(!rhs));
Step::Continue(())
}
fn compare_values_inner<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &impl VmRuntimeCtx,
pred: fn(Ordering) -> bool,
lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>,
) -> crate::VmResult<()> {
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
let ord = match (a, b) {
(NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b),
(NixNum::Float(a), NixNum::Float(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Less),
(NixNum::Int(a), NixNum::Float(b)) => {
(a as f64).partial_cmp(&b).unwrap_or(Ordering::Less)
}
(NixNum::Float(a), NixNum::Int(b)) => {
a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less)
}
};
m.push(Value::new(pred(ord)));
return Ok(());
}
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
m.push(Value::new(pred(a.cmp(b))));
return Ok(());
}
if let (Some(a), Some(b)) = (lhs.downcast::<Path>(), rhs.downcast::<Path>()) {
let a = ctx.resolve_string(a.0);
let b = ctx.resolve_string(b.0);
m.push(Value::new(pred(a.cmp(b))));
return Ok(());
}
// TODO: compare other types
Err(crate::vm_err(format!(
"cannot compare {} with {}",
lhs.ty(),
rhs.ty()
)))
}
pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
if let Some(i) = val.as_inline::<i32>() {
Some(NixNum::Int(i as i64))
} else if let Some(gc_i) = val.as_gc::<i64>() {
if let Some(i) = val.downcast::<i32>() {
Some(NixNum::Int(i64::from(i)))
} else if let Some(gc_i) = val.downcast::<i64>() {
Some(NixNum::Int(*gc_i))
} else {
val.as_float().map(NixNum::Float)
val.downcast_float().map(NixNum::Float)
}
}
+164 -166
View File
@@ -1,6 +1,6 @@
use fix_abstract_vm::{resolve_operand, *};
use fix_builtins::PrimOpPhase;
use fix_bytecode::Continuation;
use fix_error::Error;
use fix_runtime::{resolve_operand, *};
use gc_arena::{Gc, Mutation, RefLock};
use crate::{
@@ -8,176 +8,174 @@ use crate::{
VmRuntimeCtxExt,
};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn call(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
arg: Value<'gc>,
resume_pc: usize,
) -> Step {
let func = self.force_and_retry::<StrictValue>(reader, mc)?;
if self.call_depth > 10000 {
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
#[inline(always)]
pub(crate) fn call<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
arg: Value<'gc>,
resume_pc: usize,
) -> Step {
let func = m.force_and_retry::<StrictValue>(reader, mc)?;
if m.call_depth() > 10000 {
return m.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
}
m.inc_call_depth();
if let Some(closure) = func.downcast::<Closure>() {
if closure.pattern.is_some() {
// FIXME: better DX...
m.push(func.relax());
m.push(arg);
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
reader.set_pc(Continuation::CallPattern.ip() as usize);
return Step::Continue(());
}
self.call_depth += 1;
if let Some(closure) = func.as_gc::<Closure>() {
if closure.pattern.is_some() {
// FIXME: better DX...
self.push(func.relax());
self.push(arg);
self.call_stack.push(CallFrame {
pc: resume_pc,
thunk: None,
env: self.env,
});
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
let ip = closure.ip;
let n_locals = closure.n_locals;
let env = closure.env;
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
reader.set_pc(ip as usize);
m.set_env(new_env);
} else if let Some(primop) = func.downcast::<PrimOp>() {
if primop.arity == 1 {
m.push(arg);
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
reader.set_pc(primop.dispatch_ip as usize)
} else {
let app = PrimOpApp {
primop,
arity: primop.arity - 1,
args: [arg, Value::default(), Value::default()],
};
m.push(Value::new(Gc::new(mc, app)));
}
} else if let Some(app) = func.downcast::<PrimOpApp>() {
if app.arity == 1 {
for i in 0..app.primop.arity - 1 {
m.push(app.args[i as usize]);
}
m.push(arg);
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
reader.set_pc(app.primop.dispatch_ip as usize)
} else {
let position = (app.primop.arity - app.arity) as usize;
let mut new_app = PrimOpApp {
arity: app.arity - 1,
..*app
};
new_app.args[position] = arg;
m.push(Value::new(Gc::new(mc, new_app)))
}
} else if let Some(attrs) = func.downcast::<AttrSet>()
&& let Some(functor) = attrs.lookup(m.functor_sym())
{
// f arg => (f.__functor f) arg
//
// Stage the work for `CallFunctor1` so retries during force are
// safe: the stack invariant `[..., orig_arg, self, functor]`
// holds every time control re-enters phase 1.
m.dec_call_depth();
m.push_call_frame(CallFrame {
pc: resume_pc,
thunk: None,
env: m.env(),
});
m.push(arg);
m.push(func.relax());
m.push(functor);
reader.set_pc(Continuation::CallFunctor1.ip() as usize);
return Step::Continue(());
} else {
return m.finish_err(Error::eval_error(format!(
"attempt to call something which is not a function but {}",
func.ty()
)));
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_call<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let arg = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let pc = reader.pc();
m.call(reader, mc, arg, pc)
}
#[inline(always)]
pub(crate) fn op_return<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
let Some(CallFrame {
pc: ret_pc,
thunk,
env,
}) = m.pop_call_frame()
else {
match m.force_mode() {
ForceMode::AsIs => return m.finish_ok(ctx.convert_value(val.relax())),
ForceMode::Shallow => {
m.push(val.relax());
reader.set_pc(Continuation::ForceResultShallow.ip() as usize);
return Step::Continue(());
}
let ip = closure.ip;
let n_locals = closure.n_locals;
let env = closure.env;
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame {
pc: resume_pc,
thunk: None,
env: self.env,
});
reader.set_pc(ip as usize);
self.env = new_env;
} else if let Some(primop) = func.as_inline::<PrimOp>() {
if primop.arity == 1 {
self.push(arg);
self.call_stack.push(CallFrame {
pc: resume_pc,
ForceMode::Deep => {
m.push(val.relax());
m.push(val.relax());
m.push_call_frame(CallFrame {
pc: Continuation::ForceResultDeepFinish.ip() as usize,
thunk: None,
env: self.env,
env: m.env(),
});
reader.set_pc(primop.dispatch_ip as usize)
} else {
let app = PrimOpApp {
primop,
arity: primop.arity - 1,
args: [arg, Value::default(), Value::default()],
};
self.push(Value::new_gc(Gc::new(mc, app)));
m.inc_call_depth();
reader.set_pc(Continuation::PDeepSeq.ip() as usize);
return Step::Continue(());
}
} 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,
thunk: None,
env: self.env,
});
reader.set_pc(app.primop.dispatch_ip as usize)
} else {
let position = (app.primop.arity - app.arity) as usize;
let mut new_app = PrimOpApp {
arity: app.arity - 1,
..*app
};
new_app.args[position] = arg;
self.push(Value::new_gc(Gc::new(mc, new_app)))
}
} else if let Some(attrs) = func.as_gc::<AttrSet>()
&& let Some(functor) = attrs.lookup(self.functor_sym)
{
// f arg => (f.__functor f) arg
//
// Stage the work for `CallFunctor1` so retries during force are
// safe: the stack invariant `[..., orig_arg, self, functor]`
// holds every time control re-enters phase 1.
self.call_depth -= 1;
self.call_stack.push(CallFrame {
pc: resume_pc,
thunk: None,
env: self.env,
});
self.push(arg);
self.push(func.relax());
self.push(functor);
reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize);
return Step::Continue(());
} else {
return self.finish_err(Error::eval_error(format!(
"attempt to call something which is not a function but {}",
func.ty()
)));
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_call(
&mut self,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let arg = resolve_operand(&reader.read_operand_data(ctx), mc, self);
let pc = reader.pc();
self.call(reader, mc, arg, pc)
}
#[inline(always)]
pub(crate) fn op_return(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
let Some(CallFrame {
pc: ret_pc,
thunk,
env,
}) = self.call_stack.pop()
else {
match self.force_mode {
ForceMode::AsIs => return self.finish_ok(ctx.convert_value(val.relax())),
ForceMode::Shallow => {
self.push(val.relax());
reader.set_pc(PrimOpPhase::ForceResultShallow.ip() as usize);
return Step::Continue(());
}
ForceMode::Deep => {
self.push(val.relax());
self.push(val.relax());
self.call_stack.push(CallFrame {
pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize,
thunk: None,
env: self.env,
});
self.call_depth += 1;
reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize);
return Step::Continue(());
}
}
};
reader.set_pc(ret_pc);
if let Some(outer_thunk) = thunk {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
} else {
self.call_depth -= 1;
self.push(val.relax())
}
self.env = env;
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_dispatch_primop(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
fix_primops::dispatch_primop(self, ctx, reader, mc)
};
reader.set_pc(ret_pc);
if let Some(outer_thunk) = thunk {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
} else {
m.dec_call_depth();
m.push(val.relax())
}
m.set_env(env);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_dispatch_cont<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
crate::primops::dispatch_cont(m, ctx, reader, mc)
}
+84 -85
View File
@@ -1,94 +1,93 @@
use fix_runtime::Machine;
use gc_arena::{Gc, Mutation, RefLock};
use crate::{BytecodeReader, Step, ThunkState, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_make_thunk(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let entry_point = reader.read_u32();
let thunk = Gc::new(
mc,
RefLock::new(ThunkState::Pending {
ip: entry_point as usize,
env: self.env,
}),
);
self.push(Value::new_gc(thunk));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_thunk<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let entry_point = reader.read_u32();
let thunk = Gc::new(
mc,
RefLock::new(ThunkState::Pending {
ip: entry_point as usize,
env: m.env(),
}),
);
m.push(Value::new(thunk));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_closure(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let entry_point = reader.read_u32();
let n_locals = reader.read_u32();
let closure = Gc::new(
mc,
crate::Closure {
ip: entry_point,
n_locals,
env: self.env,
pattern: None,
},
);
self.push(Value::new_gc(closure));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_closure<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let entry_point = reader.read_u32();
let n_locals = reader.read_u32();
let closure = Gc::new(
mc,
crate::Closure {
ip: entry_point,
n_locals,
env: m.env(),
pattern: None,
},
);
m.push(Value::new(closure));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_pattern_closure(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let entry_point = reader.read_u32();
let n_locals = reader.read_u32();
let req_count = reader.read_u16() as usize;
let opt_count = reader.read_u16() as usize;
let has_ellipsis = reader.read_u8() != 0;
#[inline(always)]
pub(crate) fn op_make_pattern_closure<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let entry_point = reader.read_u32();
let n_locals = reader.read_u32();
let req_count = reader.read_u16() as usize;
let opt_count = reader.read_u16() as usize;
let has_ellipsis = reader.read_u8() != 0;
let mut required = smallvec::SmallVec::new();
for _ in 0..req_count {
required.push(reader.read_string_id());
}
let mut optional = smallvec::SmallVec::new();
for _ in 0..opt_count {
optional.push(reader.read_string_id());
}
let total = req_count + opt_count;
let mut param_spans = Vec::with_capacity(total);
for _ in 0..total {
let name = reader.read_string_id();
let span_id = reader.read_u32();
param_spans.push((name, span_id));
}
let mut required = smallvec::SmallVec::new();
for _ in 0..req_count {
required.push(reader.read_string_id());
}
let mut optional = smallvec::SmallVec::new();
for _ in 0..opt_count {
optional.push(reader.read_string_id());
}
let total = req_count + opt_count;
let mut param_spans = Vec::with_capacity(total);
for _ in 0..total {
let name = reader.read_string_id();
let span_id = reader.read_u32();
param_spans.push((name, span_id));
}
let pattern = Gc::new(
mc,
crate::PatternInfo {
required,
optional,
ellipsis: has_ellipsis,
param_spans: param_spans.into_boxed_slice(),
},
);
let closure = Gc::new(
mc,
crate::Closure {
ip: entry_point,
n_locals,
env: self.env,
pattern: Some(pattern),
},
);
self.push(Value::new_gc(closure));
Step::Continue(())
}
let pattern = Gc::new(
mc,
crate::PatternInfo {
required,
optional,
ellipsis: has_ellipsis,
param_spans: param_spans.into_boxed_slice(),
},
);
let closure = Gc::new(
mc,
crate::Closure {
ip: entry_point,
n_locals,
env: m.env(),
pattern: Some(pattern),
},
);
m.push(Value::new(closure));
Step::Continue(())
}
+311 -308
View File
@@ -1,6 +1,6 @@
use fix_abstract_vm::{NixType, resolve_operand};
use fix_common::StringId;
use fix_error::Error;
use fix_lang::StringId;
use fix_runtime::{Machine, MachineExt, NixType, resolve_operand};
use gc_arena::{Gc, RefLock};
use smallvec::SmallVec;
@@ -8,327 +8,330 @@ use crate::{
AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_make_attrs(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let static_count = reader.read_u32() as usize;
let dynamic_count = reader.read_u32() as usize;
#[inline(always)]
pub(crate) fn op_make_attrs<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let static_count = reader.read_u32() as usize;
let dynamic_count = reader.read_u32() as usize;
for i in 0..dynamic_count {
let depth = dynamic_count - 1 - i;
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?;
}
let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count);
for i in 0..dynamic_count {
let depth = dynamic_count - 1 - i;
let key_val = self.peek_forced(depth);
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => Some(id),
Err(NixType::Null) => None,
Err(got) => return self.finish_type_err(NixType::String, got),
};
dyn_keys.push(key_sid);
}
self.stack.truncate(self.stack.len() - dynamic_count);
let mut kv: SmallVec<[(crate::StringId, Value); 4]> =
SmallVec::with_capacity(static_count + dynamic_count);
for _ in 0..static_count {
let key = reader.read_string_id();
let val = resolve_operand(&reader.read_operand_data(ctx), mc, self);
let _span_id = reader.read_u32();
kv.push((key, val));
}
for key in dyn_keys {
let val = resolve_operand(&reader.read_operand_data(ctx), mc, self);
let _span_id = reader.read_u32();
if let Some(key) = key {
kv.push((key, val))
}
}
kv.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
self.push(Value::new_gc(attrs));
Step::Continue(())
for i in 0..dynamic_count {
let depth = dynamic_count - 1 - i;
m.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?;
}
#[inline(always)]
pub(crate) fn op_make_empty_attrs(&mut self) -> Step {
self.push(self.empty_attrs);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_select_static(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let key = reader.read_string_id();
let attrset = self.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
match attrset.lookup(key) {
Some(v) => {
self.push(v);
}
None => return self.select_skip(key, ctx, reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_select_dynamic(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let (attrset, key_val) = self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count);
for i in 0..dynamic_count {
let depth = dynamic_count - 1 - i;
let key_val = m.peek_forced(depth);
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got),
Ok(id) => Some(id),
Err(NixType::Null) => None,
Err(got) => return m.finish_type_err(NixType::String, got),
};
match attrset.lookup(key_sid) {
Some(v) => {
self.push(v);
}
None => return self.select_skip(key_sid, ctx, reader),
}
Step::Continue(())
dyn_keys.push(key_sid);
}
/// Skip the rest of a **Select** attrpath after a missing attribute.
/// Only recognises Select opcodes and jumps; encountering any other
/// opcode means we've reached the end of the select sequence and
/// should report the missing-attribute error.
fn select_skip(
&mut self,
key: StringId,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
) -> Step {
use fix_codegen::Op::*;
loop {
match reader.read_op() {
SelectStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
SelectDynamic => {
reader.set_pc(reader.pc() + 4);
}
JumpIfSelectSucceeded => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
_ => {
let name = ctx.resolve_string(key);
return self
.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
}
}
}
}
m.drop_n(dynamic_count);
/// Skip the rest of a **HasAttr** attrpath after an intermediate
/// lookup failed. Only recognises HasAttr opcodes and jumps.
fn has_attr_skip(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
use fix_codegen::Op::*;
loop {
match reader.read_op() {
HasAttrPathStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
HasAttrPathDynamic => {
reader.set_pc(reader.pc() + 4);
}
HasAttrStatic => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
HasAttrDynamic => {
break Step::Continue(());
}
HasAttrResolve => {
reader.set_pc(reader.pc() - 1);
break Step::Continue(());
}
other => {
unreachable!("unexpected opcode {:?} in has_attr_skip", other as u8)
}
}
}
}
let mut kv: SmallVec<[(crate::StringId, Value); 4]> =
SmallVec::with_capacity(static_count + dynamic_count);
#[inline(always)]
pub(crate) fn op_has_attr_path_static(
&mut self,
_ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
for _ in 0..static_count {
let key = reader.read_string_id();
let current = self.force_and_retry::<StrictValue>(reader, mc)?;
match current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
{
Some(v) => {
self.push(v);
}
None => return self.has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_path_dynamic(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let val = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let _span_id = reader.read_u32();
kv.push((key, val));
}
let (current, key_val) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
for key in dyn_keys {
let val = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let _span_id = reader.read_u32();
if let Some(key) = key {
kv.push((key, val))
}
}
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got),
};
kv.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
m.push(Value::new(attrs));
Step::Continue(())
}
match current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
{
Some(v) => {
self.push(v);
#[inline(always)]
pub(crate) fn op_make_empty_attrs<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
m.push(m.empty_attrs());
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_select_static<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let key = reader.read_string_id();
let attrset = m.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
match attrset.lookup(key) {
Some(v) => {
m.push(v);
}
None => return select_skip(m, key, ctx, reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_select_dynamic<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let (attrset, key_val) = m.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id,
Err(got) => return m.finish_type_err(NixType::String, got),
};
match attrset.lookup(key_sid) {
Some(v) => {
m.push(v);
}
None => return select_skip(m, key_sid, ctx, reader),
}
Step::Continue(())
}
/// Skip the rest of a **Select** attrpath after a missing attribute.
/// Only recognises Select opcodes and jumps; encountering any other
/// opcode means we've reached the end of the select sequence and
/// should report the missing-attribute error.
fn select_skip<'gc, M: Machine<'gc>>(
m: &mut M,
key: StringId,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
) -> Step {
use fix_bytecode::Op::*;
loop {
match reader.read_op() {
SelectStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
SelectDynamic => {
reader.set_pc(reader.pc() + 4);
}
JumpIfSelectSucceeded => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
_ => {
let name = ctx.resolve_string(key);
return m.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
}
None => return self.has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_failed(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
// No-op
let _offset = reader.read_i32();
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_static(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let key = reader.read_string_id();
let current = self.force_and_retry::<StrictValue>(reader, mc)?;
self.push(Value::new_inline(
current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_dynamic(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let (current, dyn_key) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(dyn_key) {
Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got),
};
self.push(Value::new_inline(
current
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_resolve(&mut self) -> Step {
// If we reach here, has_attr check has failed, push false (AttrSet is already popped)
self.push(Value::new_inline(false));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_list(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let count = reader.read_u32() as usize;
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
for _ in 0..count {
items.push(resolve_operand(&reader.read_operand_data(ctx), mc, self));
}
let list = Gc::new(
mc,
List {
inner: RefLock::new(items),
},
);
self.push(Value::new_gc(list));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_empty_list(&mut self) -> Step {
self.push(self.empty_list);
Step::Continue(())
}
}
/// Skip the rest of a **HasAttr** attrpath after an intermediate
/// lookup failed. Only recognises HasAttr opcodes and jumps.
fn has_attr_skip(reader: &mut BytecodeReader<'_>) -> Step {
use fix_bytecode::Op::*;
loop {
match reader.read_op() {
HasAttrPathStatic => {
reader.set_pc(reader.pc() + 4 + 4);
}
HasAttrPathDynamic => {
reader.set_pc(reader.pc() + 4);
}
HasAttrStatic => {
reader.set_pc(reader.pc() + 4);
break Step::Continue(());
}
JumpIfSelectFailed => {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
HasAttrDynamic => {
break Step::Continue(());
}
HasAttrResolve => {
reader.set_pc(reader.pc() - 1);
break Step::Continue(());
}
other => {
unreachable!("unexpected opcode {:?} in has_attr_skip", other as u8)
}
}
}
}
#[inline(always)]
pub(crate) fn op_has_attr_path_static<'gc, M: Machine<'gc>>(
m: &mut M,
_ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let key = reader.read_string_id();
let current = m.force_and_retry::<StrictValue>(reader, mc)?;
match current
.downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
{
Some(v) => {
m.push(v);
}
None => return has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_path_dynamic<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let _span_id = reader.read_u32();
let (current, key_val) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id,
Err(got) => return m.finish_type_err(NixType::String, got),
};
match current
.downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
{
Some(v) => {
m.push(v);
}
None => return has_attr_skip(reader),
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_failed<'gc, M: Machine<'gc>>(
_m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
// No-op
let _offset = reader.read_i32();
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_select_succeeded<'gc, M: Machine<'gc>>(
_m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_static<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let key = reader.read_string_id();
let current = m.force_and_retry::<StrictValue>(reader, mc)?;
m.push(Value::new(
current
.downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(key))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_dynamic<'gc, M: MachineExt<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let (current, dyn_key) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(dyn_key) {
Ok(id) => id,
Err(got) => return m.finish_type_err(NixType::String, got),
};
m.push(Value::new(
current
.downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid))
.is_some(),
));
// Skip HasAttrResolve
reader.set_pc(reader.pc() + 1);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_has_attr_resolve<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
// If we reach here, has_attr check has failed, push false (AttrSet is already popped)
m.push(Value::new(false));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_list<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let count = reader.read_u32() as usize;
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
for _ in 0..count {
items.push(resolve_operand(&reader.read_operand_data(), mc, ctx, m));
}
let list = Gc::new(
mc,
List {
inner: RefLock::new(items),
},
);
m.push(Value::new(list));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_make_empty_list<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
m.push(m.empty_list());
Step::Continue(())
}
+50 -52
View File
@@ -1,60 +1,58 @@
use fix_abstract_vm::*;
use fix_error::Error;
use fix_runtime::*;
use gc_arena::Mutation;
use crate::{BytecodeReader, Step, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_jump_if_false(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let offset = reader.read_i32();
let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
if cond.as_inline::<bool>() == Some(false) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_true(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let offset = reader.read_i32();
let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
if cond.as_inline::<bool>() == Some(true) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let offset = reader.read_i32();
#[inline(always)]
pub(crate) fn op_jump_if_false<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let offset = reader.read_i32();
let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
if cond.downcast::<bool>() == Some(false) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_assert(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let raw_id = reader.read_string_id();
let raw = ctx.resolve_string(raw_id);
let _span_id = reader.read_u32();
let assertion = self.force_and_retry::<bool>(reader, mc)?;
if !assertion {
// FIXME: use catchable error
return self.finish_err(Error::eval_error(format!("assertion '{raw}' failed")));
}
Step::Continue(())
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump_if_true<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let offset = reader.read_i32();
let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
if cond.downcast::<bool>() == Some(true) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_jump<'gc, M: Machine<'gc>>(_m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_assert<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let raw_id = reader.read_string_id();
let raw = ctx.resolve_string(raw_id);
let _span_id = reader.read_u32();
let assertion = m.force_and_retry::<bool>(reader, mc)?;
if !assertion {
// FIXME: use catchable error
return m.finish_err(Error::eval_error(format!("assertion '{raw}' failed")));
}
Step::Continue(())
}
+52 -44
View File
@@ -1,55 +1,63 @@
use fix_runtime::Machine;
use gc_arena::{Gc, Mutation};
use crate::{BytecodeReader, Step, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_push_smi(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let val = reader.read_i32();
self.push(Value::new_inline(val));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_smi<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let val = reader.read_i32();
m.push(Value::new(val));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_bigint(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = reader.read_i64();
self.push(Value::new_gc(Gc::new(mc, val)));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_bigint<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = reader.read_i64();
m.push(Value::new(Gc::new(mc, val)));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_float(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let val = reader.read_f64();
self.push(Value::new_float(val));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_float<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let val = reader.read_f64();
m.push(Value::new_float(val));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_string(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let sid = reader.read_string_id();
self.push(Value::new_inline(sid));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_string<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let sid = reader.read_string_id();
m.push(Value::new(sid));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_null(&mut self) -> Step {
self.push(Value::new_inline(crate::Null));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_null<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
m.push(Value::new(crate::Null));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_true(&mut self) -> Step {
self.push(Value::new_inline(true));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_true<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
m.push(Value::new(true));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_false(&mut self) -> Step {
self.push(Value::new_inline(false));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_push_false<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
m.push(Value::new(false));
Step::Continue(())
}
+145 -149
View File
@@ -1,169 +1,165 @@
use std::path::PathBuf;
use fix_abstract_vm::{
AttrSet, NixString, Path, StrictValue, StringContext, canon_path_str
};
use fix_builtins::BuiltinId;
use fix_common::StringId;
use fix_bytecode::Continuation;
use fix_error::Error;
use num_enum::TryFromPrimitive;
use fix_lang::{BUILTINS, BuiltinId, StringId};
use fix_runtime::{
AttrSet, Machine, MachineExt, NixString, Path, StrictValue, StringContext, canon_path_str,
};
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_load_builtins(&mut self) -> Step {
self.push(self.builtins);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_load_builtins<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
m.push(m.builtins());
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8())
.map_err(|err| panic!("unknown builtin id: {}", err.number));
self.push(Value::new_inline(PrimOp {
id,
arity: fix_builtins::BUILTINS[id as usize].1,
dispatch_ip: id.entry_phase().ip(),
}));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_load_builtin<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let Ok(id) = BuiltinId::try_from(reader.read_u8())
.map_err(|err| panic!("unknown builtin id: {}", err.number));
m.push(Value::new(PrimOp {
id,
arity: BUILTINS[id as usize].1,
dispatch_ip: Continuation::entry_for_builtin(id).ip(),
}));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let _name = reader.read_string_id();
todo!("LoadReplBinding");
}
#[inline(always)]
pub(crate) fn op_load_repl_binding<'gc, M: Machine<'gc>>(
_m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let _name = reader.read_string_id();
todo!("LoadReplBinding");
}
#[inline(always)]
pub(crate) fn op_load_scoped_binding(
&mut self,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
_mc: &gc_arena::Mutation<'gc>,
) -> Step {
let slot_id = reader.read_u32();
let name = reader.read_string_id();
let scope = match self.scope_slots.get(slot_id as usize).copied() {
Some(s) => s,
None => {
return self.finish_err(Error::eval_error(format!(
"internal: invalid scope slot {slot_id}"
)));
}
};
let Some(attrs) = scope.as_gc::<AttrSet>() else {
return self.finish_err(Error::eval_error("internal: scope slot is not an attrset"));
};
match attrs.lookup(name) {
Some(val) => {
self.push(val);
Step::Continue(())
}
None => self.finish_err(Error::eval_error(format!(
"scoped binding '{}' not found",
ctx.resolve_string(name)
))),
#[inline(always)]
pub(crate) fn op_load_scoped_binding<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
_mc: &gc_arena::Mutation<'gc>,
) -> Step {
let slot_id = reader.read_u32();
let name = reader.read_string_id();
let scope = m.scope_slot(slot_id);
let Some(attrs) = scope.downcast::<AttrSet>() else {
return m.finish_err(Error::eval_error("internal: scope slot is not an attrset"));
};
match attrs.lookup(name) {
Some(val) => {
m.push(val);
Step::Continue(())
}
None => m.finish_err(Error::eval_error(format!(
"scoped binding '{}' not found",
ctx.resolve_string(name)
))),
}
}
#[inline(always)]
pub(crate) fn op_coerce_to_string<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if val.is::<StringId>() || val.is::<NixString>() {
m.push(val.relax());
} else if let Some(p) = val.downcast::<Path>() {
// Coercing a path to a string yields the canonical path text.
// FIXME: copy to store
m.push(Value::new(p.0));
} else {
todo!("coerce other types to string: {:?}", val.ty());
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_concat_strings<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let count = reader.read_u16() as usize;
let _force_string = reader.read_u8() != 0;
let mut total_len = 0;
let mut has_any_context = false;
for i in 0..count {
let val = m.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced");
total_len += s.len();
if !ctx.get_string_context(val).is_empty() {
has_any_context = true;
}
}
#[inline(always)]
pub(crate) fn op_coerce_to_string(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
if val.is::<StringId>() || val.is::<NixString>() {
self.push(val.relax());
} else if let Some(p) = val.as_inline::<Path>() {
// Coercing a path to a string yields the canonical path text.
// FIXME: copy to store
self.push(Value::new_inline(p.0));
} else {
todo!("coerce other types to string: {:?}", val.ty());
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_concat_strings(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let count = reader.read_u16() as usize;
let _force_string = reader.read_u8() != 0;
let mut total_len = 0;
let mut has_any_context = false;
for i in 0..count {
let val = self.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced");
total_len += s.len();
if !ctx.get_string_context(val).is_empty() {
has_any_context = true;
let mut result = String::with_capacity(total_len);
let mut merged = StringContext::new();
for i in 0..count {
let val = m.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced");
result.push_str(s);
if has_any_context {
let ctx = ctx.get_string_context(val);
if !ctx.is_empty() {
merged = merged.merge(ctx);
}
}
let mut result = String::with_capacity(total_len);
let mut merged = StringContext::new();
for i in 0..count {
let val = self.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced");
result.push_str(s);
if has_any_context {
let ctx = ctx.get_string_context(val);
if !ctx.is_empty() {
merged = merged.merge(ctx);
}
}
}
self.stack.truncate(self.stack.len() - count);
if merged.is_empty() {
let sid = ctx.intern_string(result);
self.push(Value::new_inline(sid));
} else {
let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged));
self.push(Value::new_gc(ns));
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_resolve_path(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
let dir_id = reader.read_string_id();
// Already a path: keep as-is. ResolvePath is idempotent on paths.
if let Some(p) = path_val.as_inline::<Path>() {
self.push(Value::new_inline(p));
return Step::Continue(());
}
let path = match ctx.get_string(path_val) {
Some(s) => s.to_owned(),
None => {
return self.finish_err(Error::eval_error(format!(
"expected a string for path, got {}",
path_val.ty()
)));
}
};
let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) {
Ok(s) => s,
Err(e) => return self.finish_err(e),
};
let sid = ctx.intern_string(resolved);
self.push(Value::new_inline(Path(sid)));
Step::Continue(())
m.drop_n(count);
if merged.is_empty() {
let sid = ctx.intern_string(result);
m.push(Value::new(sid));
} else {
let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged));
m.push(Value::new(ns));
}
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_resolve_path<'gc, M: MachineExt<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
let dir_id = reader.read_string_id();
// Already a path: keep as-is. ResolvePath is idempotent on paths.
if let Some(p) = path_val.downcast::<Path>() {
m.push(Value::new(p));
return Step::Continue(());
}
let path = match ctx.get_string(path_val) {
Some(s) => s.to_owned(),
None => {
return m.finish_err(Error::eval_error(format!(
"expected a string for path, got {}",
path_val.ty()
)));
}
};
let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) {
Ok(s) => s,
Err(e) => return m.finish_err(e),
};
let sid = ctx.intern_string(resolved);
m.push(Value::new(Path(sid)));
Step::Continue(())
}
fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>> {
+10
View File
@@ -7,3 +7,13 @@ pub(crate) mod literals;
pub(crate) mod misc;
pub(crate) mod variables;
pub(crate) mod with_scope;
pub(crate) use arithmetic::*;
pub(crate) use calls::*;
pub(crate) use closures::*;
pub(crate) use collections::*;
pub(crate) use control::*;
pub(crate) use literals::*;
pub(crate) use misc::*;
pub(crate) use variables::*;
pub(crate) use with_scope::*;
+49 -43
View File
@@ -1,50 +1,56 @@
use fix_runtime::Machine;
use crate::{BytecodeReader, Mutation, Step, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_load_local(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let idx = reader.read_u32() as usize;
self.push(self.env.borrow().locals[idx]);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_load_local<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let idx = reader.read_u32() as usize;
m.push(m.env().borrow().locals[idx]);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_load_outer(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
let layer = reader.read_u8();
let idx = reader.read_u32() as usize;
let mut cur = self.env;
for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
cur = prev;
}
let val = cur.borrow().locals[idx];
self.push(val);
Step::Continue(())
#[inline(always)]
pub(crate) fn op_load_outer<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let layer = reader.read_u8();
let idx = reader.read_u32() as usize;
let mut cur = m.env();
for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
cur = prev;
}
let val = cur.borrow().locals[idx];
m.push(val);
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_store_local(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let idx = reader.read_u32() as usize;
let val = self.pop();
self.env.borrow_mut(mc).locals[idx] = val;
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_store_local<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let idx = reader.read_u32() as usize;
let val = m.pop();
m.env().borrow_mut(mc).locals[idx] = val;
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_alloc_locals(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let count = reader.read_u32() as usize;
self.env
.borrow_mut(mc)
.locals
.extend(std::iter::repeat_n(Value::default(), count));
Step::Continue(())
}
#[inline(always)]
pub(crate) fn op_alloc_locals<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let count = reader.read_u32() as usize;
m.env()
.borrow_mut(mc)
.locals
.extend(std::iter::repeat_n(Value::default(), count));
Step::Continue(())
}
+62 -65
View File
@@ -1,78 +1,75 @@
use fix_abstract_vm::{resolve_operand, *};
use fix_common::Symbol;
use fix_error::Error;
use fix_lang::Symbol;
use fix_runtime::{resolve_operand, *};
use smallvec::SmallVec;
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> {
#[inline(always)]
pub(crate) fn op_lookup_with(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
#[allow(clippy::unwrap_used)]
let counter = self.peek_forced(0).as_inline::<i32>().unwrap();
#[inline(always)]
pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>,
) -> Step {
#[allow(clippy::unwrap_used)]
let counter = m.peek_forced(0).downcast::<i32>().unwrap();
let name = reader.read_string_id();
let n = reader.read_u8();
let mut namespaces = SmallVec::<[_; 2]>::new();
for _ in 0..n {
namespaces.push(resolve_operand(&reader.read_operand_data(ctx), mc, self));
}
let name = reader.read_string_id();
let n = reader.read_u8();
let mut namespaces = SmallVec::<[_; 2]>::new();
for _ in 0..n {
namespaces.push(resolve_operand(&reader.read_operand_data(), mc, ctx, m));
}
let resume_pc = reader.inst_start_pc();
let namespace = match namespaces[counter as usize].restrict() {
Ok(val) => val,
Err(thunk) => {
let mut state = thunk.borrow_mut(mc);
match *state {
ThunkState::Pending { ip, env } => {
*state = ThunkState::Blackhole;
self.call_stack.push(CallFrame {
thunk: Some(thunk),
pc: resume_pc,
env: self.env,
});
self.env = env;
reader.set_pc(ip);
return Step::Break(Break::Force);
}
ThunkState::Evaluated(v) => v,
ThunkState::Apply { func, arg } => {
self.call_stack.push(CallFrame {
thunk: Some(thunk),
pc: resume_pc,
env: self.env,
});
self.push(func);
return self.call(reader, mc, arg, resume_pc);
}
ThunkState::Blackhole => {
return self
.finish_err(Error::eval_error("infinite recursion encountered"));
}
let resume_pc = reader.inst_start_pc();
let namespace = match namespaces[counter as usize].restrict() {
Ok(val) => val,
Err(thunk) => {
let mut state = thunk.borrow_mut(mc);
match *state {
ThunkState::Pending { ip, env } => {
*state = ThunkState::Blackhole;
m.push_call_frame(CallFrame {
thunk: Some(thunk),
pc: resume_pc,
env: m.env(),
});
m.set_env(env);
reader.set_pc(ip);
return Step::Break(Break::Force);
}
ThunkState::Evaluated(v) => v,
ThunkState::Apply { func, arg } => {
m.push_call_frame(CallFrame {
thunk: Some(thunk),
pc: resume_pc,
env: m.env(),
});
m.push(func);
return m.call(reader, mc, arg, resume_pc);
}
ThunkState::Blackhole => {
return m.finish_err(Error::eval_error("infinite recursion encountered"));
}
}
};
if let Some(val) = namespace
.as_gc::<AttrSet>()
.and_then(|attrs| attrs.lookup(name))
{
self.replace(0, val);
} else if counter + 1 == n as i32 {
return self.finish_err(Error::eval_error(format!(
"undefined variable '{}'",
Symbol::from(ctx.resolve_string(name))
)));
} else {
self.replace(0, Value::new_inline(counter + 1));
reader.set_pc(resume_pc);
}
};
Step::Continue(())
if let Some(val) = namespace
.downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(name))
{
m.replace(0, val);
} else if counter + 1 == n as i32 {
return m.finish_err(Error::eval_error(format!(
"undefined variable '{}'",
Symbol::from(ctx.resolve_string(name))
)));
} else {
m.replace(0, Value::new(counter + 1));
reader.set_pc(resume_pc);
}
Step::Continue(())
}
+108 -231
View File
@@ -7,20 +7,19 @@
use std::path::PathBuf;
use fix_builtins::{BUILTINS, BuiltinId};
use fix_codegen::InstructionPtr;
use fix_common::StringId;
use fix_bytecode::{Continuation, InstructionPtr};
use fix_error::{Error, Result, Source};
use fix_lang::{BUILTINS, BuiltinId, StringId};
use gc_arena::metrics::Pacing;
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
use hashbrown::HashMap;
use num_enum::TryFromPrimitive;
use smallvec::SmallVec;
#[cfg(feature = "tailcall")]
mod dispatch_tailcall;
pub use fix_abstract_vm::*;
pub use fix_runtime::*;
mod instructions;
mod primops;
type VmResult<T> = std::result::Result<T, VmError>;
@@ -46,7 +45,7 @@ pub struct Vm<'gc> {
force_mode: ForceMode,
#[collect(require_static)]
result: Option<Result<fix_common::Value>>,
result: Option<Result<fix_lang::Value>>,
#[collect(require_static)]
pending_load: Option<PendingLoad>,
@@ -58,13 +57,13 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
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 id = BuiltinId::try_from(idx as u8).expect("infallible");
let name = name.strip_prefix("__").unwrap_or(name);
let name = ctx.intern_string(name);
let dispatch_ip = id.entry_phase().ip();
let dispatch_ip = Continuation::entry_for_builtin(id).ip();
entries.push((
name,
Value::new_inline(PrimOp {
Value::new(PrimOp {
id,
arity,
dispatch_ip,
@@ -75,21 +74,15 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
let consts = [
(
"__currentSystem",
Value::new_inline(ctx.intern_string("x86_64-linux")),
Value::new(ctx.intern_string("x86_64-linux")),
),
("__langVersion", Value::new_inline(6i32)),
(
"__nixVersion",
Value::new_inline(ctx.intern_string("2.24.0")),
),
(
"__storeDir",
Value::new_inline(ctx.intern_string("/nix/store")),
),
("__nixPath", Value::new_gc(Gc::new(mc, List::default()))),
("null", Value::new_inline(Null)),
("true", Value::new_inline(true)),
("false", Value::new_inline(false)),
("__langVersion", Value::new(6i32)),
("__nixVersion", Value::new(ctx.intern_string("2.24.0"))),
("__storeDir", Value::new(ctx.intern_string("/nix/store"))),
("__nixPath", Value::new(Gc::new(mc, List::default()))),
("null", Value::new(Null)),
("true", Value::new(true)),
("false", Value::new(false)),
];
for (name, val) in consts {
@@ -100,12 +93,12 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
let self_ref_thunk = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
let sym = ctx.intern_string("builtins");
entries.push((sym, Value::new_gc(self_ref_thunk)));
entries.push((sym, Value::new(self_ref_thunk)));
entries.sort_by_key(|(k, _)| *k);
let builtins_set = Gc::new(mc, AttrSet::from_sorted_unchecked(entries));
let builtins_value = Value::new_gc(builtins_set);
let builtins_value = Value::new(builtins_set);
*self_ref_thunk.borrow_mut(mc) =
ThunkState::Evaluated(builtins_value.restrict().expect("builtins is not a thunk"));
builtins_value
@@ -126,8 +119,8 @@ impl<'gc> Vm<'gc> {
scope_slots: Vec::new(),
builtins,
empty_list: Value::new_gc(Gc::new(mc, List::default())),
empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())),
empty_list: Value::new(Gc::new(mc, List::default())),
empty_attrs: Value::new(Gc::new(mc, AttrSet::default())),
force_mode,
@@ -137,45 +130,20 @@ impl<'gc> Vm<'gc> {
functor_sym: ctx.intern_string("__functor"),
}
}
}
#[inline(always)]
fn finish_ok(&mut self, val: fix_common::Value) -> Step {
self.result = Some(Ok(val));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_err(&mut self, err: Box<Error>) -> Step {
self.result = Some(Err(err));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
self.result = Some(Err(Error::eval_error(format!(
"expected {expected}, got {got}"
))));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_vm_err(&mut self, err: VmError) -> Step {
self.finish_err(err.into_error())
}
impl<'gc> Machine<'gc> for Vm<'gc> {
#[inline(always)]
fn push(&mut self, val: Value<'gc>) {
self.stack.push(val);
}
#[inline(always)]
#[must_use]
fn pop(&mut self) -> Value<'gc> {
self.stack.pop().expect("stack underflow")
}
#[inline(always)]
#[must_use]
fn peek(&self, depth: usize) -> Value<'gc> {
*self
.stack
@@ -184,7 +152,6 @@ impl<'gc> Vm<'gc> {
}
#[inline(always)]
#[must_use]
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
self.stack
.get(self.stack.len() - depth - 1)
@@ -193,6 +160,15 @@ impl<'gc> Vm<'gc> {
.expect("forced")
}
#[inline(always)]
fn pop_forced(&mut self) -> StrictValue<'gc> {
self.stack
.pop()
.expect("stack underflow")
.restrict()
.expect("forced")
}
#[inline(always)]
fn replace(&mut self, depth: usize, val: Value<'gc>) {
let len = self.stack.len();
@@ -203,72 +179,13 @@ impl<'gc> Vm<'gc> {
}
#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
fn pop_forced(&mut self) -> StrictValue<'gc> {
self.stack
.pop()
.expect("stack underflow")
.restrict()
.expect("forced")
}
/// Force the top `T::WIDTH` stack slots and return them as `T`.
///
/// If any slot holds a pending thunk, this method pushes a call frame
/// whose resume PC is the **start of the current instruction**
/// (`reader.inst_start_pc()`), enters the thunk, and returns
/// `Break::Force`. When the thunk eventually returns, the VM will
/// **re-execute the entire opcode handler from the beginning**.
///
/// # Invariants
///
/// * **Do not call this method more than once in a single handler.**
/// If you need to force multiple values, use a tuple type such as
/// `(StrictValue, StrictValue)` so they are forced and popped in one
/// atomic operation. Calling `force_and_retry` twice (or more)
/// means the handler will be re-run from the top after each retry;
/// any stack modifications between the two calls would be duplicated
/// and corrupt the stack layout.
///
/// * The caller must ensure that the stack layout at the point of
/// invocation is **identical** every time the handler is re-entered.
/// In practice this means no pushes, pops, or local mutations may
/// happen before the call, and the call must be the first thing
/// that consumes the instruction's operand values.
///
/// * The return value must be propagated with `?` so that
/// `Break::Force` correctly unwinds to the dispatch loop.
#[inline(always)]
fn force_and_retry<T: Forced<'gc>>(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> std::ops::ControlFlow<Break, T> {
self.force_and_retry_pc(reader, mc, reader.inst_start_pc())
}
/// Same as [`force_and_retry`](Self::force_and_retry) but allows
/// specifying a custom resume PC.
#[inline(always)]
fn force_and_retry_pc<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))
fn drop_n(&mut self, depth: usize) {
self.stack.truncate(self.stack.len() - depth);
}
#[inline(always)]
#[allow(unused)]
fn force_slot(
&mut self,
depth: usize,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())
fn stack_len(&self) -> usize {
self.stack.len()
}
#[inline(always)]
@@ -279,7 +196,7 @@ impl<'gc> Vm<'gc> {
mc: &Mutation<'gc>,
resume_pc: usize,
) -> Step {
let Some(thunk) = self.peek(depth).as_gc::<Thunk>() else {
let Some(thunk) = self.peek(depth).downcast::<Thunk>() else {
return Step::Continue(());
};
let mut state = thunk.borrow_mut(mc);
@@ -313,54 +230,6 @@ impl<'gc> Vm<'gc> {
}
}
}
}
impl<'gc> Machine<'gc> for Vm<'gc> {
#[inline(always)]
fn push(&mut self, val: Value<'gc>) {
self.push(val);
}
#[inline(always)]
fn pop(&mut self) -> Value<'gc> {
self.pop()
}
#[inline(always)]
fn peek(&self, depth: usize) -> Value<'gc> {
Vm::peek(self, depth)
}
#[inline(always)]
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
Vm::peek_forced(self, depth)
}
#[inline(always)]
fn pop_forced(&mut self) -> StrictValue<'gc> {
self.pop_forced()
}
#[inline(always)]
fn replace(&mut self, depth: usize, val: Value<'gc>) {
self.replace(depth, val);
}
#[inline(always)]
fn stack_len(&self) -> usize {
self.stack.len()
}
#[inline(always)]
fn force_slot_to_pc(
&mut self,
depth: usize,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
resume_pc: usize,
) -> Step {
self.force_slot_to_pc(depth, reader, mc, resume_pc)
}
#[inline(always)]
fn call(
@@ -370,7 +239,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
arg: Value<'gc>,
resume_pc: usize,
) -> Step {
self.call(reader, mc, arg, resume_pc)
instructions::call(self, reader, mc, arg, resume_pc)
}
#[inline(always)]
@@ -409,18 +278,23 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
}
#[inline(always)]
fn finish_ok(&mut self, val: fix_common::Value) -> Step {
self.finish_ok(val)
fn finish_ok(&mut self, val: fix_lang::Value) -> Step {
self.result = Some(Ok(val));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_err(&mut self, err: Box<Error>) -> Step {
self.finish_err(err)
self.result = Some(Err(err));
Step::Break(Break::Done)
}
#[inline(always)]
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
self.finish_type_err(expected, got)
self.result = Some(Err(Error::eval_error(format!(
"expected {expected}, got {got}"
))));
Step::Break(Break::Done)
}
#[inline(always)]
@@ -481,7 +355,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
enum Action {
Continue { pc: usize },
Done(Result<fix_common::Value>),
Done(Result<fix_lang::Value>),
LoadFile(PendingLoad),
}
@@ -504,7 +378,7 @@ impl Vm<'_> {
ctx: &mut C,
ip: InstructionPtr,
force_mode: ForceMode,
) -> Result<fix_common::Value> {
) -> Result<fix_lang::Value> {
let (code, runtime) = ctx.split();
let mut arena: Arena<Rootable![Vm<'_>]> = Arena::new(|mc| Vm::new(force_mode, mc, runtime));
arena.metrics().set_pacing(Pacing {
@@ -589,12 +463,15 @@ impl<'gc> Vm<'gc> {
pc: usize,
mc: &Mutation<'gc>,
) -> Action {
use fix_codegen::Op::*;
use fix_bytecode::Op::*;
use instructions::*;
let mut reader = BytecodeReader::new(bytecode, pc);
let mut fuel = Self::DEFAULT_FUEL_AMOUNT;
loop {
use crate::instructions::op_dispatch_cont;
if fuel == 0 {
return Action::Continue { pc: reader.pc() };
}
@@ -603,75 +480,75 @@ impl<'gc> Vm<'gc> {
let op = reader.read_op();
let result = match op {
PushSmi => self.op_push_smi(&mut reader),
PushBigInt => self.op_push_bigint(&mut reader, mc),
PushFloat => self.op_push_float(&mut reader),
PushString => self.op_push_string(&mut reader),
PushNull => self.op_push_null(),
PushTrue => self.op_push_true(),
PushFalse => self.op_push_false(),
PushSmi => op_push_smi(self, &mut reader),
PushBigInt => op_push_bigint(self, &mut reader, mc),
PushFloat => op_push_float(self, &mut reader),
PushString => op_push_string(self, &mut reader),
PushNull => op_push_null(self),
PushTrue => op_push_true(self),
PushFalse => op_push_false(self),
LoadLocal => self.op_load_local(&mut reader),
LoadOuter => self.op_load_outer(&mut reader),
StoreLocal => self.op_store_local(&mut reader, mc),
AllocLocals => self.op_alloc_locals(&mut reader, mc),
LoadLocal => op_load_local(self, &mut reader),
LoadOuter => op_load_outer(self, &mut reader),
StoreLocal => op_store_local(self, &mut reader, mc),
AllocLocals => op_alloc_locals(self, &mut reader, mc),
MakeThunk => self.op_make_thunk(&mut reader, mc),
MakeClosure => self.op_make_closure(&mut reader, mc),
MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc),
MakeThunk => op_make_thunk(self, &mut reader, mc),
MakeClosure => op_make_closure(self, &mut reader, mc),
MakePatternClosure => op_make_pattern_closure(self, &mut reader, mc),
Call => self.op_call(ctx, &mut reader, mc),
DispatchPrimOp => self.op_dispatch_primop(ctx, &mut reader, mc),
Return => self.op_return(ctx, &mut reader, mc),
Call => op_call(self, ctx, &mut reader, mc),
DispatchCont => op_dispatch_cont(self, ctx, &mut reader, mc),
Return => op_return(self, ctx, &mut reader, mc),
MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc),
MakeEmptyAttrs => self.op_make_empty_attrs(),
SelectStatic => self.op_select_static(ctx, &mut reader, mc),
SelectDynamic => self.op_select_dynamic(ctx, &mut reader, mc),
HasAttrPathStatic => self.op_has_attr_path_static(ctx, &mut reader, mc),
HasAttrPathDynamic => self.op_has_attr_path_dynamic(ctx, &mut reader, mc),
HasAttrStatic => self.op_has_attr_static(&mut reader, mc),
HasAttrDynamic => self.op_has_attr_dynamic(ctx, &mut reader, mc),
HasAttrResolve => self.op_has_attr_resolve(),
JumpIfSelectFailed => self.op_jump_if_select_failed(&mut reader),
JumpIfSelectSucceeded => self.op_jump_if_select_succeeded(&mut reader),
MakeAttrs => op_make_attrs(self, ctx, &mut reader, mc),
MakeEmptyAttrs => op_make_empty_attrs(self),
SelectStatic => op_select_static(self, ctx, &mut reader, mc),
SelectDynamic => op_select_dynamic(self, ctx, &mut reader, mc),
HasAttrPathStatic => op_has_attr_path_static(self, ctx, &mut reader, mc),
HasAttrPathDynamic => op_has_attr_path_dynamic(self, ctx, &mut reader, mc),
HasAttrStatic => op_has_attr_static(self, &mut reader, mc),
HasAttrDynamic => op_has_attr_dynamic(self, ctx, &mut reader, mc),
HasAttrResolve => op_has_attr_resolve(self),
JumpIfSelectFailed => op_jump_if_select_failed(self, &mut reader),
JumpIfSelectSucceeded => op_jump_if_select_succeeded(self, &mut reader),
MakeList => self.op_make_list(ctx, &mut reader, mc),
MakeEmptyList => self.op_make_empty_list(),
MakeList => op_make_list(self, ctx, &mut reader, mc),
MakeEmptyList => op_make_empty_list(self),
OpAdd => self.op_add(ctx, &mut reader, mc),
OpSub => self.op_sub(&mut reader, mc),
OpMul => self.op_mul(&mut reader, mc),
OpDiv => self.op_div(&mut reader, mc),
OpEq => self.op_eq(ctx, &mut reader, mc),
OpNeq => self.op_neq(ctx, &mut reader, mc),
OpLt => self.op_lt(ctx, &mut reader, mc),
OpGt => self.op_gt(ctx, &mut reader, mc),
OpLeq => self.op_leq(ctx, &mut reader, mc),
OpGeq => self.op_geq(ctx, &mut reader, mc),
OpConcat => self.op_concat(&mut reader, mc),
OpUpdate => self.op_update(&mut reader, mc),
OpAdd => op_add(self, ctx, &mut reader, mc),
OpSub => op_sub(self, &mut reader, mc),
OpMul => op_mul(self, &mut reader, mc),
OpDiv => op_div(self, &mut reader, mc),
OpEq => op_eq(self, ctx, &mut reader, mc),
OpNeq => op_neq(self, ctx, &mut reader, mc),
OpLt => op_lt(self, ctx, &mut reader, mc),
OpGt => op_gt(self, ctx, &mut reader, mc),
OpLeq => op_leq(self, ctx, &mut reader, mc),
OpGeq => op_geq(self, ctx, &mut reader, mc),
OpConcat => op_concat(self, &mut reader, mc),
OpUpdate => op_update(self, &mut reader, mc),
OpNeg => self.op_neg(&mut reader, mc),
OpNot => self.op_not(&mut reader, mc),
OpNeg => op_neg(self, &mut reader, mc),
OpNot => op_not(self, &mut reader, mc),
JumpIfFalse => self.op_jump_if_false(&mut reader, mc),
JumpIfTrue => self.op_jump_if_true(&mut reader, mc),
Jump => self.op_jump(&mut reader),
JumpIfFalse => op_jump_if_false(self, &mut reader, mc),
JumpIfTrue => op_jump_if_true(self, &mut reader, mc),
Jump => op_jump(self, &mut reader),
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc),
CoerceToString => self.op_coerce_to_string(&mut reader, mc),
ResolvePath => self.op_resolve_path(ctx, &mut reader, mc),
ConcatStrings => op_concat_strings(self, ctx, &mut reader, mc),
CoerceToString => op_coerce_to_string(self, &mut reader, mc),
ResolvePath => op_resolve_path(self, ctx, &mut reader, mc),
Assert => self.op_assert(ctx, &mut reader, mc),
Assert => op_assert(self, ctx, &mut reader, mc),
LookupWith => self.op_lookup_with(ctx, &mut reader, mc),
LookupWith => op_lookup_with(self, ctx, &mut reader, mc),
LoadBuiltins => self.op_load_builtins(),
LoadBuiltin => self.op_load_builtin(&mut reader),
LoadBuiltins => op_load_builtins(self),
LoadBuiltin => op_load_builtin(self, &mut reader),
LoadReplBinding => self.op_load_repl_binding(&mut reader),
LoadScopedBinding => self.op_load_scoped_binding(ctx, &mut reader, mc),
LoadReplBinding => op_load_repl_binding(self, &mut reader),
LoadScopedBinding => op_load_scoped_binding(self, ctx, &mut reader, mc),
Illegal => unreachable!(),
};
@@ -2,16 +2,16 @@
//! `builtins.unsafeDiscardStringContext`,
//! `builtins.unsafeDiscardOutputDependency`.
//!
//! See `fix-abstract-vm/src/string_context.rs` for the
//! See `fix-runtime/src/string_context.rs` for the
//! `StringContextElem` type.
use fix_abstract_vm::{
use fix_bytecode::Continuation;
use fix_error::Error;
use fix_lang::StringId;
use fix_runtime::{
AttrSet, BytecodeReader, List as VmList, Machine, MachineExt, NixString, NixType, Step,
StrictValue, StringContext, StringContextElem, Value, VmRuntimeCtx, VmRuntimeCtxExt,
};
use fix_builtins::PrimOpPhase;
use fix_common::StringId;
use fix_error::Error;
use gc_arena::{Gc, Mutation};
use smallvec::SmallVec;
@@ -22,11 +22,11 @@ pub fn has_context<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>,
) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if !val.is::<StringId>() && val.as_gc::<NixString>().is_none() {
if !val.is::<StringId>() && val.downcast::<NixString>().is_none() {
return m.finish_type_err(NixType::String, val.ty());
}
let has_ctx = !ctx.get_string_context(val).is_empty();
m.return_from_primop(Value::new_inline(has_ctx), reader)
m.return_from_primop(Value::new(has_ctx), reader)
}
pub fn unsafe_discard_string_context<'gc, M: Machine<'gc>>(
@@ -36,14 +36,14 @@ pub fn unsafe_discard_string_context<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>,
) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if let Some(sid) = val.as_inline::<StringId>() {
return m.return_from_primop(Value::new_inline(sid), reader);
if let Some(sid) = val.downcast::<StringId>() {
return m.return_from_primop(Value::new(sid), reader);
}
let Some(ns) = val.as_gc::<NixString>() else {
let Some(ns) = val.downcast::<NixString>() else {
return m.finish_type_err(NixType::String, val.ty());
};
let sid = ctx.intern_string(ns.as_str());
m.return_from_primop(Value::new_inline(sid), reader)
m.return_from_primop(Value::new(sid), reader)
}
pub fn unsafe_discard_output_dependency<'gc, M: Machine<'gc>>(
@@ -53,15 +53,15 @@ pub fn unsafe_discard_output_dependency<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>,
) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if let Some(sid) = val.as_inline::<StringId>() {
return m.return_from_primop(Value::new_inline(sid), reader);
if let Some(sid) = val.downcast::<StringId>() {
return m.return_from_primop(Value::new(sid), reader);
}
let Some(ns) = val.as_gc::<NixString>() else {
let Some(ns) = val.downcast::<NixString>() else {
return m.finish_type_err(NixType::String, val.ty());
};
if ns.context().is_empty() {
let sid = ctx.intern_string(ns.as_str());
return m.return_from_primop(Value::new_inline(sid), reader);
return m.return_from_primop(Value::new(sid), reader);
}
let mut new_ctx = StringContext::new();
@@ -77,7 +77,7 @@ pub fn unsafe_discard_output_dependency<'gc, M: Machine<'gc>>(
let s: Box<str> = ns.as_str().into();
let new_ns = Gc::new(mc, NixString::with_context(s, new_ctx));
m.return_from_primop(Value::new_gc(new_ns), reader)
m.return_from_primop(Value::new(new_ns), reader)
}
pub fn get_context<'gc, M: Machine<'gc>>(
@@ -87,7 +87,7 @@ pub fn get_context<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>,
) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if !val.is::<StringId>() && val.as_gc::<NixString>().is_none() {
if !val.is::<StringId>() && val.downcast::<NixString>().is_none() {
return m.finish_type_err(NixType::String, val.ty());
}
let elems = ctx.get_string_context(val);
@@ -135,29 +135,29 @@ pub fn get_context<'gc, M: Machine<'gc>>(
let mut sub: SmallVec<[(StringId, Value<'gc>); 4]> = SmallVec::new();
if info.all_outputs {
sub.push((ctx.intern_string("allOutputs"), Value::new_inline(true)));
sub.push((ctx.intern_string("allOutputs"), Value::new(true)));
}
if !info.outputs.is_empty() {
let items: smallvec::SmallVec<[Value<'gc>; 4]> = info
.outputs
.iter()
.map(|o| Value::new_inline(ctx.intern_string(o)))
.map(|o| Value::new(ctx.intern_string(o)))
.collect();
let list = VmList::new(mc, items);
sub.push((ctx.intern_string("outputs"), Value::new_gc(list)));
sub.push((ctx.intern_string("outputs"), Value::new(list)));
}
if info.path {
sub.push((ctx.intern_string("path"), Value::new_inline(true)));
sub.push((ctx.intern_string("path"), Value::new(true)));
}
sub.sort_by_key(|(k, _)| *k);
let sub_attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(sub));
outer_entries.push((ctx.intern_string(&path), Value::new_gc(sub_attrs)));
outer_entries.push((ctx.intern_string(&path), Value::new(sub_attrs)));
}
outer_entries.sort_by_key(|(k, _)| *k);
let outer = Gc::new(mc, AttrSet::from_sorted_unchecked(outer_entries));
m.return_from_primop(Value::new_gc(outer), reader)
m.return_from_primop(Value::new(outer), reader)
}
/// appendContext :: String -> AttrSet -> String
@@ -192,11 +192,11 @@ pub fn append_context<'gc, M: Machine<'gc>>(
let acc = Gc::new(mc, NixString::with_context("", initial_ctx));
m.push(str_val.relax());
m.push(Value::new_gc(attrs));
m.push(Value::new_inline(0i32));
m.push(Value::new_gc(acc));
m.push(Value::new(attrs));
m.push(Value::new(0i32));
m.push(Value::new(acc));
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
Step::Continue(())
}
@@ -207,9 +207,9 @@ pub fn append_context_loop<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>,
) -> Step {
#[allow(clippy::unwrap_used)]
let idx = m.peek(1).as_inline::<i32>().unwrap();
let idx = m.peek(1).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let attrs = m.peek_forced(2).as_gc::<AttrSet>().unwrap();
let attrs = m.peek_forced(2).downcast::<AttrSet>().unwrap();
if idx as usize >= attrs.entries.len() {
return append_context_finalize(m, ctx, reader, mc);
@@ -221,9 +221,9 @@ pub fn append_context_loop<'gc, M: Machine<'gc>>(
0,
reader,
mc,
PrimOpPhase::AppendContextEntryForced.ip() as usize,
Continuation::PAppendContextEntryForced.ip() as usize,
)?;
reader.set_pc(PrimOpPhase::AppendContextEntryForced.ip() as usize);
reader.set_pc(Continuation::PAppendContextEntryForced.ip() as usize);
Step::Continue(())
}
@@ -238,14 +238,14 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
// Evaluated value into the slot.
m.force_slot(0, reader, mc)?;
let entry_val = m.peek_forced(0);
let Some(entry_attrs) = entry_val.as_gc::<AttrSet>() else {
let Some(entry_attrs) = entry_val.downcast::<AttrSet>() else {
return m.finish_type_err(NixType::AttrSet, entry_val.ty());
};
#[allow(clippy::unwrap_used)]
let idx = m.peek(2).as_inline::<i32>().unwrap();
let idx = m.peek(2).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let outer = m.peek_forced(3).as_gc::<AttrSet>().unwrap();
let outer = m.peek_forced(3).downcast::<AttrSet>().unwrap();
let path_key = outer.entries[idx as usize].0;
let path_str_owned: Box<str> = ctx.resolve_string(path_key).into();
if !path_str_owned.starts_with("/nix/store/") {
@@ -263,11 +263,11 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
let outputs_id = ctx.intern_string("outputs");
#[allow(clippy::unwrap_used)]
let acc_gc = m.peek(1).as_gc::<NixString>().unwrap();
let acc_gc = m.peek(1).downcast::<NixString>().unwrap();
let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
if let Some(v) = entry_attrs.lookup(path_id)
&& v.as_inline::<bool>() == Some(true)
&& v.downcast::<bool>() == Some(true)
{
new_acc.insert(StringContextElem::Opaque {
path: path_str_owned.clone(),
@@ -275,7 +275,7 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
}
if let Some(v) = entry_attrs.lookup(all_outputs_id)
&& v.as_inline::<bool>() == Some(true)
&& v.downcast::<bool>() == Some(true)
{
if !path_str_owned.ends_with(".drv") {
return m.finish_err(Error::eval_error(format!(
@@ -288,7 +288,7 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
}
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc));
m.replace(1, Value::new_gc(new_acc_gc));
m.replace(1, Value::new(new_acc_gc));
if let Some(outputs_val) = entry_attrs.lookup(outputs_id) {
m.replace(0, outputs_val);
@@ -296,17 +296,17 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
0,
reader,
mc,
PrimOpPhase::AppendContextOutputsForced.ip() as usize,
Continuation::PAppendContextOutputsForced.ip() as usize,
)?;
reader.set_pc(PrimOpPhase::AppendContextOutputsForced.ip() as usize);
reader.set_pc(Continuation::PAppendContextOutputsForced.ip() as usize);
return Step::Continue(());
}
let _ = m.pop();
#[allow(clippy::unwrap_used)]
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
m.replace(1, Value::new_inline(idx_back + 1));
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
let idx_back = m.peek(1).downcast::<i32>().unwrap();
m.replace(1, Value::new(idx_back + 1));
reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
Step::Continue(())
}
@@ -318,21 +318,21 @@ pub fn append_context_outputs_forced<'gc, M: Machine<'gc>>(
) -> Step {
m.force_slot(0, reader, mc)?;
let list_val = m.peek_forced(0);
let Some(list) = list_val.as_gc::<VmList>() else {
let Some(list) = list_val.downcast::<VmList>() else {
return m.finish_type_err(NixType::List, list_val.ty());
};
if list.inner.borrow().is_empty() {
// Stack: [strVal, attrs, idx, acc, list] -> drop list, bump idx.
let _ = m.pop();
#[allow(clippy::unwrap_used)]
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
m.replace(1, Value::new_inline(idx_back + 1));
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
let idx_back = m.peek(1).downcast::<i32>().unwrap();
m.replace(1, Value::new(idx_back + 1));
reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
return Step::Continue(());
}
m.push(Value::new_inline(0i32));
reader.set_pc(PrimOpPhase::AppendContextOutputElementLoop.ip() as usize);
m.push(Value::new(0i32));
reader.set_pc(Continuation::PAppendContextOutputElementLoop.ip() as usize);
Step::Continue(())
}
@@ -343,9 +343,9 @@ pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>,
) -> Step {
#[allow(clippy::unwrap_used)]
let oidx = m.peek(0).as_inline::<i32>().unwrap();
let oidx = m.peek(0).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(1).as_gc::<VmList>().unwrap();
let list = m.peek_forced(1).downcast::<VmList>().unwrap();
let len = list.inner.borrow().len();
if oidx as usize >= len {
// Stack: [strVal, attrs, idx, acc, list, oidx] -> drop oidx & list,
@@ -353,9 +353,9 @@ pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
let _ = m.pop();
let _ = m.pop();
#[allow(clippy::unwrap_used)]
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
m.replace(1, Value::new_inline(idx_back + 1));
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
let idx_back = m.peek(1).downcast::<i32>().unwrap();
m.replace(1, Value::new(idx_back + 1));
reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
return Step::Continue(());
}
@@ -365,9 +365,9 @@ pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
0,
reader,
mc,
PrimOpPhase::AppendContextOutputElementForced.ip() as usize,
Continuation::PAppendContextOutputElementForced.ip() as usize,
)?;
reader.set_pc(PrimOpPhase::AppendContextOutputElementForced.ip() as usize);
reader.set_pc(Continuation::PAppendContextOutputElementForced.ip() as usize);
Step::Continue(())
}
@@ -385,9 +385,9 @@ pub fn append_context_output_element_forced<'gc, M: Machine<'gc>>(
let output_name: Box<str> = output_name.into();
#[allow(clippy::unwrap_used)]
let idx = m.peek(4).as_inline::<i32>().unwrap();
let idx = m.peek(4).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let outer = m.peek_forced(5).as_gc::<AttrSet>().unwrap();
let outer = m.peek_forced(5).downcast::<AttrSet>().unwrap();
let path_key = outer.entries[idx as usize].0;
let path_str: Box<str> = ctx.resolve_string(path_key).into();
if !path_str.ends_with(".drv") {
@@ -397,22 +397,22 @@ pub fn append_context_output_element_forced<'gc, M: Machine<'gc>>(
}
#[allow(clippy::unwrap_used)]
let acc_gc = m.peek(3).as_gc::<NixString>().unwrap();
let acc_gc = m.peek(3).downcast::<NixString>().unwrap();
let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
new_acc.insert(StringContextElem::Built {
drv_path: path_str,
output: output_name,
});
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc));
m.replace(3, Value::new_gc(new_acc_gc));
m.replace(3, Value::new(new_acc_gc));
// Stack: [strVal, attrs, idx, acc, list, oidx, outElem] -> drop outElem,
// bump oidx in place.
let _ = m.pop();
#[allow(clippy::unwrap_used)]
let oidx = m.peek(0).as_inline::<i32>().unwrap();
m.replace(0, Value::new_inline(oidx + 1));
reader.set_pc(PrimOpPhase::AppendContextOutputElementLoop.ip() as usize);
let oidx = m.peek(0).downcast::<i32>().unwrap();
m.replace(0, Value::new(oidx + 1));
reader.set_pc(Continuation::PAppendContextOutputElementLoop.ip() as usize);
Step::Continue(())
}
@@ -424,7 +424,7 @@ fn append_context_finalize<'gc, M: Machine<'gc>>(
) -> Step {
// Stack: [strVal, attrs, idx, acc]
#[allow(clippy::unwrap_used)]
let acc_gc = m.pop().as_gc::<NixString>().unwrap();
let acc_gc = m.pop().downcast::<NixString>().unwrap();
let _ = m.pop(); // idx
let _ = m.pop(); // attrs
let str_val_raw = m.pop();
@@ -438,10 +438,10 @@ fn append_context_finalize<'gc, M: Machine<'gc>>(
let context: StringContext = acc_gc.context().iter().cloned().collect();
let result = if context.is_empty() {
let sid = ctx.intern_string(s_str);
Value::new_inline(sid)
Value::new(sid)
} else {
let ns = Gc::new(mc, NixString::with_context(s_str, context));
Value::new_gc(ns)
Value::new(ns)
};
m.return_from_primop(result, reader)
}
@@ -1,9 +1,9 @@
use fix_abstract_vm::{
use fix_bytecode::Continuation;
use fix_error::Error;
use fix_runtime::{
AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Step, StrictValue, Value,
VmRuntimeCtx, VmRuntimeCtxExt,
};
use fix_builtins::PrimOpPhase;
use fix_error::Error;
use gc_arena::{Gc, Mutation, RefLock};
use smallvec::SmallVec;
@@ -44,14 +44,14 @@ pub fn deep_seq_force_top<'gc, M: Machine<'gc>>(
let e1 = m.peek_forced(1);
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
let children: SmallVec<_> = if let Some(attrs) = e1.downcast::<AttrSet>() {
let attrs = &attrs.entries;
if attrs.is_empty() {
SmallVec::new()
} else {
attrs.iter().map(|&(_, v)| v).collect()
}
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
} else if let Some(list) = e1.downcast::<List>() {
let inner = list.inner.borrow();
if inner.is_empty() {
SmallVec::new()
@@ -69,16 +69,16 @@ pub fn deep_seq_force_top<'gc, M: Machine<'gc>>(
}
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 seen = Gc::new(mc, List::default());
let worklist = List::new(mc, children);
let e2 = m.pop();
let _ = m.pop();
m.push(e2);
m.push(Value::new_gc(seen));
m.push(Value::new_gc(worklist));
m.push(Value::new_inline(count));
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
m.push(Value::new(seen));
m.push(Value::new(worklist));
m.push(Value::new(count));
reader.set_pc(Continuation::PDeepSeqPush.ip() as usize);
Step::Continue(())
}
@@ -89,7 +89,7 @@ pub fn deep_seq_push<'gc, M: Machine<'gc>>(
) -> Step {
// stack: [e2, seen, worklist, counter]
#[allow(clippy::unwrap_used)]
let counter = m.peek(0).as_inline::<i32>().unwrap();
let counter = m.peek(0).downcast::<i32>().unwrap();
if counter == 0 {
let _ = m.pop(); // counter
let _ = m.pop(); // worklist
@@ -99,15 +99,15 @@ pub fn deep_seq_push<'gc, M: Machine<'gc>>(
}
#[allow(clippy::unwrap_used)]
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
let worklist = m.peek_forced(1).downcast::<List>().unwrap();
#[allow(clippy::unwrap_used)]
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
m.replace(0, Value::new_inline(counter - 1));
m.replace(0, Value::new(counter - 1));
m.push(item);
// force item at TOS, resume at DeepSeqLoop after force
m.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
m.force_slot_to_pc(0, reader, mc, Continuation::PDeepSeqLoop.ip() as usize)?;
reader.set_pc(Continuation::PDeepSeqLoop.ip() as usize);
Step::Continue(())
}
@@ -119,17 +119,17 @@ pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
// stack after pop: [e2, seen, worklist, counter]
let item = m.pop();
#[allow(clippy::unwrap_used)]
let counter = m.peek(0).as_inline::<i32>().unwrap();
let counter = m.peek(0).downcast::<i32>().unwrap();
let mut added: usize = 0;
if let Some(attrs) = item.as_gc::<AttrSet>() {
if let Some(attrs) = item.downcast::<AttrSet>() {
let attrs = &attrs.entries;
#[allow(clippy::unwrap_used)]
let seen = m.peek_forced(2).as_gc::<List<'gc>>().unwrap();
let seen = m.peek_forced(2).downcast::<List>().unwrap();
if !is_value_in_seen(seen, item) {
add_value_to_seen(seen, mc, item);
#[allow(clippy::unwrap_used)]
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
let worklist = m.peek_forced(1).downcast::<List>().unwrap();
{
let mut wl = worklist.unlock(mc).borrow_mut();
for &(_, v) in attrs.iter() {
@@ -138,13 +138,13 @@ pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
added = attrs.len();
}
}
} else if let Some(list) = item.as_gc::<List<'gc>>() {
} else if let Some(list) = item.downcast::<List>() {
#[allow(clippy::unwrap_used)]
let seen = m.peek_forced(2).as_gc::<List<'gc>>().unwrap();
let seen = m.peek_forced(2).downcast::<List>().unwrap();
if !is_value_in_seen(seen, item) {
add_value_to_seen(seen, mc, item);
#[allow(clippy::unwrap_used)]
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
let worklist = m.peek_forced(1).downcast::<List>().unwrap();
{
let inner = list.inner.borrow();
let mut wl = worklist.unlock(mc).borrow_mut();
@@ -156,8 +156,8 @@ pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
}
}
m.replace(0, Value::new_inline(counter + added as i32));
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
m.replace(0, Value::new(counter + added as i32));
reader.set_pc(Continuation::PDeepSeqPush.ip() as usize);
Step::Continue(())
}
@@ -170,10 +170,10 @@ pub fn force_result_shallow<'gc, M: Machine<'gc>>(
m.force_slot(0, reader, mc)?;
let val = m.peek_forced(0);
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
let (count, has_children) = if let Some(attrs) = val.downcast::<AttrSet>() {
let len = attrs.entries.len();
(len, len > 0)
} else if let Some(list) = val.as_gc::<List<'gc>>() {
} else if let Some(list) = val.downcast::<List>() {
let len = list.inner.borrow().len();
(len, len > 0)
} else {
@@ -185,9 +185,9 @@ pub fn force_result_shallow<'gc, M: Machine<'gc>>(
return m.finish_ok(ctx.convert_value(val));
}
m.push(Value::new_inline(0i32));
m.push(Value::new_inline(count as i32));
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
m.push(Value::new(0i32));
m.push(Value::new(count as i32));
reader.set_pc(Continuation::ForceResultShallowPush.ip() as usize);
Step::Continue(())
}
@@ -198,9 +198,9 @@ pub fn force_result_shallow_push<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>,
) -> Step {
#[allow(clippy::unwrap_used)]
let idx = m.peek(1).as_inline::<i32>().unwrap();
let idx = m.peek(1).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let len = m.peek(0).as_inline::<i32>().unwrap();
let len = m.peek(0).downcast::<i32>().unwrap();
if idx == len {
let _ = m.pop(); // len
@@ -210,24 +210,24 @@ pub fn force_result_shallow_push<'gc, M: Machine<'gc>>(
}
let val = m.peek_forced(2);
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
let child = if let Some(attrs) = val.downcast::<AttrSet>() {
attrs.entries.get(idx as usize).map(|&(_, v)| v)
} else if let Some(list) = val.as_gc::<List<'gc>>() {
} else if let Some(list) = val.downcast::<List>() {
list.inner.borrow().get(idx as usize).copied()
} else {
None
};
if let Some(child) = child {
m.replace(1, Value::new_inline(idx + 1));
m.replace(1, Value::new(idx + 1));
m.push(child);
m.force_slot_to_pc(
0,
reader,
mc,
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
Continuation::ForceResultShallowLoop.ip() as usize,
)?;
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
reader.set_pc(Continuation::ForceResultShallowLoop.ip() as usize);
}
Step::Continue(())
}
@@ -238,7 +238,7 @@ pub fn force_result_shallow_loop<'gc, M: Machine<'gc>>(
_mc: &Mutation<'gc>,
) -> Step {
let _ = m.pop(); // forced child
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
reader.set_pc(Continuation::ForceResultShallowPush.ip() as usize);
Step::Continue(())
}
@@ -288,7 +288,7 @@ pub fn call_functor_1<'gc, M: Machine<'gc>>(
reader,
mc,
self_val,
PrimOpPhase::CallFunctor2.ip() as usize,
Continuation::CallFunctor2.ip() as usize,
)
}
@@ -349,7 +349,7 @@ pub fn call_pattern<'gc, M: Machine<'gc>>(
let new_env = Gc::new(
mc,
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
RefLock::new(Env::with_arg(Value::new(attrset), n_locals, env)),
);
reader.set_pc(ip as usize);
m.set_env(new_env);
@@ -1,9 +1,9 @@
use fix_abstract_vm::{
use fix_error::Error;
use fix_lang::StringId;
use fix_runtime::{
BytecodeReader, Machine, MachineExt, NixString, NixType, Path, Step, StrictValue, Value,
VmRuntimeCtx,
};
use fix_common::StringId;
use fix_error::Error;
use gc_arena::Mutation;
pub fn to_string<'gc, M: Machine<'gc>>(
@@ -16,8 +16,8 @@ pub fn to_string<'gc, M: Machine<'gc>>(
if val.is::<StringId>() || val.is::<NixString>() {
return m.return_from_primop(val.relax(), reader);
}
if let Some(p) = val.as_inline::<Path>() {
return m.return_from_primop(Value::new_inline(p.0), reader);
if let Some(p) = val.downcast::<Path>() {
return m.return_from_primop(Value::new(p.0), reader);
}
// TODO: derivations / `__toString` / `outPath`,
// numbers, lists.
@@ -47,5 +47,5 @@ pub fn type_of<'gc, M: Machine<'gc>>(
NixType::Thunk => unreachable!("forced"),
};
let sid = ctx.intern_string(name);
m.return_from_primop(Value::new_inline(sid), reader)
m.return_from_primop(Value::new(sid), reader)
}
@@ -1,8 +1,8 @@
use fix_abstract_vm::{
use fix_bytecode::Continuation;
use fix_runtime::{
AttrSet, BytecodeReader, CallFrame, List, Machine, MachineExt, NixNum, Null, Path, Step,
StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
};
use fix_builtins::PrimOpPhase;
use gc_arena::{Gc, Mutation};
use smallvec::SmallVec;
@@ -17,11 +17,11 @@ pub fn start_eq<'gc, M: Machine<'gc>>(
) -> Step {
match shallow_eq(ctx, lhs, rhs) {
ShallowEq::True => {
m.push(Value::new_inline(!negate));
m.push(Value::new(!negate));
Step::Continue(())
}
ShallowEq::False => {
m.push(Value::new_inline(negate));
m.push(Value::new(negate));
Step::Continue(())
}
ShallowEq::RecurseList(la, lb) => {
@@ -30,10 +30,8 @@ pub fn start_eq<'gc, M: Machine<'gc>>(
enter_eq_machine(m, reader, mc, negate, lhs_init, rhs_init)
}
ShallowEq::RecurseAttrs(a, b) => {
let lhs_init: SmallVec<[Value<'gc>; 4]> =
a.entries.iter().map(|&(_, v)| v).collect();
let rhs_init: SmallVec<[Value<'gc>; 4]> =
b.entries.iter().map(|&(_, v)| v).collect();
let lhs_init: SmallVec<[Value<'gc>; 4]> = a.entries.iter().map(|&(_, v)| v).collect();
let rhs_init: SmallVec<[Value<'gc>; 4]> = b.entries.iter().map(|&(_, v)| v).collect();
enter_eq_machine(m, reader, mc, negate, lhs_init, rhs_init)
}
}
@@ -46,15 +44,15 @@ pub fn eq_step<'gc, M: Machine<'gc>>(
) -> Step {
let rhs_q = m
.peek(0)
.as_gc::<List<'gc>>()
.downcast::<List>()
.expect("eq state corrupted: rhs_queue");
let lhs_q = m
.peek(1)
.as_gc::<List<'gc>>()
.downcast::<List>()
.expect("eq state corrupted: lhs_queue");
let result = m
.peek(2)
.as_inline::<bool>()
.downcast::<bool>()
.expect("eq state corrupted: result");
if !result || lhs_q.inner.borrow().is_empty() {
@@ -73,7 +71,7 @@ pub fn eq_step<'gc, M: Machine<'gc>>(
.expect("non-empty rhs queue");
m.push(lhs);
m.push(rhs);
reader.set_pc(PrimOpPhase::EqForce.ip() as usize);
reader.set_pc(Continuation::EqForce.ip() as usize);
Step::Continue(())
}
@@ -85,7 +83,7 @@ pub fn eq_force<'gc, M: Machine<'gc>>(
) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
apply_pair(m, ctx, mc, lhs, rhs);
reader.set_pc(PrimOpPhase::EqStep.ip() as usize);
reader.set_pc(Continuation::EqStep.ip() as usize);
Step::Continue(())
}
@@ -94,13 +92,13 @@ fn finalize<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) ->
let _ = m.pop();
let result = m
.pop()
.as_inline::<bool>()
.downcast::<bool>()
.expect("eq state corrupted: result");
let negate = m
.pop()
.as_inline::<bool>()
.downcast::<bool>()
.expect("eq state corrupted: negate");
m.return_from_primop(Value::new_inline(result ^ negate), reader)
m.return_from_primop(Value::new(result ^ negate), reader)
}
fn apply_pair<'gc, M: Machine<'gc>>(
@@ -113,10 +111,15 @@ fn apply_pair<'gc, M: Machine<'gc>>(
match shallow_eq(ctx, lhs, rhs) {
ShallowEq::True => {}
ShallowEq::False => {
m.replace(2, Value::new_inline(false));
m.replace(2, Value::new(false));
}
ShallowEq::RecurseList(la, lb) => {
extend_queues(m, mc, la.inner.borrow().iter().copied(), lb.inner.borrow().iter().copied());
extend_queues(
m,
mc,
la.inner.borrow().iter().copied(),
lb.inner.borrow().iter().copied(),
);
}
ShallowEq::RecurseAttrs(a, b) => {
extend_queues(
@@ -137,11 +140,11 @@ where
{
let rhs_q = m
.peek(0)
.as_gc::<List<'gc>>()
.downcast::<List>()
.expect("eq state corrupted: rhs_queue");
let lhs_q = m
.peek(1)
.as_gc::<List<'gc>>()
.downcast::<List>()
.expect("eq state corrupted: lhs_queue");
let mut lq = lhs_q.unlock(mc).borrow_mut();
let mut rq = rhs_q.unlock(mc).borrow_mut();
@@ -166,11 +169,11 @@ fn enter_eq_machine<'gc, M: Machine<'gc>>(
env: m.env(),
});
m.inc_call_depth();
m.push(Value::new_inline(negate));
m.push(Value::new_inline(true));
m.push(Value::new_gc(List::new(mc, lhs_init)));
m.push(Value::new_gc(List::new(mc, rhs_init)));
reader.set_pc(PrimOpPhase::EqStep.ip() as usize);
m.push(Value::new(negate));
m.push(Value::new(true));
m.push(Value::new(List::new(mc, lhs_init)));
m.push(Value::new(List::new(mc, rhs_init)));
reader.set_pc(Continuation::EqStep.ip() as usize);
Step::Continue(())
}
@@ -195,25 +198,28 @@ fn shallow_eq<'gc>(
};
return bool_outcome(eq);
}
if let (Some(a), Some(b)) = (lhs.as_inline::<bool>(), rhs.as_inline::<bool>()) {
if let (Some(a), Some(b)) = (lhs.downcast::<bool>(), rhs.downcast::<bool>()) {
return bool_outcome(a == b);
}
if lhs.is::<Null>() && rhs.is::<Null>() {
return ShallowEq::True;
}
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
if let (Some(a), Some(b)) = (lhs.downcast::<Path>(), rhs.downcast::<Path>()) {
return bool_outcome(a.0 == b.0);
}
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
return bool_outcome(a == b);
}
if let (Some(a), Some(b)) = (lhs.as_gc::<List<'gc>>(), rhs.as_gc::<List<'gc>>()) {
if let (Some(a), Some(b)) = (lhs.downcast::<List>(), rhs.downcast::<List>()) {
if a.inner.borrow().len() != b.inner.borrow().len() {
return ShallowEq::False;
}
return ShallowEq::RecurseList(a, b);
}
if let (Some(a), Some(b)) = (lhs.as_gc::<AttrSet<'gc>>(), rhs.as_gc::<AttrSet<'gc>>()) {
if let (Some(a), Some(b)) = (
lhs.downcast::<AttrSet<'gc>>(),
rhs.downcast::<AttrSet<'gc>>(),
) {
let ae = &a.entries;
let be = &b.entries;
if ae.len() != be.len() {
@@ -230,9 +236,5 @@ fn shallow_eq<'gc>(
}
fn bool_outcome<'gc>(b: bool) -> ShallowEq<'gc> {
if b {
ShallowEq::True
} else {
ShallowEq::False
}
if b { ShallowEq::True } else { ShallowEq::False }
}
@@ -1,12 +1,12 @@
use std::path::PathBuf;
use fix_abstract_vm::{
use fix_bytecode::Continuation;
use fix_error::Error;
use fix_lang::StringId;
use fix_runtime::{
AttrSet, Break, BytecodeReader, CallFrame, Machine, MachineExt, Path, PendingLoad,
PendingScope, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, canon_path_str,
};
use fix_builtins::PrimOpPhase;
use fix_common::StringId;
use fix_error::Error;
use gc_arena::{Gc, Mutation};
use hashbrown::HashSet;
@@ -40,10 +40,10 @@ pub fn import<'gc, M: Machine<'gc>>(
// finalizer can use it as the cache key. The slot we pop here was
// freed by `force_and_retry`, so we simply push.
let path_sid = ctx.intern_string(abs.to_string_lossy());
m.push(Value::new_inline(path_sid));
m.push(Value::new(path_sid));
let env = m.env();
m.push_call_frame(CallFrame {
pc: PrimOpPhase::ImportFinalize.ip() as usize,
pc: Continuation::PImportFinalize.ip() as usize,
thunk: None,
env,
});
@@ -63,7 +63,7 @@ pub fn import_finalize<'gc, M: Machine<'gc>>(
// stack: [path_sid, return_value]
let val = m.pop();
#[allow(clippy::unwrap_used)]
let path_sid = m.pop().as_inline::<StringId>().unwrap();
let path_sid = m.pop().downcast::<StringId>().unwrap();
// The cache key is keyed by the absolute path string we interned in
// `import`. Resolve it back to the host PathBuf.
let path_str = ctx.resolve_string(path_sid).to_owned();
@@ -107,11 +107,11 @@ pub fn scoped_import<'gc, M: Machine<'gc>>(
};
let keys: HashSet<StringId> = scope_attrs.entries.iter().map(|&(k, _)| k).collect();
let slot_id = m.scope_slots_push(Value::new_gc(scope_attrs));
let slot_id = m.scope_slots_push(Value::new(scope_attrs));
let env = m.env();
m.push_call_frame(CallFrame {
pc: PrimOpPhase::ScopedImportFinalize.ip() as usize,
pc: Continuation::PScopedImportFinalize.ip() as usize,
thunk: None,
env,
});
@@ -146,7 +146,7 @@ pub fn path_exists<'gc, M: Machine<'gc>>(
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
// pathExists requires an absolute path. A `Path` value is
// always absolute; a string is accepted only if it starts with `/`.
let (path, is_path_value) = if let Some(p) = path_val.as_inline::<Path>() {
let (path, is_path_value) = if let Some(p) = path_val.downcast::<Path>() {
(ctx.resolve_string(p.0).to_owned(), true)
} else if let Some(s) = ctx.get_string(path_val) {
(s.to_owned(), false)
@@ -171,7 +171,7 @@ pub fn path_exists<'gc, M: Machine<'gc>>(
} else {
std::fs::symlink_metadata(p).is_ok()
};
m.return_from_primop(Value::new_inline(exists), reader)
m.return_from_primop(Value::new(exists), reader)
}
/// Convert the user-supplied path string into an absolute, dotted-segment
+288
View File
@@ -0,0 +1,288 @@
use fix_bytecode::Continuation;
use fix_runtime::{BytecodeReader, List, Machine, MachineExt, NixType, Step, StrictValue, Value};
use gc_arena::Mutation;
pub fn filter_force_list<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(0, reader, mc)?;
let list = match m.peek_forced(0).expect::<List>() {
Ok(list) => list,
Err(got) => return m.finish_type_err(NixType::List, got),
};
if list.inner.borrow().is_empty() {
let val = m.pop();
let _pred = m.pop();
return m.return_from_primop(val, reader);
}
// prepare stack layout: [ pred list idx acc ]
m.push(Value::new(0));
m.push(Value::new(List::new_gc(mc)));
reader.set_pc(Continuation::PFilterCallPred.ip() as usize);
Step::Continue(())
}
pub fn filter_call_pred<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(3, reader, mc)?;
let pred = m.peek_forced(3);
#[allow(clippy::unwrap_used)]
let idx = m.peek(1).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let elem = m.peek_forced(2).downcast::<List>().unwrap().inner.borrow()[idx as usize];
m.push(pred.relax());
m.call(reader, mc, elem, Continuation::PFilterCheck.ip() as usize)
}
pub fn filter_check<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let ret = m.force_and_retry::<bool>(reader, mc)?;
#[allow(clippy::unwrap_used)]
let idx = m.peek(1).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(2).downcast::<List>().unwrap();
let list = list.inner.borrow();
#[allow(clippy::unwrap_used)]
let acc = m.peek_forced(0).downcast::<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 = m.pop();
let _ = m.pop(); // idx
let _ = m.pop(); // list
let _ = m.pop(); // pred
return m.return_from_primop(acc, reader);
}
m.replace(1, Value::new(idx + 1));
reader.set_pc(Continuation::PFilterCallPred.ip() as usize);
Step::Continue(())
}
// foldl' op nul list
//
// Stack layouts across phases:
// Entry: [op, nul, list]
// Empty: [op, nul]
// Call1: [op, list, idx, acc]
// Call2: [op, list, idx, acc, intermediate]
// Update: [op, list, idx, acc, result]
pub fn foldl_strict_entry<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(0, reader, mc)?;
let list_val = m.peek_forced(0);
let Some(list) = list_val.downcast::<List>() else {
return m.finish_type_err(NixType::List, list_val.ty());
};
if list.inner.borrow().is_empty() {
let _ = m.pop(); // list
reader.set_pc(Continuation::PFoldlStrictEmpty.ip() as usize);
return Step::Continue(());
}
let list_val = m.pop();
let nul_val = m.pop();
m.push(list_val);
m.push(Value::new(0i32));
m.push(nul_val);
reader.set_pc(Continuation::PFoldlStrictCall1.ip() as usize);
Step::Continue(())
}
pub fn foldl_strict_empty<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let nul = m.force_and_retry::<StrictValue>(reader, mc)?;
let _ = m.pop(); // op
m.return_from_primop(nul.relax(), reader)
}
pub fn foldl_strict_call1<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(3, reader, mc)?;
let op = m.peek_forced(3);
let acc = m.peek(0);
m.push(op.relax());
m.call(
reader,
mc,
acc,
Continuation::PFoldlStrictCall2.ip() as usize,
)
}
pub fn foldl_strict_call2<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
#[allow(clippy::unwrap_used)]
let idx = m.peek(2).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(3).downcast::<List>().unwrap();
let elem = list.inner.borrow()[idx as usize];
m.call(
reader,
mc,
elem,
Continuation::PFoldlStrictUpdate.ip() as usize,
)
}
pub fn foldl_strict_update<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
_mc: &Mutation<'gc>,
) -> Step {
let result = m.pop();
m.replace(0, result);
#[allow(clippy::unwrap_used)]
let idx = m.peek(1).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(2).downcast::<List>().unwrap();
let len = list.inner.borrow().len();
if (idx as usize) + 1 == len {
let acc = m.pop();
let _ = m.pop(); // idx
let _ = m.pop(); // list
let _ = m.pop(); // op
return m.return_from_primop(acc, reader);
}
m.replace(1, Value::new(idx + 1));
reader.set_pc(Continuation::PFoldlStrictCall1.ip() as usize);
Step::Continue(())
}
pub fn all_entry<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(0, reader, mc)?;
let list = match m.peek_forced(0).expect::<List>() {
Ok(list) => list,
Err(got) => return m.finish_type_err(NixType::List, got),
};
// FIXME: force callable
m.force_slot(1, reader, mc)?;
if list.inner.borrow().is_empty() {
let _list = m.pop();
let _pred = m.pop();
return m.return_from_primop(Value::new(true), reader);
}
// prepare stack layout: [ pred list idx ]
m.push(Value::new(0));
reader.set_pc(Continuation::PAllCallPred.ip() as usize);
Step::Continue(())
}
pub fn all_call_pred<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let pred = m.peek_forced(2);
#[allow(clippy::unwrap_used)]
let idx = m.peek(0).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let elem = m.peek_forced(1).downcast::<List>().unwrap().inner.borrow()[idx as usize];
m.push(pred.relax());
m.call(reader, mc, elem, Continuation::PAllCheck.ip() as usize)
}
pub fn all_check<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let ret = m.force_and_retry::<bool>(reader, mc)?;
#[allow(clippy::unwrap_used)]
let idx = m.peek(0).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(1).downcast::<List>().unwrap();
let list = list.inner.borrow();
if idx as usize == list.len() - 1 || !ret {
let _ = m.pop(); // idx
let _ = m.pop(); // list
let _ = m.pop(); // pred
return m.return_from_primop(Value::new(ret), reader);
}
m.replace(0, Value::new(idx + 1));
reader.set_pc(Continuation::PAllCallPred.ip() as usize);
Step::Continue(())
}
pub fn any_entry<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
m.force_slot(0, reader, mc)?;
let list = match m.peek_forced(0).expect::<List>() {
Ok(list) => list,
Err(got) => return m.finish_type_err(NixType::List, got),
};
// FIXME: force callable
m.force_slot(1, reader, mc)?;
if list.inner.borrow().is_empty() {
let _list = m.pop();
let _pred = m.pop();
return m.return_from_primop(Value::new(false), reader);
}
// prepare stack layout: [ pred list idx ]
m.push(Value::new(0));
reader.set_pc(Continuation::PAnyCallPred.ip() as usize);
Step::Continue(())
}
pub fn any_call_pred<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let pred = m.peek_forced(2);
#[allow(clippy::unwrap_used)]
let idx = m.peek(0).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let elem = m.peek_forced(1).downcast::<List>().unwrap().inner.borrow()[idx as usize];
m.push(pred.relax());
m.call(reader, mc, elem, Continuation::PAnyCheck.ip() as usize)
}
pub fn any_check<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let ret = m.force_and_retry::<bool>(reader, mc)?;
#[allow(clippy::unwrap_used)]
let idx = m.peek(0).downcast::<i32>().unwrap();
#[allow(clippy::unwrap_used)]
let list = m.peek_forced(1).downcast::<List>().unwrap();
let list = list.inner.borrow();
if idx as usize == list.len() - 1 || ret {
let _ = m.pop(); // idx
let _ = m.pop(); // list
let _ = m.pop(); // pred
return m.return_from_primop(Value::new(ret), reader);
}
m.replace(0, Value::new(idx + 1));
reader.set_pc(Continuation::PAnyCallPred.ip() as usize);
Step::Continue(())
}
+98
View File
@@ -0,0 +1,98 @@
mod context;
mod control;
mod conv;
mod eq;
mod io;
mod list;
mod path;
pub use context::*;
pub use control::*;
pub use conv::*;
pub use eq::*;
use fix_bytecode::Continuation;
use fix_error::Error;
use fix_runtime::{BytecodeReader, Machine, Step, VmRuntimeCtx};
use gc_arena::Mutation;
pub use io::*;
pub use list::*;
pub use path::*;
#[allow(clippy::too_many_lines)]
pub fn dispatch_cont<'gc, M: Machine<'gc>>(
m: &mut M,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
use Continuation::*;
let cont = reader.read_u8();
let Ok(cont) = Continuation::try_from(cont) else {
return m.finish_err(Error::eval_error("invalid primop phase"));
};
match cont {
PAbort => abort(m, ctx, reader, mc),
PAll => all_entry(m, reader, mc),
PAllCallPred => all_call_pred(m, reader, mc),
PAllCheck => all_check(m, reader, mc),
PAny => any_entry(m, reader, mc),
PAnyCallPred => any_call_pred(m, reader, mc),
PAnyCheck => any_check(m, reader, mc),
PDeepSeq => deep_seq_force_top(m, reader, mc),
PDeepSeqPush => deep_seq_push(m, reader, mc),
PDeepSeqLoop => deep_seq_loop(m, reader, mc),
PSeq => seq(m, reader, mc),
PFilterForceList => filter_force_list(m, reader, mc),
PFilterCallPred => filter_call_pred(m, reader, mc),
PFilterCheck => filter_check(m, reader, mc),
PFoldlStrict => foldl_strict_entry(m, reader, mc),
PFoldlStrictEmpty => foldl_strict_empty(m, reader, mc),
PFoldlStrictCall1 => foldl_strict_call1(m, reader, mc),
PFoldlStrictCall2 => foldl_strict_call2(m, reader, mc),
PFoldlStrictUpdate => foldl_strict_update(m, reader, mc),
ForceResultShallow => force_result_shallow(m, ctx, reader, mc),
ForceResultShallowPush => force_result_shallow_push(m, ctx, reader, mc),
ForceResultShallowLoop => force_result_shallow_loop(m, reader, mc),
ForceResultDeepFinish => force_result_deep_finish(m, ctx, reader, mc),
EqStep => eq_step(m, reader, mc),
EqForce => eq_force(m, ctx, reader, mc),
CallPattern => call_pattern(m, ctx, reader, mc),
CallFunctor1 => call_functor_1(m, reader, mc),
CallFunctor2 => call_functor_2(m, reader, mc),
PImport => import(m, ctx, reader, mc),
PImportFinalize => import_finalize(m, ctx, reader),
PScopedImport => scoped_import(m, ctx, reader, mc),
PScopedImportFinalize => scoped_import_finalize(m, ctx, reader, mc),
PPathExists => path_exists(m, ctx, reader, mc),
PToPath => to_path(m, ctx, reader, mc),
PIsPath => is_path(m, reader, mc),
PToString => to_string(m, ctx, reader, mc),
PTypeOf => type_of(m, ctx, reader, mc),
PHasContext => has_context(m, ctx, reader, mc),
PGetContext => get_context(m, ctx, reader, mc),
PAppendContext => append_context(m, ctx, reader, mc),
PAppendContextLoop => append_context_loop(m, ctx, reader, mc),
PAppendContextEntryForced => append_context_entry_forced(m, ctx, reader, mc),
PAppendContextOutputsForced => append_context_outputs_forced(m, ctx, reader, mc),
PAppendContextOutputElementLoop => append_context_output_element_loop(m, ctx, reader, mc),
PAppendContextOutputElementForced => {
append_context_output_element_forced(m, ctx, reader, mc)
}
PUnsafeDiscardStringContext => unsafe_discard_string_context(m, ctx, reader, mc),
PUnsafeDiscardOutputDependency => unsafe_discard_output_dependency(m, ctx, reader, mc),
phase => todo!("primop phase {phase:?}"),
}
}
@@ -1,8 +1,8 @@
use fix_abstract_vm::{
use fix_error::Error;
use fix_runtime::{
BytecodeReader, Machine, MachineExt, Path, Step, StrictValue, Value, VmRuntimeCtx,
VmRuntimeCtxExt, canon_path_str,
};
use fix_error::Error;
use gc_arena::Mutation;
pub fn to_path<'gc, M: Machine<'gc>>(
@@ -13,8 +13,8 @@ pub fn to_path<'gc, M: Machine<'gc>>(
) -> Step {
// coerce to path THEN TO STRING
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if let Some(Path(s)) = val.as_inline::<Path>() {
return m.return_from_primop(Value::new_inline(s), reader);
if let Some(Path(s)) = val.downcast::<Path>() {
return m.return_from_primop(Value::new(s), reader);
}
let Some(s) = ctx.get_string(val) else {
return m.finish_err(Error::eval_error(format!(
@@ -29,7 +29,7 @@ pub fn to_path<'gc, M: Machine<'gc>>(
}
let canon = canon_path_str(s);
let sid = ctx.intern_string(canon);
m.return_from_primop(Value::new_inline(sid), reader)
m.return_from_primop(Value::new(sid), reader)
}
pub fn is_path<'gc, M: Machine<'gc>>(
@@ -39,5 +39,5 @@ pub fn is_path<'gc, M: Machine<'gc>>(
) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
let is_path = val.is::<Path>();
m.return_from_primop(Value::new_inline(is_path), reader)
m.return_from_primop(Value::new(is_path), reader)
}
+17 -24
View File
@@ -3,6 +3,18 @@ name = "fix"
version = "0.1.0"
edition = "2024"
[[bench]]
harness = false
name = "basic_ops"
[[bench]]
harness = false
name = "builtins"
[[bench]]
harness = false
name = "thunk_scope"
[dependencies]
mimalloc = "0.1"
@@ -17,43 +29,24 @@ clap = { version = "4", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
miette = { version = "7.4", features = ["fancy"] }
# Error Reporting
thiserror = "2"
miette = { version = "7.4", features = ["fancy"] }
# Data Structure
hashbrown = { workspace = true }
string-interner = { workspace = true }
# Memory Management
bumpalo = { workspace = true }
rnix = { workspace = true }
ere = { workspace = true }
ghost-cell = { workspace = true }
fix-abstract-vm = { path = "../fix-abstract-vm" }
fix-builtins = { path = "../fix-builtins" }
fix-common = { path = "../fix-common" }
fix-codegen = { path = "../fix-codegen" }
fix-bytecode = { path = "../fix-bytecode" }
fix-compiler = { path = "../fix-compiler" }
fix-error = { path = "../fix-error" }
fix-ir = { path = "../fix-ir" }
fix-lang = { path = "../fix-lang" }
fix-runtime = { path = "../fix-runtime" }
fix-vm = { path = "../fix-vm" }
[dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] }
tempfile = "3.24"
test-log = { version = "0.2", features = ["trace"] }
[[bench]]
name = "basic_ops"
harness = false
[[bench]]
name = "builtins"
harness = false
[[bench]]
name = "thunk_scope"
harness = false
+1 -1
View File
@@ -1,8 +1,8 @@
#![allow(dead_code)]
use fix::Evaluator;
use fix_common::Value;
use fix_error::{Result, Source};
use fix_lang::Value;
pub fn eval(expr: &str) -> Value {
Evaluator::new()
+18 -589
View File
@@ -1,20 +1,13 @@
#![warn(clippy::unwrap_used)]
#![allow(dead_code)]
use bumpalo::Bump;
use fix_abstract_vm::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx};
use fix_builtins::PrimOpPhase;
use fix_codegen::disassembler::{Disassembler, DisassemblerContext};
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::{
GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef,
ThunkId,
};
use fix_bytecode::InstructionPtr;
use fix_bytecode::disassembler::{Disassembler, DisassemblerContext};
use fix_compiler::{CodeState, ExtraScope};
use fix_error::{Result, Source};
use fix_lang::StringId;
use fix_runtime::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx};
use fix_vm::Vm;
use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{HashMap, HashSet};
use string_interner::{DefaultStringInterner, Symbol as _};
@@ -29,15 +22,6 @@ pub struct RuntimeState {
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, MaybeThunk>,
pub current_scope_slot: Option<u32>,
}
pub struct Evaluator {
pub runtime: RuntimeState,
pub code: CodeState,
@@ -52,37 +36,25 @@ impl Default for Evaluator {
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);
}
let code = CodeState::new(&mut strings);
Self {
runtime: RuntimeState {
strings,
constants: Constants::default(),
},
code: CodeState {
sources: Vec::new(),
spans: Vec::new(),
thunk_count: 0,
bytecode,
global_env,
current_scope_slot: None,
},
code,
}
}
pub fn eval(&mut self, source: Source) -> Result<fix_common::Value> {
pub fn eval(&mut self, source: Source) -> Result<fix_lang::Value> {
self.do_eval(source, None, ForceMode::AsIs)
}
pub fn eval_shallow(&mut self, source: Source) -> Result<fix_common::Value> {
pub fn eval_shallow(&mut self, source: Source) -> Result<fix_lang::Value> {
self.do_eval(source, None, ForceMode::Shallow)
}
pub fn eval_deep(&mut self, source: Source) -> Result<fix_common::Value> {
pub fn eval_deep(&mut self, source: Source) -> Result<fix_lang::Value> {
self.do_eval(source, None, ForceMode::Deep)
}
@@ -90,7 +62,7 @@ impl Evaluator {
&mut self,
source: Source,
scope: &HashSet<StringId>,
) -> Result<fix_common::Value> {
) -> Result<fix_lang::Value> {
self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow)
}
@@ -99,14 +71,10 @@ impl Evaluator {
source: Source,
extra_scope: Option<ExtraScope<'ctx>>,
force_mode: ForceMode,
) -> Result<fix_common::Value> {
let ip = {
let mut compiler = CompilerCtx {
code: &mut self.code,
runtime: &mut self.runtime,
};
compiler.compile_bytecode(source, extra_scope)?
};
) -> Result<fix_lang::Value> {
let ip = self
.code
.compile_bytecode(source, extra_scope, &mut self.runtime)?;
Vm::run(self, ip, force_mode)
}
@@ -115,16 +83,12 @@ impl Evaluator {
_ident: &str,
_expr: &str,
_scope: &mut HashSet<StringId>,
) -> Result<fix_common::Value> {
) -> Result<fix_lang::Value> {
todo!("add_binding")
}
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
let mut compiler = CompilerCtx {
code: &mut self.code,
runtime: &mut self.runtime,
};
compiler.compile_bytecode(source, None)
self.code.compile_bytecode(source, None, &mut self.runtime)
}
pub fn disassemble_colored(&self, ip: InstructionPtr) -> String {
@@ -149,151 +113,12 @@ impl VmRuntimeCtx for RuntimeState {
}
}
impl VmCode for CodeState {
fn bytecode(&self) -> &[u8] {
&self.bytecode
}
fn compile_with_scope(
&mut self,
source: Source,
extra_scope: Option<fix_abstract_vm::ExtraScope>,
runtime: &mut impl VmRuntimeCtx,
) -> Result<InstructionPtr> {
let mut compiler = CompilerCtx {
code: self,
runtime,
};
let extra = extra_scope.map(|s| match s {
fix_abstract_vm::ExtraScope::ScopedImport { keys, slot_id } => {
ExtraScope::ScopedImport { keys, slot_id }
}
});
compiler.compile_bytecode(source, extra)
}
}
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<ExtraScope>,
) -> Result<InstructionPtr> {
let prev_scope_slot = self.code.current_scope_slot;
self.code.current_scope_slot = match &extra_scope {
Some(ExtraScope::ScopedImport { slot_id, .. }) => Some(*slot_id),
_ => None,
};
let result = (|| -> Result<InstructionPtr> {
let root = self.downgrade(source, extra_scope)?;
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
Ok(ip)
})();
self.code.current_scope_slot = prev_scope_slot;
result
}
fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
tracing::debug!("Parsing Nix expression");
self.code.sources.push(source.clone());
let root = rnix::Root::parse(&source.src);
handle_parse_error(root.errors(), source.clone()).map_or(Ok(()), Err)?;
tracing::debug!("Downgrading Nix expression");
let expr = root
.tree()
.expr()
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
let bump = Bump::new();
GhostToken::new(|token| {
let downgrade_ctx = DowngradeCtx::new(
&bump,
token,
self.runtime,
&self.code.global_env,
extra_scope.map(Into::into),
&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<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
fn intern_string(&mut self, s: &str) -> StringId {
self.runtime.intern_string(s)
}
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),
Path(_) => todo!("path value type"),
PrimOp {
id,
arity,
dispatch_ip,
} => StaticValue::new_primop(id, arity, dispatch_ip),
Null => StaticValue::default(),
};
self.runtime.add_const(val)
}
fn current_source_dir(&mut self) -> StringId {
let dir = self
.code
.sources
.last()
.expect("current_source not set")
.get_dir()
.to_string_lossy()
.into_owned();
self.runtime.intern_string(dir)
}
fn current_scope_slot(&self) -> Option<u32> {
self.code.current_scope_slot
}
}
#[derive(Default)]
pub struct Constants {
data: Vec<StaticValue>,
@@ -315,402 +140,6 @@ impl Constants {
}
}
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
use rnix::ParseError::*;
match error {
Unexpected(range)
| UnexpectedExtra(range)
| UnexpectedWanted(_, range, _)
| UnexpectedDoubleBind(range)
| DuplicatedArgs(range, _) => Some(*range),
_ => None,
}
}
fn handle_parse_error<'a>(
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
source: Source,
) -> Option<Box<Error>> {
for err in errors {
if let Some(span) = parse_error_span(err) {
return Some(
Error::parse_error(err.to_string())
.with_source(source)
.with_span(span),
);
}
}
None
}
struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
bump: &'ir Bump,
token: GhostToken<'id>,
runtime: &'ctx mut R,
source: Source,
scopes: Vec<Scope<'ctx, 'id, 'ir>>,
with_stack: Vec<GhostRoMaybeThunkRef<'id, 'ir>>,
arg_count: u32,
thunk_count: &'ctx mut usize,
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
}
impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
fn new(
bump: &'ir Bump,
token: GhostToken<'id>,
runtime: &'ctx mut R,
global: &'ctx HashMap<StringId, MaybeThunk>,
extra_scope: Option<Scope<'ctx, 'id, 'ir>>,
thunk_count: &'ctx mut usize,
source: Source,
) -> Self {
Self {
bump,
token,
runtime,
source,
scopes: std::iter::once(Scope::Global(global))
.chain(extra_scope)
.collect(),
thunk_count,
arg_count: 0,
with_stack: Vec::new(),
thunk_scopes: vec![ThunkScope::new_in(bump)],
}
}
}
impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
for DowngradeCtx<'ctx, 'id, 'ir, R>
{
fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir> {
self.bump.alloc(GhostCell::new(expr).into())
}
fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir> {
use MaybeThunk::*;
let expr = (|| {
let expr = match *ir.borrow(&self.token) {
Ir::Builtin(x) => Builtin(x),
Ir::Int(x) => Int(x),
Ir::Float(x) => Float(x),
Ir::Bool(x) => Bool(x),
Ir::Str(x) => Str(x),
Ir::Arg { layer } => Arg { layer },
Ir::Builtins => Builtins,
Ir::Null => Null,
Ir::MaybeThunk(thunk) => return Some(thunk),
_ => return None,
};
Some(self.bump.alloc(GhostCell::new(expr).into()))
})();
if let Some(thunk) = expr {
return thunk;
}
let id = ThunkId(*self.thunk_count);
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
self.thunk_scopes
.last_mut()
.expect("no active cache scope")
.add_binding(id, ir);
self.bump.alloc(GhostCell::new(Thunk(id)).into())
}
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
self.runtime.intern_string(sym)
}
fn resolve_sym(&self, id: StringId) -> Symbol<'_> {
self.runtime.resolve_string(id).into()
}
fn lookup(
&mut self,
sym: StringId,
span: rnix::TextRange,
) -> Result<GhostRoMaybeThunkRef<'id, 'ir>> {
for scope in self.scopes.iter().rev() {
match scope {
&Scope::Global(global_scope) => {
if let Some(expr) = global_scope.get(&sym) {
return Ok(expr.into());
}
}
&Scope::Repl(repl_bindings) => {
if repl_bindings.contains(&sym) {
return Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into()));
}
}
Scope::ScopedImport { keys, .. } => {
if keys.contains(&sym) {
return Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)).into()));
}
}
Scope::Let(let_scope) => {
if let Some(&expr) = let_scope.get(&sym) {
return Ok(expr.into());
}
}
&Scope::Param {
sym: param_sym,
abs_layer,
} => {
if param_sym == sym {
let layers: u8 =
self.thunk_scopes.len().try_into().expect("scope too deep!");
let layer = layers - abs_layer;
return Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::Arg { layer }).into()));
}
}
}
}
if !self.with_stack.is_empty() {
let id = ThunkId(*self.thunk_count);
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
let mut namespaces =
bumpalo::collections::Vec::with_capacity_in(self.with_stack.len(), self.bump);
namespaces.extend(self.with_stack.iter().rev().copied());
let body = self
.bump
.alloc(GhostCell::new(Ir::WithLookup { sym, namespaces }).into());
self.thunk_scopes
.last_mut()
.expect("no active thunk scope")
.add_binding(id, body);
Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::Thunk(id)).into()))
} else {
Err(Error::downgrade_error(
format!("'{}' not found", self.resolve_sym(sym)),
self.get_current_source(),
span,
))
}
}
fn get_current_source(&self) -> Source {
self.source.clone()
}
fn with_let_scope<F, Ret>(&mut self, keys: &[StringId], f: F) -> Result<Ret>
where
F: FnOnce(
&mut Self,
) -> Result<(
bumpalo::collections::Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>,
Ret,
)>,
{
let base = *self.thunk_count;
*self.thunk_count = self
.thunk_count
.checked_add(keys.len())
.expect("thunk id overflow");
let handles = (base..base + keys.len())
.map(|id| {
&*self
.bump
.alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(id))))
})
.collect::<Vec<_>>();
let scope = keys.iter().copied().zip(handles.iter().copied()).collect();
self.scopes.push(Scope::Let(scope));
let (vals, ret) = { f(self)? };
self.scopes.pop();
assert_eq!(keys.len(), vals.len());
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
for (i, (val, handle)) in vals.into_iter().zip(handles).enumerate() {
let thunk = *val.borrow(&self.token);
*handle.borrow_mut(&mut self.token) = thunk;
let id = ThunkId(base + i);
let ir_ref = self
.bump
.alloc(GhostCell::new(Ir::MaybeThunk(handle.into())).into());
scope.add_binding(id, ir_ref);
}
Ok(ret)
}
fn with_param_scope<F, Ret>(&mut self, sym: StringId, f: F) -> Ret
where
F: FnOnce(&mut Self) -> Ret,
{
self.scopes.push(Scope::Param {
sym,
abs_layer: self.thunk_scopes.len().try_into().expect("scope too deep!"),
});
let mut guard = ScopeGuard { ctx: self };
f(guard.as_ctx())
}
fn with_with_scope<F, Ret>(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> Ret
where
F: FnOnce(&mut Self) -> Ret,
{
self.with_stack.push(namespace);
let ret = f(self);
self.with_stack.pop();
ret
}
fn with_thunk_scope<F, Ret>(
&mut self,
f: F,
) -> (
Ret,
bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
)
where
F: FnOnce(&mut Self) -> Ret,
{
if self.thunk_scopes.len() == u8::MAX as usize {
panic!("scope too deep!");
}
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
let ret = f(self);
(
ret,
self.thunk_scopes
.pop()
.expect("no thunk scope left???")
.bindings,
)
}
fn bump(&self) -> &'ir bumpalo::Bump {
self.bump
}
}
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
.thunk_scopes
.pop()
.expect("no thunk scope left???")
.bindings;
Ok(Ir::freeze(
self.new_expr(Ir::TopLevel { body, thunks }),
self.token,
))
}
}
struct ThunkScope<'id, 'ir> {
bindings: bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
}
impl<'id, 'ir> ThunkScope<'id, 'ir> {
fn new_in(bump: &'ir Bump) -> Self {
Self {
bindings: bumpalo::collections::Vec::new_in(bump),
}
}
fn add_binding(&mut self, id: ThunkId, ir: GhostRoIrRef<'id, 'ir>) {
self.bindings.push((id, ir));
}
fn extend_bindings(
&mut self,
iter: impl IntoIterator<Item = (ThunkId, GhostRoIrRef<'id, 'ir>)>,
) {
self.bindings.extend(iter);
}
}
enum Scope<'ctx, 'id, 'ir> {
Global(&'ctx HashMap<StringId, MaybeThunk>),
Repl(&'ctx HashSet<StringId>),
ScopedImport {
keys: HashSet<StringId>,
slot_id: u32,
},
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
Param {
sym: StringId,
abs_layer: u8,
},
}
pub enum ExtraScope<'ctx> {
Repl(&'ctx HashSet<StringId>),
ScopedImport {
keys: HashSet<StringId>,
slot_id: u32,
},
}
impl<'ctx> From<ExtraScope<'ctx>> for Scope<'ctx, '_, '_> {
fn from(value: ExtraScope<'ctx>) -> Self {
use ExtraScope::*;
match value {
ScopedImport { keys, slot_id } => Scope::ScopedImport { keys, slot_id },
Repl(scope) => Scope::Repl(scope),
}
}
}
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>,
}
impl<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> Drop for ScopeGuard<'a, 'ctx, 'id, 'ir, R> {
fn drop(&mut self) {
self.ctx.scopes.pop();
}
}
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
}
}
struct OwnedIr {
_bump: Bump,
ir: RawIrRef<'static>,
}
impl OwnedIr {
/// # Safety
/// `ir` must be an allocation backed by `bump`. The reference's
/// lifetime is extended to `'static` as a placeholder; the stored IR
/// must only be re-borrowed via [`OwnedIr::as_ref`], which narrows
/// the lifetime back to that of the `&self` borrow. Moving `bump`
/// into the struct keeps the underlying allocation live for the
/// lifetime of the `OwnedIr`.
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
Self {
_bump: bump,
// SAFETY: see function docs - caller guarantees `ir` is in `bump`,
// and the `'static` lifetime is a placeholder narrowed by `as_ref`.
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
}
}
fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
// SAFETY: narrows the placeholder `'static` lifetime stored in
// `self.ir` down to `'ir = &'ir self`. Lifetime shortening is
// logically sound for covariant positions; the transmute is only
// needed because `RawRef<'ir>` carries `'ir` through a GAT
// (`Ref::Ref<T>`), which prevents the compiler from inferring
// covariance automatically. The bump arena that backs the IR is
// owned by `self._bump`, so the data is live for at least `'ir`.
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
}
}
impl DisassemblerContext for Evaluator {
fn get_code(&self) -> &[u8] {
&self.code.bytecode
+1 -1
View File
@@ -1,4 +1,4 @@
use fix_common::Value;
use fix_lang::Value;
use crate::utils::{eval_deep, eval_deep_result};
+1 -1
View File
@@ -1,6 +1,6 @@
use fix::Evaluator;
use fix_common::Value;
use fix_error::Source;
use fix_lang::Value;
use crate::utils::{eval, eval_result};
+1 -1
View File
@@ -3,8 +3,8 @@
use std::path::PathBuf;
use fix::Evaluator;
use fix_common::Value;
use fix_error::{Source, SourceType};
use fix_lang::Value;
fn get_lang_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
+1 -1
View File
@@ -1,5 +1,5 @@
use fix::Evaluator;
use fix_common::Value;
use fix_lang::Value;
use crate::utils::eval_result;
+1 -1
View File
@@ -1,8 +1,8 @@
#![allow(dead_code)]
use fix::Evaluator;
use fix_common::Value;
use fix_error::{Result, Source};
use fix_lang::Value;
pub fn eval(expr: &str) -> Value {
Evaluator::new()
Generated
+17 -18
View File
@@ -45,16 +45,15 @@
]
},
"locked": {
"lastModified": 1778445566,
"lastModified": 1778446047,
"narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=",
"owner": "nix-community",
"repo": "bun2nix",
"rev": "2499dedd70744dba1815875b854818a3019e9e4c",
"rev": "f2bc12af1a6369648aac41041ceeaa0b866599c6",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "staging-2.1.0",
"repo": "bun2nix",
"type": "github"
}
@@ -67,11 +66,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1778919578,
"narHash": "sha256-+z+jgTly48gsAiX8rOe/vs8C/2G4vdCpcEtqMJUpFqw=",
"lastModified": 1781343250,
"narHash": "sha256-KBJktAwDG9+10j2wMfvOVkBEhZr3yS769xoqqdFI62s=",
"owner": "nix-community",
"repo": "fenix",
"rev": "ecd6d4ff22cfdb1339b2915455a2ff4dc85bf52e",
"rev": "aad7d8bb6936d473c2b9d1a5846a1fe1bc92767a",
"type": "github"
},
"original": {
@@ -127,11 +126,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1778929997,
"narHash": "sha256-iAfbBUHBbR0N4DFqFWr4Jtmpc1YcOK7kpVM4f0MK1V8=",
"lastModified": 1781330261,
"narHash": "sha256-2fFAGel2VVXr5mwrTXldqXva2ng3T3HHxyuBKRIxauI=",
"owner": "numtide",
"repo": "llm-agents.nix",
"rev": "0da8f0313c9f68c155e0932f880fc1913e7be846",
"rev": "24ec6b7b1ddf8896ac8df3b65dc564575e0a1928",
"type": "github"
},
"original": {
@@ -142,11 +141,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1778443072,
"narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=",
"lastModified": 1781074563,
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32",
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
"type": "github"
},
"original": {
@@ -167,11 +166,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1778854817,
"narHash": "sha256-iG+VuMy8W585geVVCUd7pR025WsY3ZkgSv5Yt5bxDmQ=",
"lastModified": 1781294997,
"narHash": "sha256-XjCyIvJw4JtcwItTKRdQz5h1pLF9hr8ZSYeMP+/1d3A=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "1a68212c5683555ad80f0eab71db9715c6d52145",
"rev": "3f92cd1612268995d5667bd04fa03ba2916413d9",
"type": "github"
},
"original": {
@@ -204,11 +203,11 @@
]
},
"locked": {
"lastModified": 1775636079,
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
"lastModified": 1780220602,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
"type": "github"
},
"original": {
+3 -2
View File
@@ -47,11 +47,12 @@
just
samply
tokei
tombi
llm-agents.codex
# llm-agents.codex
llm-agents.claude-code
llm-agents.opencode
llm-agents.forge
# llm-agents.forge
];
};
}
+2 -2
View File
@@ -1,7 +1,7 @@
[files]
extend-exclude = [
"nix-js/tests/tests/regex.rs",
"nix-js/tests/tests/lang",
"nix-js/tests/tests/regex.rs",
"nix-js/tests/tests/lang",
]
[default.extend-words]