Compare commits

...
9 Commits
65 changed files with 3583 additions and 3643 deletions
Generated
+77 -88
View File
@@ -445,22 +445,18 @@ name = "fix"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bumpalo",
"clap", "clap",
"criterion", "criterion",
"ere", "ere",
"fix-abstract-vm", "fix-bytecode",
"fix-builtins", "fix-compiler",
"fix-codegen",
"fix-common",
"fix-error", "fix-error",
"fix-ir", "fix-lang",
"fix-runtime",
"fix-vm", "fix-vm",
"ghost-cell",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"miette", "miette",
"mimalloc", "mimalloc",
"rnix",
"rustyline", "rustyline",
"string-interner", "string-interner",
"tempfile", "tempfile",
@@ -471,51 +467,31 @@ dependencies = [
] ]
[[package]] [[package]]
name = "fix-abstract-vm" name = "fix-bytecode"
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"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"colored", "colored",
"fix-builtins", "fix-lang",
"fix-common",
"fix-ir",
"hashbrown 0.16.1",
"num_enum", "num_enum",
"rnix",
"string-interner", "string-interner",
] ]
[[package]] [[package]]
name = "fix-common" name = "fix-compiler"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ere", "bumpalo",
"gc-arena", "colored",
"fix-bytecode",
"fix-error",
"fix-lang",
"fix-runtime",
"ghost-cell",
"hashbrown 0.16.1",
"rnix",
"rowan",
"string-interner", "string-interner",
"tracing",
] ]
[[package]] [[package]]
@@ -528,34 +504,37 @@ dependencies = [
] ]
[[package]] [[package]]
name = "fix-ir" name = "fix-lang"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "ere",
"fix-builtins", "gc-arena",
"fix-common",
"fix-error",
"ghost-cell",
"hashbrown 0.16.1",
"num_enum", "num_enum",
"rnix",
"rowan",
"string-interner", "string-interner",
] ]
[[package]] [[package]]
name = "fix-primops" name = "fix-macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"fix-abstract-vm", "manyhow",
"fix-builtins", "proc-macro2",
"fix-codegen", "quote",
"fix-common", "syn",
]
[[package]]
name = "fix-runtime"
version = "0.1.0"
dependencies = [
"fix-bytecode",
"fix-error", "fix-error",
"fix-lang",
"fix-macros",
"gc-arena", "gc-arena",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"num_enum",
"smallvec", "smallvec",
"sptr",
"string-interner", "string-interner",
] ]
@@ -563,19 +542,13 @@ dependencies = [
name = "fix-vm" name = "fix-vm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"fix-abstract-vm", "fix-bytecode",
"fix-builtins",
"fix-codegen",
"fix-common",
"fix-error", "fix-error",
"fix-primops", "fix-lang",
"fix-runtime",
"gc-arena", "gc-arena",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"likely_stable",
"num_enum",
"smallvec", "smallvec",
"sptr",
"string-interner",
"sysinfo", "sysinfo",
] ]
@@ -773,15 +746,6 @@ dependencies = [
"libc", "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]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.12.1" version = "0.12.1"
@@ -794,6 +758,29 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 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]] [[package]]
name = "matchers" name = "matchers"
version = "0.2.0" version = "0.2.0"
@@ -1042,6 +1029,17 @@ dependencies = [
"toml_edit", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@@ -1053,9 +1051,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.45" version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -1158,15 +1156,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 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]] [[package]]
name = "rustix" name = "rustix"
version = "1.1.4" version = "1.1.4"
@@ -1331,9 +1320,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.117" version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
+21 -20
View File
@@ -2,41 +2,42 @@
resolver = "3" resolver = "3"
members = [ members = [
"fix", "fix",
"fix-abstract-vm", "fix-bytecode",
"fix-builtins", "fix-compiler",
"fix-codegen",
"fix-common",
"fix-error", "fix-error",
"fix-ir", "fix-lang",
"fix-primops", "fix-macros",
"fix-runtime",
"fix-vm", "fix-vm",
] ]
[profile.profiling]
inherits = "release"
debug = true
[profile.lto]
inherits = "release"
lto = true
[workspace.dependencies] [workspace.dependencies]
bumpalo = { version = "3.20", features = [ bumpalo = {
version = "3.20",
features = [
"allocator-api2", "allocator-api2",
"boxed", "boxed",
"collections", "collections",
] } ]
}
ere = "0.2"
ghost-cell = "0.2" ghost-cell = "0.2"
hashbrown = "0.16" hashbrown = "0.16"
num_enum = "0.7.5" num_enum = "0.7.5"
smallvec = { version = "1.15", features = ["const_new", "const_generics"] }
ere = "0.2"
string-interner = "0.19"
rnix = "0.14" rnix = "0.14"
rowan = "0.16" rowan = "0.16"
likely_stable = "0.1" smallvec = { version = "1.15", features = ["const_generics", "const_new"] }
string-interner = "0.19"
[workspace.dependencies.gc-arena] [workspace.dependencies.gc-arena]
git = "https://github.com/kyren/gc-arena" git = "https://github.com/kyren/gc-arena"
rev = "75671ae03f53718357b741ed4027560f14e90836" rev = "75671ae03f53718357b741ed4027560f14e90836"
features = ["allocator-api2", "hashbrown", "smallvec"] 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 std::fmt::Write;
use colored::Colorize; use colored::Colorize as _;
use fix_builtins::BuiltinId;
use num_enum::TryFromPrimitive;
use crate::{InstructionPtr, Op, OperandType}; use crate::{Continuation, InstructionPtr, Op, OperandType};
pub trait DisassemblerContext { pub trait DisassemblerContext {
fn resolve_string(&self, id: u32) -> &str; fn resolve_string(&self, id: u32) -> &str;
@@ -82,7 +80,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
fn read_operand_data(&mut self) { fn read_operand_data(&mut self) {
use OperandType::*; use OperandType::*;
let tag = self.read_u8(); 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 { match ty {
Const => { Const => {
self.read_u32(); 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) { 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 { match op {
Op::PushSmi => { Op::PushSmi => {
@@ -296,9 +294,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
self.read_operand_data(); self.read_operand_data();
("Call", "arg=?".into()) ("Call", "arg=?".into())
} }
Op::DispatchPrimOp => { Op::DispatchCont => {
let id = BuiltinId::try_from_primitive(self.read_u8()).expect("invalid builtin id"); let phase = Continuation::try_from(self.read_u8()).expect("invalid primop phase");
("DispatchPrimOp", format!("id={id:?}")) ("DispatchPrimOp", format!("phase={phase:?}"))
} }
Op::MakeAttrs => { 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] [package]
name = "fix-ir" name = "fix-compiler"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
bumpalo = { workspace = true } bumpalo = { workspace = true }
colored = "3.1.1"
ghost-cell = { workspace = true } ghost-cell = { workspace = true }
hashbrown = { workspace = true }
rnix = { workspace = true } rnix = { workspace = true }
rowan = { workspace = true } rowan = { workspace = true }
string-interner = { workspace = true } string-interner = { workspace = true }
hashbrown = { workspace = true }
num_enum = { workspace = true }
fix-builtins = { path = "../fix-builtins" } fix-bytecode = { path = "../fix-bytecode" }
fix-common = { path = "../fix-common" }
fix-error = { path = "../fix-error" } fix-error = { path = "../fix-error" }
fix-lang = { path = "../fix-lang" }
fix-runtime = { path = "../fix-runtime" }
tracing = "0.1"
+546
View File
@@ -0,0 +1,546 @@
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(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(fix_runtime::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::Bump;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use fix_builtins::{BUILTINS, BuiltinId}; use fix_lang::{BUILTINS, BuiltinId, StringId};
use fix_common::StringId;
use ghost_cell::{GhostCell, GhostToken}; use ghost_cell::{GhostCell, GhostToken};
use num_enum::TryFromPrimitive as _;
use rnix::{TextRange, ast}; use rnix::{TextRange, ast};
use string_interner::DefaultStringInterner; use string_interner::DefaultStringInterner;
@@ -92,7 +90,7 @@ pub enum MaybeThunk {
BuiltinConst(StringId), BuiltinConst(StringId),
Builtins, Builtins,
ReplBinding(StringId), ReplBinding(StringId),
ScopedImportBinding(StringId), ScopedImportBinding { slot_id: u32, sym: StringId },
} }
pub trait Ref<'ir> { pub trait Ref<'ir> {
@@ -213,17 +211,16 @@ pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
}, },
MaybeThunk(R::MaybeThunkRef), MaybeThunk(R::MaybeThunkRef),
ReplBinding(StringId), ReplBinding(StringId),
ScopedImportBinding(StringId), ScopedImportBinding {
sym: StringId,
slot_id: u32,
},
} }
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ThunkId(pub usize); 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. /// Represents a key in an attribute path.
#[allow(unused)] #[allow(unused)]
#[derive(Debug)] #[derive(Debug)]
@@ -295,7 +292,7 @@ pub fn new_global_env(
global_env.insert(builtins_sym, MaybeThunk::Builtins); global_env.insert(builtins_sym, MaybeThunk::Builtins);
for (idx, &(name, _)) in BUILTINS.iter().enumerate() { 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)); let name = StringId(strings.get_or_intern(name));
global_env.insert(name, MaybeThunk::Builtin(id)); global_env.insert(name, MaybeThunk::Builtin(id));
} }
@@ -1,7 +1,6 @@
use bumpalo::collections::{CollectIn, Vec}; use bumpalo::collections::{CollectIn, Vec};
use fix_builtins::BuiltinId;
use fix_common::Symbol;
use fix_error::{Error, Result, Source}; use fix_error::{Error, Result, Source};
use fix_lang::{BuiltinId, Symbol};
use hashbrown::HashSet; use hashbrown::HashSet;
use hashbrown::hash_map::Entry; use hashbrown::hash_map::Entry;
use rnix::TextRange; use rnix::TextRange;
@@ -1,14 +1,14 @@
use fix_builtins::{BUILTINS, BuiltinId}; use fix_bytecode::{Const, Continuation, InstructionPtr, Op, OperandType};
use fix_common::StringId; use fix_lang::{BUILTINS, StringId};
use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
use hashbrown::HashMap; use hashbrown::HashMap;
use num_enum::TryFromPrimitive;
use rnix::TextRange; use rnix::TextRange;
use string_interner::Symbol as _; use string_interner::Symbol as _;
pub mod disassembler; mod context;
pub mod ir;
pub struct InstructionPtr(pub usize); pub use context::{CodeState, ExtraScope};
pub use fix_bytecode::disassembler;
pub use ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
pub trait BytecodeContext { pub trait BytecodeContext {
fn intern_string(&mut self, s: &str) -> StringId; 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 get_code_mut(&mut self) -> &mut Vec<u8>;
fn add_constant(&mut self, val: Const) -> u32; fn add_constant(&mut self, val: Const) -> u32;
fn current_source_dir(&mut self) -> StringId; 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 { struct ScopeInfo {
@@ -109,39 +29,6 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
scope_stack: Vec<ScopeInfo>, 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 { pub enum InlineOperand {
Const(Const), Const(Const),
BigInt(i64), BigInt(i64),
@@ -149,7 +36,7 @@ pub enum InlineOperand {
BuiltinConst(StringId), BuiltinConst(StringId),
Builtins, Builtins,
ReplBinding(StringId), ReplBinding(StringId),
ScopedImportBinding(StringId), ScopedImportBinding { id: StringId, slot_id: u32 },
} }
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr { 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 { InlineOperand::Const(Const::PrimOp {
id, id,
arity, arity,
dispatch_ip: id.entry_phase().ip(), dispatch_ip: Continuation::entry_for_builtin(id).ip(),
}) })
} }
BuiltinConst(id) => InlineOperand::BuiltinConst(id), BuiltinConst(id) => InlineOperand::BuiltinConst(id),
Builtins => InlineOperand::Builtins, Builtins => InlineOperand::Builtins,
ReplBinding(id) => InlineOperand::ReplBinding(id), 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_u8(OperandType::ReplBinding as u8);
self.emit_str_id(id); self.emit_str_id(id);
} }
ScopedImportBinding(id) => { ScopedImportBinding { id, slot_id } => {
self.emit_u8(OperandType::ScopedImportBinding as u8); self.emit_u8(OperandType::ScopedImportBinding as u8);
let slot = self self.emit_u32(slot_id);
.ctx
.current_scope_slot()
.expect("ScopedImportBinding outside scoped compilation");
self.emit_u32(slot);
self.emit_str_id(id); self.emit_str_id(id);
} }
} }
@@ -555,14 +440,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::LoadReplBinding); self.emit_op(Op::LoadReplBinding);
self.emit_str_id(name); self.emit_str_id(name);
} }
&Ir::ScopedImportBinding(name) => { &Ir::ScopedImportBinding { sym, slot_id } => {
self.emit_op(Op::LoadScopedBinding); self.emit_op(Op::LoadScopedBinding);
let slot = self self.emit_u32(slot_id);
.ctx self.emit_str_id(sym);
.current_scope_slot()
.expect("ScopedImportBinding outside scoped compilation");
self.emit_u32(slot);
self.emit_str_id(name);
} }
Ir::WithLookup { sym, namespaces } => { Ir::WithLookup { sym, namespaces } => {
// counter // counter
@@ -629,14 +510,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::LoadReplBinding); self.emit_op(Op::LoadReplBinding);
self.emit_str_id(name); self.emit_str_id(name);
} }
ScopedImportBinding(name) => { ScopedImportBinding { slot_id, sym } => {
self.emit_op(Op::LoadScopedBinding); self.emit_op(Op::LoadScopedBinding);
let slot = self self.emit_u32(slot_id);
.ctx self.emit_str_id(sym);
.current_scope_slot()
.expect("ScopedImportBinding outside scoped compilation");
self.emit_u32(slot);
self.emit_str_id(name);
} }
} }
} }
@@ -775,7 +652,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_attrset( fn emit_attrset(
&mut self, &mut self,
stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>, stcs: &ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>,
dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)], dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)],
) { ) {
if stcs.is_empty() && dyns.is_empty() { if stcs.is_empty() && dyns.is_empty() {
+1 -1
View File
@@ -5,5 +5,5 @@ edition = "2024"
[dependencies] [dependencies]
miette = { version = "7.6", features = ["fancy"] } miette = { version = "7.6", features = ["fancy"] }
thiserror = "2.0"
rnix = { workspace = true } rnix = { workspace = true }
thiserror = "2.0"
@@ -1,9 +1,10 @@
[package] [package]
name = "fix-common" name = "fix-lang"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
gc-arena = { workspace = true }
string-interner = { workspace = true }
ere = { 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 std::ops::{Deref, DerefMut};
use gc_arena::Collect; 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)] #[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)] #[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"] }
+39
View File
@@ -0,0 +1,39 @@
extern crate proc_macro;
// Adapted from `__unelide_lifetimes` in `gc-arena-derive`.
// Licensed under the MIT license.
// See: https://github.com/kyren/gc-arena
#[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] [package]
name = "fix-abstract-vm" name = "fix-runtime"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
gc-arena = { workspace = true } gc-arena = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
num_enum = { workspace = true }
smallvec = { workspace = true } smallvec = { workspace = true }
string-interner = { workspace = true }
likely_stable = { workspace = true }
sptr = "0.3" sptr = "0.3"
string-interner = { workspace = true }
fix-builtins = { path = "../fix-builtins" } fix-bytecode = { path = "../fix-bytecode" }
fix-codegen = { path = "../fix-codegen" }
fix-common = { path = "../fix-common" }
fix-error = { path = "../fix-error" } fix-error = { path = "../fix-error" }
fix-lang = { path = "../fix-lang" }
fix-macros = { path = "../fix-macros" }
@@ -1,9 +1,9 @@
use fix_common::StringId; use fix_lang::StringId;
use gc_arena::{Gc, Mutation}; use gc_arena::Mutation;
use crate::{ use crate::{
AttrSet, Break, BytecodeReader, Closure, List, Machine, NixNum, NixString, NixType, Null, AttrSet, Break, BytecodeReader, Closure, List, Machine, NixNum, NixString, NixType, Null,
PrimOp, PrimOpApp, Step, StrictValue, PrimOp, PrimOpApp, Step, StrictValue, ValueVariant,
}; };
pub trait Forced<'gc>: Sized { pub trait Forced<'gc>: Sized {
@@ -47,10 +47,10 @@ impl<'gc> Forced<'gc> for StrictValue<'gc> {
} }
} }
macro_rules! impl_forced_inline { macro_rules! impl_forced {
($($ty:ty => $nix_ty:expr),* $(,)?) => { ($($ty:ty),* $(,)?) => {
$( $(
impl<'gc> Forced<'gc> for $ty { impl<'gc> Forced<'gc> for <$ty as ValueVariant>::Ty<'gc> {
const WIDTH: usize = 1; const WIDTH: usize = 1;
#[inline(always)] #[inline(always)]
@@ -63,8 +63,8 @@ macro_rules! impl_forced_inline {
) -> Step { ) -> Step {
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
let v = m.peek_forced(base_depth); 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()); let _: Step = m.finish_type_err(<$ty as ValueVariant>::TYPE, v.ty());
return Step::Break(Break::Done); return Step::Break(Break::Done);
} }
Step::Continue(()) Step::Continue(())
@@ -73,7 +73,7 @@ macro_rules! impl_forced_inline {
#[inline(always)] #[inline(always)]
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self { fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
m.pop_forced() m.pop_forced()
.as_inline::<$ty>() .downcast::<$ty>()
.expect("type checked in force_and_check") .expect("type checked in force_and_check")
} }
} }
@@ -81,55 +81,18 @@ macro_rules! impl_forced_inline {
}; };
} }
macro_rules! impl_forced_gc { impl_forced! {
($($ty:ty => $nix_ty:expr),* $(,)?) => { i32,
$( bool,
impl<'gc> Forced<'gc> for Gc<'gc, $ty> { Null,
const WIDTH: usize = 1; StringId,
PrimOp,
#[inline(always)] i64,
fn force_and_check<M: Machine<'gc>>( NixString,
m: &mut M, AttrSet<'gc>,
reader: &mut BytecodeReader<'_>, List<'gc>,
mc: &Mutation<'gc>, Closure<'gc>,
base_depth: usize, PrimOpApp<'gc>,
resume_pc: usize,
) -> 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() {
let _: Step = m.finish_type_err($nix_ty, v.ty());
return Step::Break(Break::Done);
}
Step::Continue(())
}
#[inline(always)]
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
m.pop_forced()
.as_gc::<$ty>()
.expect("type checked in force_and_check")
}
}
)*
};
}
impl_forced_inline! {
i32 => NixType::Int,
bool => NixType::Bool,
Null => NixType::Null,
StringId => NixType::String,
PrimOp => NixType::PrimOp,
}
impl_forced_gc! {
i64 => NixType::Int,
NixString => NixType::String,
AttrSet<'gc> => NixType::AttrSet,
List<'gc> => NixType::List,
Closure<'gc> => NixType::Closure,
PrimOpApp<'gc> => NixType::PrimOpApp,
} }
impl<'gc> Forced<'gc> for NixNum { impl<'gc> Forced<'gc> for NixNum {
@@ -145,7 +108,7 @@ impl<'gc> Forced<'gc> for NixNum {
) -> Step { ) -> Step {
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
let v = m.peek_forced(base_depth); let v = m.peek_forced(base_depth);
if v.as_num().is_none() { if v.downcast_num().is_none() {
let _: Step = m.finish_type_err(NixType::Int, v.ty()); let _: Step = m.finish_type_err(NixType::Int, v.ty());
return Step::Break(Break::Done); return Step::Break(Break::Done);
} }
@@ -155,7 +118,7 @@ impl<'gc> Forced<'gc> for NixNum {
#[inline(always)] #[inline(always)]
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self { fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
m.pop_forced() m.pop_forced()
.as_num() .downcast_num()
.expect("type checked in force_and_check") .expect("type checked in force_and_check")
} }
} }
@@ -173,7 +136,7 @@ impl<'gc> Forced<'gc> for f64 {
) -> Step { ) -> Step {
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?; m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
let v = m.peek_forced(base_depth); let v = m.peek_forced(base_depth);
if v.as_float().is_none() { if v.downcast::<f64>().is_none() {
let _: Step = m.finish_type_err(NixType::Float, v.ty()); let _: Step = m.finish_type_err(NixType::Float, v.ty());
return Step::Break(Break::Done); return Step::Break(Break::Done);
} }
@@ -183,7 +146,7 @@ impl<'gc> Forced<'gc> for f64 {
#[inline(always)] #[inline(always)]
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self { fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
m.pop_forced() m.pop_forced()
.as_float() .downcast::<f64>()
.expect("type checked in force_and_check") .expect("type checked in force_and_check")
} }
} }
@@ -1,10 +1,11 @@
use fix_codegen::InstructionPtr; use fix_bytecode::InstructionPtr;
use fix_common::StringId;
use fix_error::Source; use fix_error::Source;
use fix_lang::{self, BUILTINS, StringId};
use hashbrown::HashSet; use hashbrown::HashSet;
use crate::{ 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 { pub trait VmContext {
@@ -38,15 +39,15 @@ pub trait VmRuntimeCtxExt: VmRuntimeCtx {
/// Returns the string context attached to `val`, or `&[]` if `val` is /// Returns the string context attached to `val`, or `&[]` if `val` is
/// either a non-string or a string without context. /// either a non-string or a string without context.
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext; 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 { impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> { 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)) Some(self.resolve_string(sid))
} else { } 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 /// would coerce a path to a string (string interpolation, file IO
/// builtins, etc.). /// builtins, etc.).
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> { 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)) Some(self.resolve_string(p.0))
} else { } else {
self.get_string(val) self.get_string(val)
@@ -66,9 +67,9 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
&'a mut self, &'a mut self,
val: StrictValue<'gc>, val: StrictValue<'gc>,
) -> std::result::Result<StringId, NixType> { ) -> std::result::Result<StringId, NixType> {
if let Some(sid) = val.as_inline::<StringId>() { if let Some(sid) = val.downcast::<StringId>() {
Ok(sid) 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)) Ok(self.intern_string(s))
} else { } else {
Err(val.ty()) Err(val.ty())
@@ -76,43 +77,43 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
} }
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext { 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() ns.as_ref().context()
} else { } else {
StringContext::empty() 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()) self.convert_value_with_seen(val, &mut HashSet::new())
} }
} }
pub(crate) trait ConvertValueWithSeen: VmRuntimeCtx { 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 { impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
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 {
use fix_common::Value; use fix_lang::Value;
if let Some(i) = val.as_inline::<i32>() { if let Some(i) = val.downcast::<i32>() {
Value::Int(i as i64) 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) Value::Int(*gc_i)
} else if let Some(f) = val.as_float() { } else if let Some(f) = val.downcast::<f64>() {
Value::Float(f) Value::Float(f)
} else if let Some(b) = val.as_inline::<bool>() { } else if let Some(b) = val.downcast::<bool>() {
Value::Bool(b) Value::Bool(b)
} else if val.is::<Null>() { } else if val.is::<Null>() {
Value::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(); let s = self.resolve_string(sid).to_owned();
Value::String(s) 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()) 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()) 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(); let bits = val.to_bits();
if attrs.entries.is_empty() { if attrs.entries.is_empty() {
return Value::AttrSet(Default::default()); return Value::AttrSet(Default::default());
@@ -124,10 +125,10 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
for &(key, val) in attrs.entries.iter() { for &(key, val) in attrs.entries.iter() {
let key = self.resolve_string(key).to_owned(); let key = self.resolve_string(key).to_owned();
let converted = self.convert_value_with_seen(val, seen); 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)) Value::AttrSet(fix_lang::AttrSet::new(map))
} else if let Some(list) = val.as_gc::<List>() { } else if let Some(list) = val.downcast::<List>() {
let bits = val.to_bits(); let bits = val.to_bits();
if list.inner.borrow().is_empty() { if list.inner.borrow().is_empty() {
return Value::List(Default::default()); return Value::List(Default::default());
@@ -142,20 +143,20 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
.copied() .copied()
.map(|v| self.convert_value_with_seen(v, seen)) .map(|v| self.convert_value_with_seen(v, seen))
.collect(); .collect();
Value::List(fix_common::List::new(items)) Value::List(fix_lang::List::new(items))
} else if val.is::<Closure>() { } else if val.is::<Closure>() {
Value::Func 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() { if let ThunkState::Evaluated(v) = *thunk.borrow() {
self.convert_value_with_seen(v.relax(), seen) self.convert_value_with_seen(v.relax(), seen)
} else { } else {
Value::Thunk Value::Thunk
} }
} else if let Some(primop) = val.as_inline::<PrimOp>() { } else if let Some(primop) = val.downcast::<PrimOp>() {
let name = fix_builtins::BUILTINS[primop.id as usize].0; let name = BUILTINS[primop.id as usize].0;
Value::PrimOp(name.strip_prefix("__").unwrap_or(name)) Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
} else if let Some(app) = val.as_gc::<PrimOpApp>() { } else if let Some(app) = val.downcast::<PrimOpApp>() {
let name = fix_builtins::BUILTINS[app.primop.id as usize].0; let name = BUILTINS[app.primop.id as usize].0;
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name)) Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
} else { } else {
Value::Null Value::Null
@@ -1,5 +1,4 @@
mod boxing; mod boxing;
mod bytecode_reader;
mod forced; mod forced;
mod host; mod host;
mod machine; mod machine;
@@ -9,7 +8,7 @@ mod state;
mod string_context; mod string_context;
mod value; mod value;
pub use bytecode_reader::*; pub use fix_bytecode::{BytecodeReader, OperandData};
pub use forced::*; pub use forced::*;
pub use host::*; pub use host::*;
pub use machine::*; pub use machine::*;
@@ -1,8 +1,8 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use fix_common::StringId;
use fix_error::Error; use fix_error::Error;
use fix_lang::{self, StringId};
use gc_arena::Mutation; use gc_arena::Mutation;
use crate::{ use crate::{
@@ -26,11 +26,15 @@ use crate::{
/// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`) /// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`)
pub trait Machine<'gc> { pub trait Machine<'gc> {
fn push(&mut self, val: Value<'gc>); fn push(&mut self, val: Value<'gc>);
#[must_use]
fn pop(&mut self) -> Value<'gc>; fn pop(&mut self) -> Value<'gc>;
#[must_use]
fn peek(&self, depth: usize) -> Value<'gc>; fn peek(&self, depth: usize) -> Value<'gc>;
#[must_use]
fn peek_forced(&self, depth: usize) -> StrictValue<'gc>; fn peek_forced(&self, depth: usize) -> StrictValue<'gc>;
fn pop_forced(&mut self) -> StrictValue<'gc>; fn pop_forced(&mut self) -> StrictValue<'gc>;
fn replace(&mut self, depth: usize, val: Value<'gc>); fn replace(&mut self, depth: usize, val: Value<'gc>);
fn drop_n(&mut self, depth: usize);
fn stack_len(&self) -> usize; fn stack_len(&self) -> usize;
fn force_slot_to_pc( fn force_slot_to_pc(
@@ -96,7 +100,7 @@ pub trait Machine<'gc> {
cur.borrow().locals[idx as usize] 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_err(&mut self, err: Box<Error>) -> Step;
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> 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 gc_arena::{Gc, Mutation};
use crate::{AttrSet, Machine, OperandData, Value}; use crate::{AttrSet, Machine, Value, VmRuntimeCtx};
/// Resolve a decoded operand into a runtime [`Value`]. /// 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>>( pub fn resolve_operand<'gc, M: Machine<'gc>>(
op: &OperandData, op: &OperandData,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
ctx: &impl VmRuntimeCtx,
m: &M, m: &M,
) -> Value<'gc> { ) -> Value<'gc> {
use OperandData::*; use OperandData::*;
match *op { match *op {
Const(sv) => sv.into(), Const(id) => ctx.get_const(id).into(),
BigInt(val) => Value::new_gc(Gc::new(mc, val)), BigInt(val) => Value::new(Gc::new(mc, val)),
Local { layer, idx } => m.local(layer, idx), Local { layer, idx } => m.local(layer, idx),
#[allow(clippy::unwrap_used)] #[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(), Builtins => m.builtins(),
ReplBinding(_id) => todo!(), ReplBinding(_id) => todo!(),
ScopedImportBinding { slot_id, name } => { ScopedImportBinding { slot_id, name } => {
let scope = m.scope_slot(slot_id); let scope = m.scope_slot(slot_id);
#[allow(clippy::unwrap_used)] #[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)] #[allow(clippy::unwrap_used)]
attrs.lookup(name).expect("scoped binding not found") attrs.lookup(name).expect("scoped binding not found")
} }
@@ -1,12 +1,12 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use std::path::PathBuf; use std::path::PathBuf;
use fix_common::StringId;
use fix_error::Error; use fix_error::Error;
use fix_lang::StringId;
use gc_arena::{Collect, Gc}; use gc_arena::{Collect, Gc};
use hashbrown::HashSet; use hashbrown::HashSet;
use crate::{GcEnv, StaticValue, Thunk}; use crate::{GcEnv, Thunk};
#[allow(dead_code)] #[allow(dead_code)]
pub enum VmError { pub enum VmError {
@@ -87,13 +87,3 @@ pub enum ExtraScope {
slot_id: u32, 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 { impl FromIterator<StringContextElem> for StringContext {
fn from_iter<T: IntoIterator<Item = StringContextElem>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = StringContextElem>>(iter: T) -> Self {
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::cell::RefCell;
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::size_of; use std::mem::size_of;
use std::ops::Deref; use std::ops::Deref;
use fix_builtins::BuiltinId; use fix_lang::*;
use fix_common::*;
use gc_arena::barrier::Unlock; use gc_arena::barrier::Unlock;
use gc_arena::collect::Trace; use gc_arena::collect::Trace;
use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock}; use gc_arena::{Collect, Gc, GcRefLock, Mutation, RefLock};
use num_enum::TryFromPrimitive;
use smallvec::SmallVec; use smallvec::SmallVec;
use string_interner::Symbol; use string_interner::Symbol;
use string_interner::symbol::SymbolU32; use string_interner::symbol::SymbolU32;
@@ -23,37 +19,84 @@ mod private {
pub trait Cealed {} pub trait Cealed {}
} }
pub trait ValueVariant: private::Cealed {
type Ty<'gc>: 'gc;
const TYPE: NixType;
fn is_value(value: &Value<'_>) -> bool;
/// # Safety /// # Safety
/// ///
/// TAG must be unique among all implementors. /// [`Self::is_value`] must hold for `value`.
#[allow(private_interfaces)] unsafe fn from_raw<'gc>(value: &Value<'gc>) -> Self::Ty<'gc>;
pub unsafe trait Storable: private::Cealed { }
const TAG: RawTag;
/// # Safety
///
/// 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 { macro_rules! define_value_types {
( (
inline { $($itype:ty => $itag:expr, $iname:literal;)* } inline { $($itype:ty => $itag:path, $ity:path, $iname:literal;)* }
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* } gc { $($gtype:ty => $gtag:path, $gty:path, $gname:literal;)* }
) => { ) => {
$( $(
#[allow(private_interfaces)]
unsafe impl Storable for $itype { 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 private::Cealed for $itype {}
impl ValueVariant for $itype {
type Ty<'gc> = $itype;
const TYPE: NixType = $ity;
#[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 Gc<'_, $gtype> {
unsafe impl Storable for $gtype { fn to_raw_box(self) -> RawBox {
const TAG: RawTag = $gtag; RawBox::from_value(RawValue::store($gtag, Gc::as_ptr(self)))
} }
impl GcStorable for $gtype {} unsafe fn from_raw_box(raw: &RawBox) -> Self {
unsafe { Gc::from_ptr(<*mut $gtype as RawStore>::from_val(raw.value().unwrap_unchecked())) }
}
}
impl private::Cealed for Gc<'_, $gtype> {}
impl private::Cealed for $gtype {} impl private::Cealed for $gtype {}
impl ValueVariant for $gtype {
type Ty<'gc> = Gc<'gc, fix_macros::unelide_lifetimes!('gc; $gtype)>;
const TYPE: NixType = $gty;
#[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); const _: () = assert!(size_of::<Value<'static>>() == 8);
@@ -83,10 +126,10 @@ macro_rules! define_value_types {
fn trace<T: Trace<'gc>>(&self, cc: &mut T) { fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
let Some(tag) = self.raw.tag() else { return }; let Some(tag) = self.raw.tag() else { return };
match tag { match tag {
$(<$gtype as Storable>::TAG => unsafe { $($gtag => unsafe {
self.load_gc::<$gtype>().trace(cc) self.downcast::<$gtype>().unwrap_unchecked().trace(cc)
},)* },)*
$(<$itype as Storable>::TAG => (),)* $($itag => (),)*
_ => unreachable!("invalid value tag"), _ => unreachable!("invalid value tag"),
} }
} }
@@ -98,10 +141,10 @@ macro_rules! define_value_types {
None => write!(f, "Float({:?})", unsafe { None => write!(f, "Float({:?})", unsafe {
self.raw.float().unwrap_unchecked() self.raw.float().unwrap_unchecked()
}), }),
$(Some(<$itype as Storable>::TAG) => write!(f, "{}({:?})", $iname, unsafe { $(Some($itag) => write!(f, "{}({:?})", $iname, unsafe {
self.as_inline::<$itype>().unwrap_unchecked() self.downcast::<$itype>().unwrap_unchecked()
}),)* }),)*
$(Some(<$gtype as Storable>::TAG) => $(Some($gtag) =>
write!(f, "{}(..)", $gname),)* write!(f, "{}(..)", $gname),)*
_ => unreachable!("invalid value tag"), _ => unreachable!("invalid value tag"),
} }
@@ -112,21 +155,45 @@ macro_rules! define_value_types {
define_value_types! { define_value_types! {
inline { inline {
i32 => RawTag::P1, "SmallInt"; i32 => RawTag::P1, NixType::Int, "SmallInt";
bool => RawTag::P2, "Bool"; bool => RawTag::P2, NixType::Bool, "Bool";
Null => RawTag::P3, "Null"; Null => RawTag::P3, NixType::Null, "Null";
StringId => RawTag::P4, "SmallString"; StringId => RawTag::P4, NixType::String, "SmallString";
PrimOp => RawTag::P5, "PrimOp"; PrimOp => RawTag::P5, NixType::PrimOp, "PrimOp";
Path => RawTag::N6, "Path"; Path => RawTag::P6, NixType::Path, "Path";
} }
gc { gc {
i64 => RawTag::P6, "BigInt"; i64 => RawTag::P7, NixType::Int, "BigInt";
NixString => RawTag::P7, "String"; NixString => RawTag::N1, NixType::String, "String";
AttrSet<'_> => RawTag::N1, "AttrSet"; AttrSet<'_> => RawTag::N2, NixType::AttrSet, "AttrSet";
List<'_> => RawTag::N2, "List"; List<'_> => RawTag::N3, NixType::List, "List";
Thunk<'_> => RawTag::N3, "Thunk"; Thunk<'_> => RawTag::N4, NixType::Thunk, "Thunk";
Closure<'_> => RawTag::N4, "Closure"; Closure<'_> => RawTag::N5, NixType::Closure, "Closure";
PrimOpApp<'_> => RawTag::N5, "PrimOpApp"; PrimOpApp<'_> => RawTag::N6, NixType::Closure, "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;
const TYPE: NixType = NixType::Float;
#[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 +210,11 @@ pub struct Value<'gc> {
impl Default for Value<'_> { impl Default for Value<'_> {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
Self::new_inline(Null) Self::new(Null)
} }
} }
impl<'gc> Value<'gc> { 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)] #[inline(always)]
const fn tag(self) -> Option<RawTag> { const fn tag(self) -> Option<RawTag> {
self.raw.tag() self.raw.tag()
@@ -178,75 +223,31 @@ impl<'gc> Value<'gc> {
impl<'gc> Value<'gc> { impl<'gc> Value<'gc> {
#[inline] #[inline]
pub fn new_float(val: f64) -> Self { #[allow(private_bounds)]
pub fn new<T: Storable>(val: T) -> Self {
Self { Self {
raw: RawBox::from_float(val), raw: val.to_raw_box(),
_marker: PhantomData, _marker: PhantomData,
} }
} }
#[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))
}
#[inline] #[inline]
pub fn make_int(val: i64, mc: &Mutation<'gc>) -> Self { pub fn make_int(val: i64, mc: &Mutation<'gc>) -> Self {
if val >= i32::MIN as i64 && val <= i32::MAX as i64 { if val >= i32::MIN as i64 && val <= i32::MAX as i64 {
Value::new_inline(val as i32) Value::new(val as i32)
} else { } else {
Value::new_gc(Gc::new(mc, val)) Value::new(Gc::new(mc, val))
}
}
}
impl<'gc> Value<'gc> {
#[inline]
pub fn is_float(self) -> bool {
self.raw.is_float()
}
#[inline]
pub fn is<T: Storable>(self) -> bool {
self.tag() == Some(T::TAG)
}
}
impl<'gc> Value<'gc> {
#[inline]
pub fn as_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] #[inline]
pub fn as_gc<T: GcStorable>(self) -> Option<Gc<'gc, T>> { pub fn is<T: ValueVariant>(self) -> bool {
if self.is::<T>() { T::is_value(&self)
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
} }
#[inline]
pub fn downcast<T: ValueVariant>(self) -> Option<T::Ty<'gc>> {
self.is::<T>().then(|| unsafe { T::from_raw(&self) })
} }
#[inline] #[inline]
@@ -255,19 +256,19 @@ impl<'gc> Value<'gc> {
} }
#[inline] #[inline]
pub fn as_num(self) -> Option<NixNum> { pub fn downcast_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)) 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)) Some(NixNum::Int(*gc_i))
} else { } else {
self.as_float().map(NixNum::Float) self.downcast::<f64>().map(NixNum::Float)
} }
} }
#[inline] #[inline]
pub fn restrict(self) -> Result<StrictValue<'gc>, Gc<'gc, Thunk<'gc>>> { 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) Err(thunk)
} else { } else {
Ok(StrictValue(self)) Ok(StrictValue(self))
@@ -276,7 +277,7 @@ impl<'gc> Value<'gc> {
#[inline] #[inline]
pub fn ty(self) -> NixType { pub fn ty(self) -> NixType {
if self.is_float() { if self.is::<f64>() {
NixType::Float NixType::Float
} else if self.is::<i32>() || self.is::<i64>() { } else if self.is::<i32>() || self.is::<i64>() {
NixType::Int NixType::Int
@@ -308,28 +309,13 @@ impl<'gc> Value<'gc> {
} }
#[inline] #[inline]
pub fn expect_inline<T: InlineStorable>(self) -> Result<T, NixType> { pub fn expect<T: ValueVariant>(self) -> Result<T::Ty<'gc>, NixType> {
self.as_inline::<T>().ok_or_else(|| self.ty()) self.downcast::<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())
} }
#[inline] #[inline]
pub fn expect_num(self) -> Result<NixNum, NixType> { pub fn expect_num(self) -> Result<NixNum, NixType> {
self.as_num().ok_or_else(|| self.ty()) self.downcast_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())
} }
} }
@@ -347,37 +333,21 @@ impl<'gc> From<StaticValue> for Value<'gc> {
impl StaticValue { impl StaticValue {
#[inline] #[inline]
pub fn new_float(val: f64) -> Self { #[allow(private_bounds)]
Self(Value::new_float(val)) pub fn new<T: Storable + 'static>(val: T) -> Self {
Self(Value::new(val))
} }
#[inline] #[inline]
pub fn new_inline<T: InlineStorable>(val: T) -> Self { pub fn is<T: ValueVariant>(self) -> bool {
Self(Value::new_inline(val))
}
#[inline]
pub fn new_primop(id: BuiltinId, arity: u8, dispatch_ip: u32) -> Self {
Self(Value::new_inline(PrimOp {
id,
arity,
dispatch_ip,
}))
}
#[inline]
pub fn is_float(self) -> bool {
self.0.is_float()
}
#[inline]
pub fn is<T: InlineStorable>(self) -> bool {
self.0.is::<T>() self.0.is::<T>()
} }
#[inline] #[inline]
pub fn as_float(self) -> Option<f64> { pub fn downcast<T: ValueVariant>(self) -> Option<T::Ty<'static>> {
self.0.as_float() self.0.downcast::<T>()
}
#[inline]
pub fn as_inline<T: InlineStorable>(self) -> Option<T> {
self.0.as_inline::<T>()
} }
#[inline] #[inline]
pub fn to_bits(self) -> u64 { pub fn to_bits(self) -> u64 {
self.0.raw.to_bits() self.0.raw.to_bits()
@@ -638,7 +608,7 @@ impl RawStore for PrimOp {
fn from_val(value: &RawValue) -> Self { fn from_val(value: &RawValue) -> Self {
let [id, arity, bytes @ ..] = *value.data(); let [id, arity, bytes @ ..] = *value.data();
Self { Self {
id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"), id: BuiltinId::try_from(id).expect("invalid BuiltinId"),
arity, arity,
dispatch_ip: u32::from_le_bytes(bytes), dispatch_ip: u32::from_le_bytes(bytes),
} }
+6 -12
View File
@@ -3,22 +3,16 @@ name = "fix-vm"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[features]
tailcall = []
[dependencies] [dependencies]
gc-arena = { workspace = true } gc-arena = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
num_enum = { workspace = true }
smallvec = { 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"] } sysinfo = { version = "0.38", default-features = false, features = ["system"] }
fix-builtins = { path = "../fix-builtins" } fix-bytecode = { path = "../fix-bytecode" }
fix-codegen = { path = "../fix-codegen" }
fix-common = { path = "../fix-common" }
fix-error = { path = "../fix-error" } fix-error = { path = "../fix-error" }
fix-abstract-vm = { path = "../fix-abstract-vm" } fix-lang = { path = "../fix-lang" }
fix-primops = { path = "../fix-primops" } fix-runtime = { path = "../fix-runtime" }
[features]
tailcall = []
+11 -11
View File
@@ -61,7 +61,7 @@ macro_rules! tail_fn {
pc: u32, pc: u32,
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let result = vm.$name(); let result = crate::instructions::$name(vm);
tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel) tail_dispatch_after!(result, pc + 1, vm, mc, ctx, bc, table, fuel)
} }
}; };
@@ -76,7 +76,7 @@ macro_rules! tail_fn {
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize); 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) tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
} }
}; };
@@ -91,7 +91,7 @@ macro_rules! tail_fn {
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize); 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) tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
} }
}; };
@@ -106,7 +106,7 @@ macro_rules! tail_fn {
fuel: u32, fuel: u32,
) -> TailResult { ) -> TailResult {
let mut reader = BytecodeReader::from_after_op(bc, pc as usize); 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) tail_dispatch_after!(result, reader.pc() as u32, vm, mc, ctx, bc, table, fuel)
} }
}; };
@@ -120,7 +120,7 @@ macro_rules! tail_fn {
pc: u32, pc: u32,
fuel: u32, fuel: u32,
) -> TailResult { ) -> 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) 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_make_pattern_closure, (reader, mc));
tail_fn!(op_call, (ctx, reader, mc)); tail_fn!(op_call, (ctx, reader, mc));
tail_fn!(op_dispatch_primop, (ctx, reader, mc)); tail_fn!(op_dispatch_cont, (ctx, reader, mc));
tail_fn!(op_return, (ctx, reader, mc)); tail_fn!(op_return, (ctx, reader, mc));
tail_fn!(op_make_attrs, (ctx, reader, mc)); tail_fn!(op_make_attrs, (ctx, reader, mc));
@@ -202,16 +202,16 @@ macro_rules! table {
impl<'gc, C: VmRuntimeCtx> DispatchTable<'gc, C> { impl<'gc, C: VmRuntimeCtx> DispatchTable<'gc, C> {
pub(crate) const NEW: Self = { pub(crate) const NEW: Self = {
let mut arr: [OpFn<'gc, C>; 256] = [op_illegal; 256]; 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) 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. // loses, or renames a variant that isn't wired up above.
#[allow(dead_code)] #[allow(dead_code)]
const _: fn(fix_codegen::Op) = |op| match op { const _: fn(fix_bytecode::Op) = |op| match op {
$( fix_codegen::Op::$variant => (), )* $( fix_bytecode::Op::$variant => (), )*
}; };
}; };
} }
@@ -235,7 +235,7 @@ table! {
MakePatternClosure => op_make_pattern_closure, MakePatternClosure => op_make_pattern_closure,
Call => op_call, Call => op_call,
DispatchPrimOp => op_dispatch_primop, DispatchCont => op_dispatch_cont,
Return => op_return, Return => op_return,
MakeAttrs => op_make_attrs, MakeAttrs => op_make_attrs,
+101 -83
View File
@@ -1,19 +1,18 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use fix_abstract_vm::*; use fix_runtime::*;
use gc_arena::{Gc, Mutation, RefLock}; use gc_arena::{Gc, Mutation, RefLock};
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx}; use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_add( pub(crate) fn op_add<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
// if the LHS is a path, the result is a path obtained by // if the LHS is a path, the result is a path obtained by
// canonicalizing the concatenated string. RHS may be a path or a // canonicalizing the concatenated string. RHS may be a path or a
// string. (A `string + path` keeps the string-typed result, handled // string. (A `string + path` keeps the string-typed result, handled
@@ -21,7 +20,7 @@ impl<'gc> crate::Vm<'gc> {
if lhs.is::<Path>() { if lhs.is::<Path>() {
let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs)) let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs))
else { else {
return self.finish_err(fix_error::Error::eval_error(format!( return m.finish_err(fix_error::Error::eval_error(format!(
"cannot append {} to a path", "cannot append {} to a path",
rhs.ty() rhs.ty()
))); )));
@@ -29,7 +28,7 @@ impl<'gc> crate::Vm<'gc> {
let combined = format!("{ls}{rs}"); let combined = format!("{ls}{rs}");
let canon = canon_path_str(&combined); let canon = canon_path_str(&combined);
let sid = ctx.intern_string(canon); let sid = ctx.intern_string(canon);
self.push(Value::new_inline(fix_abstract_vm::Path(sid))); m.push(Value::new(fix_runtime::Path(sid)));
return Step::Continue(()); return Step::Continue(());
} }
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) { if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
@@ -40,54 +39,66 @@ impl<'gc> crate::Vm<'gc> {
mc, mc,
crate::NixString::with_context(format!("{ls}{rs}"), merged), crate::NixString::with_context(format!("{ls}{rs}"), merged),
); );
self.push(Value::new_gc(ns)); m.push(Value::new(ns));
return Step::Continue(()); return Step::Continue(());
} }
let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b); let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b);
match res { match res {
Ok(val) => { Ok(val) => {
self.push(val); m.push(val);
Step::Continue(()) Step::Continue(())
} }
Err(e) => self.finish_vm_err(e), Err(e) => m.finish_vm_err(e),
} }
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_sub(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_sub<'gc, M: Machine<'gc>>(
self.op_arith(reader, mc, i64::wrapping_sub, |a, b| a - b) m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
op_arith(m, reader, mc, i64::wrapping_sub, |a, b| a - b)
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_mul(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_mul<'gc, M: Machine<'gc>>(
self.op_arith(reader, mc, i64::wrapping_mul, |a, b| a * b) m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
op_arith(m, reader, mc, i64::wrapping_mul, |a, b| a * b)
} }
#[inline(always)] #[inline(always)]
fn op_arith( fn op_arith<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
int_op: fn(i64, i64) -> i64, int_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64, float_op: fn(f64, f64) -> f64,
) -> Step { ) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let res = numeric_binop(lhs, rhs, mc, int_op, float_op); let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
match res { match res {
Ok(val) => { Ok(val) => {
self.push(val); m.push(val);
Step::Continue(()) Step::Continue(())
} }
Err(e) => self.finish_vm_err(e), Err(e) => m.finish_vm_err(e),
} }
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_div<'gc, M: Machine<'gc>>(
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; 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)) { match (get_num(lhs), get_num(rhs)) {
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => { (_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error( return m.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
"division by zero", "division by zero",
))); )));
} }
@@ -96,100 +107,100 @@ impl<'gc> crate::Vm<'gc> {
let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b); let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b);
match res { match res {
Ok(val) => { Ok(val) => {
self.push(val); m.push(val);
Step::Continue(()) Step::Continue(())
} }
Err(e) => self.finish_vm_err(e), Err(e) => m.finish_vm_err(e),
} }
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_eq( pub(crate) fn op_eq<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, false) crate::primops::start_eq(m, ctx, reader, mc, lhs, rhs, false)
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_neq( pub(crate) fn op_neq<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, true) crate::primops::start_eq(m, ctx, reader, mc, lhs, rhs, true)
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_lt( pub(crate) fn op_lt<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_lt) compare_values(m, ctx, reader, mc, Ordering::is_lt)
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_gt( pub(crate) fn op_gt<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_gt) compare_values(m, ctx, reader, mc, Ordering::is_gt)
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_leq( pub(crate) fn op_leq<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_le) compare_values(m, ctx, reader, mc, Ordering::is_le)
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_geq( pub(crate) fn op_geq<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
self.compare_values(ctx, reader, mc, Ordering::is_ge) compare_values(m, ctx, reader, mc, Ordering::is_ge)
} }
fn compare_values( fn compare_values<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &impl VmRuntimeCtx, ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
pred: fn(Ordering) -> bool, pred: fn(Ordering) -> bool,
) -> Step { ) -> Step {
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
match self.compare_values_inner(ctx, pred, lhs, rhs) { match compare_values_inner(m, ctx, pred, lhs, rhs) {
Ok(()) => Step::Continue(()), Ok(()) => Step::Continue(()),
Err(e) => self.finish_vm_err(e), Err(e) => m.finish_vm_err(e),
} }
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_concat( pub(crate) fn op_concat<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (l, r) = self.force_and_retry::<(Gc<List>, Gc<List>)>(reader, mc)?; let (l, r) = m.force_and_retry::<(Gc<List>, Gc<List>)>(reader, mc)?;
let mut items = smallvec::SmallVec::new(); let mut items = smallvec::SmallVec::new();
items.extend_from_slice(&l.inner.borrow()); items.extend_from_slice(&l.inner.borrow());
items.extend_from_slice(&r.inner.borrow()); items.extend_from_slice(&r.inner.borrow());
self.push(Value::new_gc(Gc::new( m.push(Value::new(Gc::new(
mc, mc,
crate::List { crate::List {
inner: RefLock::new(items), inner: RefLock::new(items),
@@ -199,35 +210,43 @@ impl<'gc> crate::Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_update( pub(crate) fn op_update<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let (l, r) = self.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?; let (l, r) = m.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
self.push(Value::new_gc(l.merge(&r, mc))); m.push(Value::new(l.merge(&r, mc)));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_neg(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_neg<'gc, M: Machine<'gc>>(
let rhs = self.force_and_retry::<NixNum>(reader, mc)?; m: &mut M,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let rhs = m.force_and_retry::<NixNum>(reader, mc)?;
match rhs { match rhs {
NixNum::Int(int) => self.push(Value::make_int(-int, mc)), NixNum::Int(int) => m.push(Value::make_int(-int, mc)),
NixNum::Float(float) => self.push(Value::new_float(-float)), NixNum::Float(float) => m.push(Value::new(-float)),
} }
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_not(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { pub(crate) fn op_not<'gc, M: Machine<'gc>>(
let rhs = self.force_and_retry::<bool>(reader, mc)?; m: &mut M,
self.push(Value::new_inline(!rhs)); reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let rhs = m.force_and_retry::<bool>(reader, mc)?;
m.push(Value::new(!rhs));
Step::Continue(()) Step::Continue(())
} }
fn compare_values_inner( fn compare_values_inner<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &impl VmRuntimeCtx, ctx: &impl VmRuntimeCtx,
pred: fn(Ordering) -> bool, pred: fn(Ordering) -> bool,
lhs: StrictValue<'gc>, lhs: StrictValue<'gc>,
@@ -244,31 +263,34 @@ impl<'gc> crate::Vm<'gc> {
a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less) a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less)
} }
}; };
self.push(Value::new_inline(pred(ord))); m.push(Value::new(pred(ord)));
return Ok(()); return Ok(());
} }
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
self.push(Value::new_inline(pred(a.cmp(b)))); m.push(Value::new(pred(a.cmp(b))));
return Ok(()); return Ok(());
} }
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>()) {
let a = ctx.resolve_string(a.0); let a = ctx.resolve_string(a.0);
let b = ctx.resolve_string(b.0); let b = ctx.resolve_string(b.0);
self.push(Value::new_inline(pred(a.cmp(b)))); m.push(Value::new(pred(a.cmp(b))));
return Ok(()); return Ok(());
} }
// TODO: compare other types // TODO: compare other types
Err(crate::vm_err("cannot compare these types")) Err(crate::vm_err(format!(
} "cannot compare {} with {}",
lhs.ty(),
rhs.ty()
)))
} }
pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> { pub(crate) fn get_num(val: StrictValue<'_>) -> Option<NixNum> {
if let Some(i) = val.as_inline::<i32>() { if let Some(i) = val.downcast::<i32>() {
Some(NixNum::Int(i as i64)) Some(NixNum::Int(i64::from(i)))
} else if let Some(gc_i) = val.as_gc::<i64>() { } else if let Some(gc_i) = val.downcast::<i64>() {
Some(NixNum::Int(*gc_i)) Some(NixNum::Int(*gc_i))
} else { } else {
val.as_float().map(NixNum::Float) val.downcast::<f64>().map(NixNum::Float)
} }
} }
@@ -282,13 +304,9 @@ fn numeric_binop<'gc>(
) -> crate::VmResult<Value<'gc>> { ) -> crate::VmResult<Value<'gc>> {
match (get_num(lhs), get_num(rhs)) { match (get_num(lhs), get_num(rhs)) {
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)), (Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)),
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(float_op(a, b))), (Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new(float_op(a, b))),
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => { (Some(NixNum::Int(a)), Some(NixNum::Float(b))) => Ok(Value::new(float_op(a as f64, b))),
Ok(Value::new_float(float_op(a as f64, b))) (Some(NixNum::Float(a)), Some(NixNum::Int(b))) => Ok(Value::new(float_op(a, b as f64))),
}
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
Ok(Value::new_float(float_op(a, b as f64)))
}
_ => Err(crate::vm_err(format!( _ => Err(crate::vm_err(format!(
"cannot perform arithmetic on non-numbers: {:?}", "cannot perform arithmetic on non-numbers: {:?}",
(lhs.ty(), rhs.ty()) (lhs.ty(), rhs.ty())
+63 -65
View File
@@ -1,6 +1,6 @@
use fix_abstract_vm::{resolve_operand, *}; use fix_bytecode::Continuation;
use fix_builtins::PrimOpPhase;
use fix_error::Error; use fix_error::Error;
use fix_runtime::{resolve_operand, *};
use gc_arena::{Gc, Mutation, RefLock}; use gc_arena::{Gc, Mutation, RefLock};
use crate::{ use crate::{
@@ -8,31 +8,30 @@ use crate::{
VmRuntimeCtxExt, VmRuntimeCtxExt,
}; };
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn call( pub(crate) fn call<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
arg: Value<'gc>, arg: Value<'gc>,
resume_pc: usize, resume_pc: usize,
) -> Step { ) -> Step {
let func = self.force_and_retry::<StrictValue>(reader, mc)?; let func = m.force_and_retry::<StrictValue>(reader, mc)?;
if self.call_depth > 10000 { if m.call_depth() > 10000 {
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded")); return m.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
} }
self.call_depth += 1; m.inc_call_depth();
if let Some(closure) = func.as_gc::<Closure>() { if let Some(closure) = func.downcast::<Closure>() {
if closure.pattern.is_some() { if closure.pattern.is_some() {
// FIXME: better DX... // FIXME: better DX...
self.push(func.relax()); m.push(func.relax());
self.push(arg); m.push(arg);
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
pc: resume_pc, pc: resume_pc,
thunk: None, thunk: None,
env: self.env, env: m.env(),
}); });
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize); reader.set_pc(Continuation::CallPattern.ip() as usize);
return Step::Continue(()); return Step::Continue(());
} }
@@ -40,20 +39,20 @@ impl<'gc> crate::Vm<'gc> {
let n_locals = closure.n_locals; let n_locals = closure.n_locals;
let env = closure.env; let env = closure.env;
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env))); let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
pc: resume_pc, pc: resume_pc,
thunk: None, thunk: None,
env: self.env, env: m.env(),
}); });
reader.set_pc(ip as usize); reader.set_pc(ip as usize);
self.env = new_env; m.set_env(new_env);
} else if let Some(primop) = func.as_inline::<PrimOp>() { } else if let Some(primop) = func.downcast::<PrimOp>() {
if primop.arity == 1 { if primop.arity == 1 {
self.push(arg); m.push(arg);
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
pc: resume_pc, pc: resume_pc,
thunk: None, thunk: None,
env: self.env, env: m.env(),
}); });
reader.set_pc(primop.dispatch_ip as usize) reader.set_pc(primop.dispatch_ip as usize)
} else { } else {
@@ -62,18 +61,18 @@ impl<'gc> crate::Vm<'gc> {
arity: primop.arity - 1, arity: primop.arity - 1,
args: [arg, Value::default(), Value::default()], args: [arg, Value::default(), Value::default()],
}; };
self.push(Value::new_gc(Gc::new(mc, app))); m.push(Value::new(Gc::new(mc, app)));
} }
} else if let Some(app) = func.as_gc::<PrimOpApp>() { } else if let Some(app) = func.downcast::<PrimOpApp>() {
if app.arity == 1 { if app.arity == 1 {
for i in 0..app.primop.arity - 1 { for i in 0..app.primop.arity - 1 {
self.push(app.args[i as usize]); m.push(app.args[i as usize]);
} }
self.push(arg); m.push(arg);
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
pc: resume_pc, pc: resume_pc,
thunk: None, thunk: None,
env: self.env, env: m.env(),
}); });
reader.set_pc(app.primop.dispatch_ip as usize) reader.set_pc(app.primop.dispatch_ip as usize)
} else { } else {
@@ -83,29 +82,29 @@ impl<'gc> crate::Vm<'gc> {
..*app ..*app
}; };
new_app.args[position] = arg; new_app.args[position] = arg;
self.push(Value::new_gc(Gc::new(mc, new_app))) m.push(Value::new(Gc::new(mc, new_app)))
} }
} else if let Some(attrs) = func.as_gc::<AttrSet>() } else if let Some(attrs) = func.downcast::<AttrSet>()
&& let Some(functor) = attrs.lookup(self.functor_sym) && let Some(functor) = attrs.lookup(m.functor_sym())
{ {
// f arg => (f.__functor f) arg // f arg => (f.__functor f) arg
// //
// Stage the work for `CallFunctor1` so retries during force are // Stage the work for `CallFunctor1` so retries during force are
// safe: the stack invariant `[..., orig_arg, self, functor]` // safe: the stack invariant `[..., orig_arg, self, functor]`
// holds every time control re-enters phase 1. // holds every time control re-enters phase 1.
self.call_depth -= 1; m.dec_call_depth();
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
pc: resume_pc, pc: resume_pc,
thunk: None, thunk: None,
env: self.env, env: m.env(),
}); });
self.push(arg); m.push(arg);
self.push(func.relax()); m.push(func.relax());
self.push(functor); m.push(functor);
reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize); reader.set_pc(Continuation::CallFunctor1.ip() as usize);
return Step::Continue(()); return Step::Continue(());
} else { } else {
return self.finish_err(Error::eval_error(format!( return m.finish_err(Error::eval_error(format!(
"attempt to call something which is not a function but {}", "attempt to call something which is not a function but {}",
func.ty() func.ty()
))); )));
@@ -114,48 +113,48 @@ impl<'gc> crate::Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_call( pub(crate) fn op_call<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &impl VmRuntimeCtx, ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let arg = resolve_operand(&reader.read_operand_data(ctx), mc, self); let arg = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let pc = reader.pc(); let pc = reader.pc();
self.call(reader, mc, arg, pc) m.call(reader, mc, arg, pc)
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_return( pub(crate) fn op_return<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?; let val = m.force_and_retry::<StrictValue>(reader, mc)?;
let Some(CallFrame { let Some(CallFrame {
pc: ret_pc, pc: ret_pc,
thunk, thunk,
env, env,
}) = self.call_stack.pop() }) = m.pop_call_frame()
else { else {
match self.force_mode { match m.force_mode() {
ForceMode::AsIs => return self.finish_ok(ctx.convert_value(val.relax())), ForceMode::AsIs => return m.finish_ok(ctx.convert_value(val.relax())),
ForceMode::Shallow => { ForceMode::Shallow => {
self.push(val.relax()); m.push(val.relax());
reader.set_pc(PrimOpPhase::ForceResultShallow.ip() as usize); reader.set_pc(Continuation::ForceResultShallow.ip() as usize);
return Step::Continue(()); return Step::Continue(());
} }
ForceMode::Deep => { ForceMode::Deep => {
self.push(val.relax()); m.push(val.relax());
self.push(val.relax()); m.push(val.relax());
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize, pc: Continuation::ForceResultDeepFinish.ip() as usize,
thunk: None, thunk: None,
env: self.env, env: m.env(),
}); });
self.call_depth += 1; m.inc_call_depth();
reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize); reader.set_pc(Continuation::PDeepSeq.ip() as usize);
return Step::Continue(()); return Step::Continue(());
} }
} }
@@ -164,20 +163,19 @@ impl<'gc> crate::Vm<'gc> {
if let Some(outer_thunk) = thunk { if let Some(outer_thunk) = thunk {
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val); *outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
} else { } else {
self.call_depth -= 1; m.dec_call_depth();
self.push(val.relax()) m.push(val.relax())
} }
self.env = env; m.set_env(env);
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_dispatch_primop( pub(crate) fn op_dispatch_cont<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
fix_primops::dispatch_primop(self, ctx, reader, mc) crate::primops::dispatch_cont(m, ctx, reader, mc)
}
} }
+13 -14
View File
@@ -1,11 +1,11 @@
use fix_runtime::Machine;
use gc_arena::{Gc, Mutation, RefLock}; use gc_arena::{Gc, Mutation, RefLock};
use crate::{BytecodeReader, Step, ThunkState, Value}; use crate::{BytecodeReader, Step, ThunkState, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_thunk( pub(crate) fn op_make_thunk<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
@@ -14,16 +14,16 @@ impl<'gc> crate::Vm<'gc> {
mc, mc,
RefLock::new(ThunkState::Pending { RefLock::new(ThunkState::Pending {
ip: entry_point as usize, ip: entry_point as usize,
env: self.env, env: m.env(),
}), }),
); );
self.push(Value::new_gc(thunk)); m.push(Value::new(thunk));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_closure( pub(crate) fn op_make_closure<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
@@ -34,17 +34,17 @@ impl<'gc> crate::Vm<'gc> {
crate::Closure { crate::Closure {
ip: entry_point, ip: entry_point,
n_locals, n_locals,
env: self.env, env: m.env(),
pattern: None, pattern: None,
}, },
); );
self.push(Value::new_gc(closure)); m.push(Value::new(closure));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_pattern_closure( pub(crate) fn op_make_pattern_closure<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
@@ -84,11 +84,10 @@ impl<'gc> crate::Vm<'gc> {
crate::Closure { crate::Closure {
ip: entry_point, ip: entry_point,
n_locals, n_locals,
env: self.env, env: m.env(),
pattern: Some(pattern), pattern: Some(pattern),
}, },
); );
self.push(Value::new_gc(closure)); m.push(Value::new(closure));
Step::Continue(()) Step::Continue(())
} }
}
+70 -67
View File
@@ -1,6 +1,6 @@
use fix_abstract_vm::{NixType, resolve_operand};
use fix_common::StringId;
use fix_error::Error; use fix_error::Error;
use fix_lang::StringId;
use fix_runtime::{Machine, MachineExt, NixType, resolve_operand};
use gc_arena::{Gc, RefLock}; use gc_arena::{Gc, RefLock};
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -8,10 +8,9 @@ use crate::{
AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
}; };
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_attrs( pub(crate) fn op_make_attrs<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
@@ -21,35 +20,35 @@ impl<'gc> crate::Vm<'gc> {
for i in 0..dynamic_count { for i in 0..dynamic_count {
let depth = dynamic_count - 1 - i; let depth = dynamic_count - 1 - i;
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?; m.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?;
} }
let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count); let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count);
for i in 0..dynamic_count { for i in 0..dynamic_count {
let depth = dynamic_count - 1 - i; let depth = dynamic_count - 1 - i;
let key_val = self.peek_forced(depth); let key_val = m.peek_forced(depth);
let key_sid = match ctx.get_string_id(key_val) { let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => Some(id), Ok(id) => Some(id),
Err(NixType::Null) => None, Err(NixType::Null) => None,
Err(got) => return self.finish_type_err(NixType::String, got), Err(got) => return m.finish_type_err(NixType::String, got),
}; };
dyn_keys.push(key_sid); dyn_keys.push(key_sid);
} }
self.stack.truncate(self.stack.len() - dynamic_count); m.drop_n(dynamic_count);
let mut kv: SmallVec<[(crate::StringId, Value); 4]> = let mut kv: SmallVec<[(crate::StringId, Value); 4]> =
SmallVec::with_capacity(static_count + dynamic_count); SmallVec::with_capacity(static_count + dynamic_count);
for _ in 0..static_count { for _ in 0..static_count {
let key = reader.read_string_id(); let key = reader.read_string_id();
let val = resolve_operand(&reader.read_operand_data(ctx), mc, self); let val = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
kv.push((key, val)); kv.push((key, val));
} }
for key in dyn_keys { for key in dyn_keys {
let val = resolve_operand(&reader.read_operand_data(ctx), mc, self); let val = resolve_operand(&reader.read_operand_data(), mc, ctx, m);
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
if let Some(key) = key { if let Some(key) = key {
kv.push((key, val)) kv.push((key, val))
@@ -58,19 +57,19 @@ impl<'gc> crate::Vm<'gc> {
kv.sort_by_key(|(k, _)| *k); kv.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv)); let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
self.push(Value::new_gc(attrs)); m.push(Value::new(attrs));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_empty_attrs(&mut self) -> Step { pub(crate) fn op_make_empty_attrs<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(self.empty_attrs); m.push(m.empty_attrs());
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_select_static( pub(crate) fn op_select_static<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
@@ -78,38 +77,38 @@ impl<'gc> crate::Vm<'gc> {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let key = reader.read_string_id(); let key = reader.read_string_id();
let attrset = self.force_and_retry::<Gc<AttrSet>>(reader, mc)?; let attrset = m.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
match attrset.lookup(key) { match attrset.lookup(key) {
Some(v) => { Some(v) => {
self.push(v); m.push(v);
} }
None => return self.select_skip(key, ctx, reader), None => return select_skip(m, key, ctx, reader),
} }
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_select_dynamic( pub(crate) fn op_select_dynamic<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let (attrset, key_val) = self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?; let (attrset, key_val) = m.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) { let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id, Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got), Err(got) => return m.finish_type_err(NixType::String, got),
}; };
match attrset.lookup(key_sid) { match attrset.lookup(key_sid) {
Some(v) => { Some(v) => {
self.push(v); m.push(v);
} }
None => return self.select_skip(key_sid, ctx, reader), None => return select_skip(m, key_sid, ctx, reader),
} }
Step::Continue(()) Step::Continue(())
} }
@@ -118,13 +117,13 @@ impl<'gc> crate::Vm<'gc> {
/// Only recognises Select opcodes and jumps; encountering any other /// Only recognises Select opcodes and jumps; encountering any other
/// opcode means we've reached the end of the select sequence and /// opcode means we've reached the end of the select sequence and
/// should report the missing-attribute error. /// should report the missing-attribute error.
fn select_skip( fn select_skip<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
key: StringId, key: StringId,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
) -> Step { ) -> Step {
use fix_codegen::Op::*; use fix_bytecode::Op::*;
loop { loop {
match reader.read_op() { match reader.read_op() {
SelectStatic => { SelectStatic => {
@@ -143,8 +142,7 @@ impl<'gc> crate::Vm<'gc> {
} }
_ => { _ => {
let name = ctx.resolve_string(key); let name = ctx.resolve_string(key);
return self return m.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
.finish_err(Error::eval_error(format!("attribute '{name}' missing")));
} }
} }
} }
@@ -152,8 +150,8 @@ impl<'gc> crate::Vm<'gc> {
/// Skip the rest of a **HasAttr** attrpath after an intermediate /// Skip the rest of a **HasAttr** attrpath after an intermediate
/// lookup failed. Only recognises HasAttr opcodes and jumps. /// lookup failed. Only recognises HasAttr opcodes and jumps.
fn has_attr_skip(&mut self, reader: &mut BytecodeReader<'_>) -> Step { fn has_attr_skip(reader: &mut BytecodeReader<'_>) -> Step {
use fix_codegen::Op::*; use fix_bytecode::Op::*;
loop { loop {
match reader.read_op() { match reader.read_op() {
HasAttrPathStatic => { HasAttrPathStatic => {
@@ -185,8 +183,8 @@ impl<'gc> crate::Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_has_attr_path_static( pub(crate) fn op_has_attr_path_static<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
_ctx: &mut impl VmRuntimeCtx, _ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
@@ -194,74 +192,80 @@ impl<'gc> crate::Vm<'gc> {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let key = reader.read_string_id(); let key = reader.read_string_id();
let current = self.force_and_retry::<StrictValue>(reader, mc)?; let current = m.force_and_retry::<StrictValue>(reader, mc)?;
match current match current
.as_gc::<AttrSet>() .downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(key)) .and_then(|attrs| attrs.lookup(key))
{ {
Some(v) => { Some(v) => {
self.push(v); m.push(v);
} }
None => return self.has_attr_skip(reader), None => return has_attr_skip(reader),
} }
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_has_attr_path_dynamic( pub(crate) fn op_has_attr_path_dynamic<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let (current, key_val) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; let (current, key_val) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(key_val) { let key_sid = match ctx.get_string_id(key_val) {
Ok(id) => id, Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got), Err(got) => return m.finish_type_err(NixType::String, got),
}; };
match current match current
.as_gc::<AttrSet>() .downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid)) .and_then(|attrs| attrs.lookup(key_sid))
{ {
Some(v) => { Some(v) => {
self.push(v); m.push(v);
} }
None => return self.has_attr_skip(reader), None => return has_attr_skip(reader),
} }
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_jump_if_select_failed(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_jump_if_select_failed<'gc, M: Machine<'gc>>(
_m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
// No-op // No-op
let _offset = reader.read_i32(); let _offset = reader.read_i32();
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_jump_if_select_succeeded(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_jump_if_select_succeeded<'gc, M: Machine<'gc>>(
_m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let offset = reader.read_i32(); let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_has_attr_static( pub(crate) fn op_has_attr_static<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let key = reader.read_string_id(); let key = reader.read_string_id();
let current = self.force_and_retry::<StrictValue>(reader, mc)?; let current = m.force_and_retry::<StrictValue>(reader, mc)?;
self.push(Value::new_inline( m.push(Value::new(
current current
.as_gc::<AttrSet>() .downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(key)) .and_then(|attrs| attrs.lookup(key))
.is_some(), .is_some(),
)); ));
@@ -272,22 +276,22 @@ impl<'gc> crate::Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_has_attr_dynamic( pub(crate) fn op_has_attr_dynamic<'gc, M: MachineExt<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let (current, dyn_key) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; let (current, dyn_key) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
let key_sid = match ctx.get_string_id(dyn_key) { let key_sid = match ctx.get_string_id(dyn_key) {
Ok(id) => id, Ok(id) => id,
Err(got) => return self.finish_type_err(NixType::String, got), Err(got) => return m.finish_type_err(NixType::String, got),
}; };
self.push(Value::new_inline( m.push(Value::new(
current current
.as_gc::<AttrSet>() .downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(key_sid)) .and_then(|attrs| attrs.lookup(key_sid))
.is_some(), .is_some(),
)); ));
@@ -298,15 +302,15 @@ impl<'gc> crate::Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_has_attr_resolve(&mut self) -> Step { 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) // If we reach here, has_attr check has failed, push false (AttrSet is already popped)
self.push(Value::new_inline(false)); m.push(Value::new(false));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_list( pub(crate) fn op_make_list<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
@@ -314,7 +318,7 @@ impl<'gc> crate::Vm<'gc> {
let count = reader.read_u32() as usize; let count = reader.read_u32() as usize;
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count); let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
for _ in 0..count { for _ in 0..count {
items.push(resolve_operand(&reader.read_operand_data(ctx), mc, self)); items.push(resolve_operand(&reader.read_operand_data(), mc, ctx, m));
} }
let list = Gc::new( let list = Gc::new(
mc, mc,
@@ -322,13 +326,12 @@ impl<'gc> crate::Vm<'gc> {
inner: RefLock::new(items), inner: RefLock::new(items),
}, },
); );
self.push(Value::new_gc(list)); m.push(Value::new(list));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_make_empty_list(&mut self) -> Step { pub(crate) fn op_make_empty_list<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(self.empty_list); m.push(m.empty_list());
Step::Continue(()) Step::Continue(())
} }
}
+14 -16
View File
@@ -1,48 +1,47 @@
use fix_abstract_vm::*;
use fix_error::Error; use fix_error::Error;
use fix_runtime::*;
use gc_arena::Mutation; use gc_arena::Mutation;
use crate::{BytecodeReader, Step, VmRuntimeCtx}; use crate::{BytecodeReader, Step, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_jump_if_false( pub(crate) fn op_jump_if_false<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let offset = reader.read_i32(); let offset = reader.read_i32();
let cond = self.force_and_retry::<StrictValue>(reader, mc)?; let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
if cond.as_inline::<bool>() == Some(false) { if cond.downcast::<bool>() == Some(false) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
} }
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_jump_if_true( pub(crate) fn op_jump_if_true<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let offset = reader.read_i32(); let offset = reader.read_i32();
let cond = self.force_and_retry::<StrictValue>(reader, mc)?; let cond = m.force_and_retry::<StrictValue>(reader, mc)?;
if cond.as_inline::<bool>() == Some(true) { if cond.downcast::<bool>() == Some(true) {
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
} }
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_jump(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_jump<'gc, M: Machine<'gc>>(_m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
let offset = reader.read_i32(); let offset = reader.read_i32();
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize); reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_assert( pub(crate) fn op_assert<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
@@ -50,11 +49,10 @@ impl<'gc> crate::Vm<'gc> {
let raw_id = reader.read_string_id(); let raw_id = reader.read_string_id();
let raw = ctx.resolve_string(raw_id); let raw = ctx.resolve_string(raw_id);
let _span_id = reader.read_u32(); let _span_id = reader.read_u32();
let assertion = self.force_and_retry::<bool>(reader, mc)?; let assertion = m.force_and_retry::<bool>(reader, mc)?;
if !assertion { if !assertion {
// FIXME: use catchable error // FIXME: use catchable error
return self.finish_err(Error::eval_error(format!("assertion '{raw}' failed"))); return m.finish_err(Error::eval_error(format!("assertion '{raw}' failed")));
} }
Step::Continue(()) Step::Continue(())
} }
}
+25 -17
View File
@@ -1,55 +1,63 @@
use fix_runtime::Machine;
use gc_arena::{Gc, Mutation}; use gc_arena::{Gc, Mutation};
use crate::{BytecodeReader, Step, Value}; use crate::{BytecodeReader, Step, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_smi(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_push_smi<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let val = reader.read_i32(); let val = reader.read_i32();
self.push(Value::new_inline(val)); m.push(Value::new(val));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_bigint( pub(crate) fn op_push_bigint<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = reader.read_i64(); let val = reader.read_i64();
self.push(Value::new_gc(Gc::new(mc, val))); m.push(Value::new(Gc::new(mc, val)));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_float(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_push_float<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let val = reader.read_f64(); let val = reader.read_f64();
self.push(Value::new_float(val)); m.push(Value::new(val));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_string(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_push_string<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let sid = reader.read_string_id(); let sid = reader.read_string_id();
self.push(Value::new_inline(sid)); m.push(Value::new(sid));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_null(&mut self) -> Step { pub(crate) fn op_push_null<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(Value::new_inline(crate::Null)); m.push(Value::new(crate::Null));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_true(&mut self) -> Step { pub(crate) fn op_push_true<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(Value::new_inline(true)); m.push(Value::new(true));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_push_false(&mut self) -> Step { pub(crate) fn op_push_false<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(Value::new_inline(false)); m.push(Value::new(false));
Step::Continue(()) Step::Continue(())
} }
}
+47 -51
View File
@@ -1,66 +1,63 @@
use std::path::PathBuf; use std::path::PathBuf;
use fix_abstract_vm::{ use fix_bytecode::Continuation;
AttrSet, NixString, Path, StrictValue, StringContext, canon_path_str
};
use fix_builtins::BuiltinId;
use fix_common::StringId;
use fix_error::Error; 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}; use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_builtins(&mut self) -> Step { pub(crate) fn op_load_builtins<'gc, M: Machine<'gc>>(m: &mut M) -> Step {
self.push(self.builtins); m.push(m.builtins());
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_builtin(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_load_builtin<'gc, M: Machine<'gc>>(
let Ok(id) = BuiltinId::try_from_primitive(reader.read_u8()) 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)); .map_err(|err| panic!("unknown builtin id: {}", err.number));
self.push(Value::new_inline(PrimOp { m.push(Value::new(PrimOp {
id, id,
arity: fix_builtins::BUILTINS[id as usize].1, arity: BUILTINS[id as usize].1,
dispatch_ip: id.entry_phase().ip(), dispatch_ip: Continuation::entry_for_builtin(id).ip(),
})); }));
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_load_repl_binding<'gc, M: Machine<'gc>>(
_m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let _name = reader.read_string_id(); let _name = reader.read_string_id();
todo!("LoadReplBinding"); todo!("LoadReplBinding");
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_scoped_binding( pub(crate) fn op_load_scoped_binding<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &impl VmRuntimeCtx, ctx: &impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
_mc: &gc_arena::Mutation<'gc>, _mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let slot_id = reader.read_u32(); let slot_id = reader.read_u32();
let name = reader.read_string_id(); let name = reader.read_string_id();
let scope = match self.scope_slots.get(slot_id as usize).copied() { let scope = m.scope_slot(slot_id);
Some(s) => s, let Some(attrs) = scope.downcast::<AttrSet>() else {
None => { return m.finish_err(Error::eval_error("internal: scope slot is not an attrset"));
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) { match attrs.lookup(name) {
Some(val) => { Some(val) => {
self.push(val); m.push(val);
Step::Continue(()) Step::Continue(())
} }
None => self.finish_err(Error::eval_error(format!( None => m.finish_err(Error::eval_error(format!(
"scoped binding '{}' not found", "scoped binding '{}' not found",
ctx.resolve_string(name) ctx.resolve_string(name)
))), ))),
@@ -68,18 +65,18 @@ impl<'gc> crate::Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_coerce_to_string( pub(crate) fn op_coerce_to_string<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?; let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if val.is::<StringId>() || val.is::<NixString>() { if val.is::<StringId>() || val.is::<NixString>() {
self.push(val.relax()); m.push(val.relax());
} else if let Some(p) = val.as_inline::<Path>() { } else if let Some(p) = val.downcast::<Path>() {
// Coercing a path to a string yields the canonical path text. // Coercing a path to a string yields the canonical path text.
// FIXME: copy to store // FIXME: copy to store
self.push(Value::new_inline(p.0)); m.push(Value::new(p.0));
} else { } else {
todo!("coerce other types to string: {:?}", val.ty()); todo!("coerce other types to string: {:?}", val.ty());
} }
@@ -87,8 +84,8 @@ impl<'gc> crate::Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_concat_strings( pub(crate) fn op_concat_strings<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
@@ -99,7 +96,7 @@ impl<'gc> crate::Vm<'gc> {
let mut total_len = 0; let mut total_len = 0;
let mut has_any_context = false; let mut has_any_context = false;
for i in 0..count { for i in 0..count {
let val = self.peek_forced(count - 1 - i); let val = m.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced"); let s = ctx.get_string(val).expect("coerced");
total_len += s.len(); total_len += s.len();
if !ctx.get_string_context(val).is_empty() { if !ctx.get_string_context(val).is_empty() {
@@ -110,7 +107,7 @@ impl<'gc> crate::Vm<'gc> {
let mut result = String::with_capacity(total_len); let mut result = String::with_capacity(total_len);
let mut merged = StringContext::new(); let mut merged = StringContext::new();
for i in 0..count { for i in 0..count {
let val = self.peek_forced(count - 1 - i); let val = m.peek_forced(count - 1 - i);
let s = ctx.get_string(val).expect("coerced"); let s = ctx.get_string(val).expect("coerced");
result.push_str(s); result.push_str(s);
if has_any_context { if has_any_context {
@@ -121,36 +118,36 @@ impl<'gc> crate::Vm<'gc> {
} }
} }
self.stack.truncate(self.stack.len() - count); m.drop_n(count);
if merged.is_empty() { if merged.is_empty() {
let sid = ctx.intern_string(result); let sid = ctx.intern_string(result);
self.push(Value::new_inline(sid)); m.push(Value::new(sid));
} else { } else {
let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged)); let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged));
self.push(Value::new_gc(ns)); m.push(Value::new(ns));
} }
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_resolve_path( pub(crate) fn op_resolve_path<'gc, M: MachineExt<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?; let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
let dir_id = reader.read_string_id(); let dir_id = reader.read_string_id();
// Already a path: keep as-is. ResolvePath is idempotent on paths. // Already a path: keep as-is. ResolvePath is idempotent on paths.
if let Some(p) = path_val.as_inline::<Path>() { if let Some(p) = path_val.downcast::<Path>() {
self.push(Value::new_inline(p)); m.push(Value::new(p));
return Step::Continue(()); return Step::Continue(());
} }
let path = match ctx.get_string(path_val) { let path = match ctx.get_string(path_val) {
Some(s) => s.to_owned(), Some(s) => s.to_owned(),
None => { None => {
return self.finish_err(Error::eval_error(format!( return m.finish_err(Error::eval_error(format!(
"expected a string for path, got {}", "expected a string for path, got {}",
path_val.ty() path_val.ty()
))); )));
@@ -158,13 +155,12 @@ impl<'gc> crate::Vm<'gc> {
}; };
let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) { let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) {
Ok(s) => s, Ok(s) => s,
Err(e) => return self.finish_err(e), Err(e) => return m.finish_err(e),
}; };
let sid = ctx.intern_string(resolved); let sid = ctx.intern_string(resolved);
self.push(Value::new_inline(Path(sid))); m.push(Value::new(Path(sid)));
Step::Continue(()) Step::Continue(())
} }
}
fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>> { fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>> {
let raw = if path.starts_with('/') { let raw = if path.starts_with('/') {
+10
View File
@@ -7,3 +7,13 @@ pub(crate) mod literals;
pub(crate) mod misc; pub(crate) mod misc;
pub(crate) mod variables; pub(crate) mod variables;
pub(crate) mod with_scope; 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::*;
+20 -14
View File
@@ -1,50 +1,56 @@
use fix_runtime::Machine;
use crate::{BytecodeReader, Mutation, Step, Value}; use crate::{BytecodeReader, Mutation, Step, Value};
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_local(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_load_local<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let idx = reader.read_u32() as usize; let idx = reader.read_u32() as usize;
self.push(self.env.borrow().locals[idx]); m.push(m.env().borrow().locals[idx]);
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_load_outer(&mut self, reader: &mut BytecodeReader<'_>) -> Step { pub(crate) fn op_load_outer<'gc, M: Machine<'gc>>(
m: &mut M,
reader: &mut BytecodeReader<'_>,
) -> Step {
let layer = reader.read_u8(); let layer = reader.read_u8();
let idx = reader.read_u32() as usize; let idx = reader.read_u32() as usize;
let mut cur = self.env; let mut cur = m.env();
for _ in 0..layer { for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
cur = prev; cur = prev;
} }
let val = cur.borrow().locals[idx]; let val = cur.borrow().locals[idx];
self.push(val); m.push(val);
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_store_local( pub(crate) fn op_store_local<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let idx = reader.read_u32() as usize; let idx = reader.read_u32() as usize;
let val = self.pop(); let val = m.pop();
self.env.borrow_mut(mc).locals[idx] = val; m.env().borrow_mut(mc).locals[idx] = val;
Step::Continue(()) Step::Continue(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn op_alloc_locals( pub(crate) fn op_alloc_locals<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let count = reader.read_u32() as usize; let count = reader.read_u32() as usize;
self.env m.env()
.borrow_mut(mc) .borrow_mut(mc)
.locals .locals
.extend(std::iter::repeat_n(Value::default(), count)); .extend(std::iter::repeat_n(Value::default(), count));
Step::Continue(()) Step::Continue(())
} }
}
+18 -21
View File
@@ -1,26 +1,25 @@
use fix_abstract_vm::{resolve_operand, *};
use fix_common::Symbol;
use fix_error::Error; use fix_error::Error;
use fix_lang::Symbol;
use fix_runtime::{resolve_operand, *};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx}; use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
impl<'gc> crate::Vm<'gc> {
#[inline(always)] #[inline(always)]
pub(crate) fn op_lookup_with( pub(crate) fn op_lookup_with<'gc, M: Machine<'gc>>(
&mut self, m: &mut M,
ctx: &mut impl VmRuntimeCtx, ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>, reader: &mut BytecodeReader<'_>,
mc: &gc_arena::Mutation<'gc>, mc: &gc_arena::Mutation<'gc>,
) -> Step { ) -> Step {
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let counter = self.peek_forced(0).as_inline::<i32>().unwrap(); let counter = m.peek_forced(0).downcast::<i32>().unwrap();
let name = reader.read_string_id(); let name = reader.read_string_id();
let n = reader.read_u8(); let n = reader.read_u8();
let mut namespaces = SmallVec::<[_; 2]>::new(); let mut namespaces = SmallVec::<[_; 2]>::new();
for _ in 0..n { for _ in 0..n {
namespaces.push(resolve_operand(&reader.read_operand_data(ctx), mc, self)); namespaces.push(resolve_operand(&reader.read_operand_data(), mc, ctx, m));
} }
let resume_pc = reader.inst_start_pc(); let resume_pc = reader.inst_start_pc();
@@ -31,48 +30,46 @@ impl<'gc> crate::Vm<'gc> {
match *state { match *state {
ThunkState::Pending { ip, env } => { ThunkState::Pending { ip, env } => {
*state = ThunkState::Blackhole; *state = ThunkState::Blackhole;
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
thunk: Some(thunk), thunk: Some(thunk),
pc: resume_pc, pc: resume_pc,
env: self.env, env: m.env(),
}); });
self.env = env; m.set_env(env);
reader.set_pc(ip); reader.set_pc(ip);
return Step::Break(Break::Force); return Step::Break(Break::Force);
} }
ThunkState::Evaluated(v) => v, ThunkState::Evaluated(v) => v,
ThunkState::Apply { func, arg } => { ThunkState::Apply { func, arg } => {
self.call_stack.push(CallFrame { m.push_call_frame(CallFrame {
thunk: Some(thunk), thunk: Some(thunk),
pc: resume_pc, pc: resume_pc,
env: self.env, env: m.env(),
}); });
self.push(func); m.push(func);
return self.call(reader, mc, arg, resume_pc); return m.call(reader, mc, arg, resume_pc);
} }
ThunkState::Blackhole => { ThunkState::Blackhole => {
return self return m.finish_err(Error::eval_error("infinite recursion encountered"));
.finish_err(Error::eval_error("infinite recursion encountered"));
} }
} }
} }
}; };
if let Some(val) = namespace if let Some(val) = namespace
.as_gc::<AttrSet>() .downcast::<AttrSet>()
.and_then(|attrs| attrs.lookup(name)) .and_then(|attrs| attrs.lookup(name))
{ {
self.replace(0, val); m.replace(0, val);
} else if counter + 1 == n as i32 { } else if counter + 1 == n as i32 {
return self.finish_err(Error::eval_error(format!( return m.finish_err(Error::eval_error(format!(
"undefined variable '{}'", "undefined variable '{}'",
Symbol::from(ctx.resolve_string(name)) Symbol::from(ctx.resolve_string(name))
))); )));
} else { } else {
self.replace(0, Value::new_inline(counter + 1)); m.replace(0, Value::new(counter + 1));
reader.set_pc(resume_pc); reader.set_pc(resume_pc);
} }
Step::Continue(()) Step::Continue(())
} }
}
+107 -230
View File
@@ -7,20 +7,19 @@
use std::path::PathBuf; use std::path::PathBuf;
use fix_builtins::{BUILTINS, BuiltinId}; use fix_bytecode::{Continuation, InstructionPtr};
use fix_codegen::InstructionPtr;
use fix_common::StringId;
use fix_error::{Error, Result, Source}; use fix_error::{Error, Result, Source};
use fix_lang::{BUILTINS, BuiltinId, StringId};
use gc_arena::metrics::Pacing; use gc_arena::metrics::Pacing;
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable}; use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
use hashbrown::HashMap; use hashbrown::HashMap;
use num_enum::TryFromPrimitive;
use smallvec::SmallVec; use smallvec::SmallVec;
#[cfg(feature = "tailcall")] #[cfg(feature = "tailcall")]
mod dispatch_tailcall; mod dispatch_tailcall;
pub use fix_abstract_vm::*; pub use fix_runtime::*;
mod instructions; mod instructions;
mod primops;
type VmResult<T> = std::result::Result<T, VmError>; type VmResult<T> = std::result::Result<T, VmError>;
@@ -46,7 +45,7 @@ pub struct Vm<'gc> {
force_mode: ForceMode, force_mode: ForceMode,
#[collect(require_static)] #[collect(require_static)]
result: Option<Result<fix_common::Value>>, result: Option<Result<fix_lang::Value>>,
#[collect(require_static)] #[collect(require_static)]
pending_load: Option<PendingLoad>, 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()); let mut entries = SmallVec::with_capacity(BUILTINS.len());
for (idx, &(name, arity)) in BUILTINS.iter().enumerate() { 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 = name.strip_prefix("__").unwrap_or(name);
let name = ctx.intern_string(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(( entries.push((
name, name,
Value::new_inline(PrimOp { Value::new(PrimOp {
id, id,
arity, arity,
dispatch_ip, dispatch_ip,
@@ -75,21 +74,15 @@ fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<
let consts = [ let consts = [
( (
"__currentSystem", "__currentSystem",
Value::new_inline(ctx.intern_string("x86_64-linux")), Value::new(ctx.intern_string("x86_64-linux")),
), ),
("__langVersion", Value::new_inline(6i32)), ("__langVersion", Value::new(6i32)),
( ("__nixVersion", Value::new(ctx.intern_string("2.24.0"))),
"__nixVersion", ("__storeDir", Value::new(ctx.intern_string("/nix/store"))),
Value::new_inline(ctx.intern_string("2.24.0")), ("__nixPath", Value::new(Gc::new(mc, List::default()))),
), ("null", Value::new(Null)),
( ("true", Value::new(true)),
"__storeDir", ("false", Value::new(false)),
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)),
]; ];
for (name, val) in consts { 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 self_ref_thunk = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
let sym = ctx.intern_string("builtins"); 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); entries.sort_by_key(|(k, _)| *k);
let builtins_set = Gc::new(mc, AttrSet::from_sorted_unchecked(entries)); 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) = *self_ref_thunk.borrow_mut(mc) =
ThunkState::Evaluated(builtins_value.restrict().expect("builtins is not a thunk")); ThunkState::Evaluated(builtins_value.restrict().expect("builtins is not a thunk"));
builtins_value builtins_value
@@ -126,8 +119,8 @@ impl<'gc> Vm<'gc> {
scope_slots: Vec::new(), scope_slots: Vec::new(),
builtins, builtins,
empty_list: Value::new_gc(Gc::new(mc, List::default())), empty_list: Value::new(Gc::new(mc, List::default())),
empty_attrs: Value::new_gc(Gc::new(mc, AttrSet::default())), empty_attrs: Value::new(Gc::new(mc, AttrSet::default())),
force_mode, force_mode,
@@ -137,45 +130,20 @@ impl<'gc> Vm<'gc> {
functor_sym: ctx.intern_string("__functor"), 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)] #[inline(always)]
fn push(&mut self, val: Value<'gc>) { fn push(&mut self, val: Value<'gc>) {
self.stack.push(val); self.stack.push(val);
} }
#[inline(always)] #[inline(always)]
#[must_use]
fn pop(&mut self) -> Value<'gc> { fn pop(&mut self) -> Value<'gc> {
self.stack.pop().expect("stack underflow") self.stack.pop().expect("stack underflow")
} }
#[inline(always)] #[inline(always)]
#[must_use]
fn peek(&self, depth: usize) -> Value<'gc> { fn peek(&self, depth: usize) -> Value<'gc> {
*self *self
.stack .stack
@@ -184,7 +152,6 @@ impl<'gc> Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
#[must_use]
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> { fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
self.stack self.stack
.get(self.stack.len() - depth - 1) .get(self.stack.len() - depth - 1)
@@ -193,6 +160,15 @@ impl<'gc> Vm<'gc> {
.expect("forced") .expect("forced")
} }
#[inline(always)]
fn pop_forced(&mut self) -> StrictValue<'gc> {
self.stack
.pop()
.expect("stack underflow")
.restrict()
.expect("forced")
}
#[inline(always)] #[inline(always)]
fn replace(&mut self, depth: usize, val: Value<'gc>) { fn replace(&mut self, depth: usize, val: Value<'gc>) {
let len = self.stack.len(); let len = self.stack.len();
@@ -203,72 +179,13 @@ impl<'gc> Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
#[cfg_attr(debug_assertions, track_caller)] fn drop_n(&mut self, depth: usize) {
fn pop_forced(&mut self) -> StrictValue<'gc> { self.stack.truncate(self.stack.len() - depth);
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))
} }
#[inline(always)] #[inline(always)]
#[allow(unused)] fn stack_len(&self) -> usize {
fn force_slot( self.stack.len()
&mut self,
depth: usize,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())
} }
#[inline(always)] #[inline(always)]
@@ -279,7 +196,7 @@ impl<'gc> Vm<'gc> {
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
resume_pc: usize, resume_pc: usize,
) -> Step { ) -> Step {
let Some(thunk) = self.peek(depth).as_gc::<Thunk>() else { let Some(thunk) = self.peek(depth).downcast::<Thunk>() else {
return Step::Continue(()); return Step::Continue(());
}; };
let mut state = thunk.borrow_mut(mc); 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)] #[inline(always)]
fn call( fn call(
@@ -370,7 +239,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
arg: Value<'gc>, arg: Value<'gc>,
resume_pc: usize, resume_pc: usize,
) -> Step { ) -> Step {
self.call(reader, mc, arg, resume_pc) instructions::call(self, reader, mc, arg, resume_pc)
} }
#[inline(always)] #[inline(always)]
@@ -409,18 +278,23 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
} }
#[inline(always)] #[inline(always)]
fn finish_ok(&mut self, val: fix_common::Value) -> Step { fn finish_ok(&mut self, val: fix_lang::Value) -> Step {
self.finish_ok(val) self.result = Some(Ok(val));
Step::Break(Break::Done)
} }
#[inline(always)] #[inline(always)]
fn finish_err(&mut self, err: Box<Error>) -> Step { fn finish_err(&mut self, err: Box<Error>) -> Step {
self.finish_err(err) self.result = Some(Err(err));
Step::Break(Break::Done)
} }
#[inline(always)] #[inline(always)]
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step { 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)] #[inline(always)]
@@ -481,7 +355,7 @@ impl<'gc> Machine<'gc> for Vm<'gc> {
enum Action { enum Action {
Continue { pc: usize }, Continue { pc: usize },
Done(Result<fix_common::Value>), Done(Result<fix_lang::Value>),
LoadFile(PendingLoad), LoadFile(PendingLoad),
} }
@@ -504,7 +378,7 @@ impl Vm<'_> {
ctx: &mut C, ctx: &mut C,
ip: InstructionPtr, ip: InstructionPtr,
force_mode: ForceMode, force_mode: ForceMode,
) -> Result<fix_common::Value> { ) -> Result<fix_lang::Value> {
let (code, runtime) = ctx.split(); let (code, runtime) = ctx.split();
let mut arena: Arena<Rootable![Vm<'_>]> = Arena::new(|mc| Vm::new(force_mode, mc, runtime)); let mut arena: Arena<Rootable![Vm<'_>]> = Arena::new(|mc| Vm::new(force_mode, mc, runtime));
arena.metrics().set_pacing(Pacing { arena.metrics().set_pacing(Pacing {
@@ -589,12 +463,15 @@ impl<'gc> Vm<'gc> {
pc: usize, pc: usize,
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Action { ) -> Action {
use fix_codegen::Op::*; use fix_bytecode::Op::*;
use instructions::*;
let mut reader = BytecodeReader::new(bytecode, pc); let mut reader = BytecodeReader::new(bytecode, pc);
let mut fuel = Self::DEFAULT_FUEL_AMOUNT; let mut fuel = Self::DEFAULT_FUEL_AMOUNT;
loop { loop {
use crate::instructions::op_dispatch_cont;
if fuel == 0 { if fuel == 0 {
return Action::Continue { pc: reader.pc() }; return Action::Continue { pc: reader.pc() };
} }
@@ -603,75 +480,75 @@ impl<'gc> Vm<'gc> {
let op = reader.read_op(); let op = reader.read_op();
let result = match op { let result = match op {
PushSmi => self.op_push_smi(&mut reader), PushSmi => op_push_smi(self, &mut reader),
PushBigInt => self.op_push_bigint(&mut reader, mc), PushBigInt => op_push_bigint(self, &mut reader, mc),
PushFloat => self.op_push_float(&mut reader), PushFloat => op_push_float(self, &mut reader),
PushString => self.op_push_string(&mut reader), PushString => op_push_string(self, &mut reader),
PushNull => self.op_push_null(), PushNull => op_push_null(self),
PushTrue => self.op_push_true(), PushTrue => op_push_true(self),
PushFalse => self.op_push_false(), PushFalse => op_push_false(self),
LoadLocal => self.op_load_local(&mut reader), LoadLocal => op_load_local(self, &mut reader),
LoadOuter => self.op_load_outer(&mut reader), LoadOuter => op_load_outer(self, &mut reader),
StoreLocal => self.op_store_local(&mut reader, mc), StoreLocal => op_store_local(self, &mut reader, mc),
AllocLocals => self.op_alloc_locals(&mut reader, mc), AllocLocals => op_alloc_locals(self, &mut reader, mc),
MakeThunk => self.op_make_thunk(&mut reader, mc), MakeThunk => op_make_thunk(self, &mut reader, mc),
MakeClosure => self.op_make_closure(&mut reader, mc), MakeClosure => op_make_closure(self, &mut reader, mc),
MakePatternClosure => self.op_make_pattern_closure(&mut reader, mc), MakePatternClosure => op_make_pattern_closure(self, &mut reader, mc),
Call => self.op_call(ctx, &mut reader, mc), Call => op_call(self, ctx, &mut reader, mc),
DispatchPrimOp => self.op_dispatch_primop(ctx, &mut reader, mc), DispatchCont => op_dispatch_cont(self, ctx, &mut reader, mc),
Return => self.op_return(ctx, &mut reader, mc), Return => op_return(self, ctx, &mut reader, mc),
MakeAttrs => self.op_make_attrs(ctx, &mut reader, mc), MakeAttrs => op_make_attrs(self, ctx, &mut reader, mc),
MakeEmptyAttrs => self.op_make_empty_attrs(), MakeEmptyAttrs => op_make_empty_attrs(self),
SelectStatic => self.op_select_static(ctx, &mut reader, mc), SelectStatic => op_select_static(self, ctx, &mut reader, mc),
SelectDynamic => self.op_select_dynamic(ctx, &mut reader, mc), SelectDynamic => op_select_dynamic(self, ctx, &mut reader, mc),
HasAttrPathStatic => self.op_has_attr_path_static(ctx, &mut reader, mc), HasAttrPathStatic => op_has_attr_path_static(self, ctx, &mut reader, mc),
HasAttrPathDynamic => self.op_has_attr_path_dynamic(ctx, &mut reader, mc), HasAttrPathDynamic => op_has_attr_path_dynamic(self, ctx, &mut reader, mc),
HasAttrStatic => self.op_has_attr_static(&mut reader, mc), HasAttrStatic => op_has_attr_static(self, &mut reader, mc),
HasAttrDynamic => self.op_has_attr_dynamic(ctx, &mut reader, mc), HasAttrDynamic => op_has_attr_dynamic(self, ctx, &mut reader, mc),
HasAttrResolve => self.op_has_attr_resolve(), HasAttrResolve => op_has_attr_resolve(self),
JumpIfSelectFailed => self.op_jump_if_select_failed(&mut reader), JumpIfSelectFailed => op_jump_if_select_failed(self, &mut reader),
JumpIfSelectSucceeded => self.op_jump_if_select_succeeded(&mut reader), JumpIfSelectSucceeded => op_jump_if_select_succeeded(self, &mut reader),
MakeList => self.op_make_list(ctx, &mut reader, mc), MakeList => op_make_list(self, ctx, &mut reader, mc),
MakeEmptyList => self.op_make_empty_list(), MakeEmptyList => op_make_empty_list(self),
OpAdd => self.op_add(ctx, &mut reader, mc), OpAdd => op_add(self, ctx, &mut reader, mc),
OpSub => self.op_sub(&mut reader, mc), OpSub => op_sub(self, &mut reader, mc),
OpMul => self.op_mul(&mut reader, mc), OpMul => op_mul(self, &mut reader, mc),
OpDiv => self.op_div(&mut reader, mc), OpDiv => op_div(self, &mut reader, mc),
OpEq => self.op_eq(ctx, &mut reader, mc), OpEq => op_eq(self, ctx, &mut reader, mc),
OpNeq => self.op_neq(ctx, &mut reader, mc), OpNeq => op_neq(self, ctx, &mut reader, mc),
OpLt => self.op_lt(ctx, &mut reader, mc), OpLt => op_lt(self, ctx, &mut reader, mc),
OpGt => self.op_gt(ctx, &mut reader, mc), OpGt => op_gt(self, ctx, &mut reader, mc),
OpLeq => self.op_leq(ctx, &mut reader, mc), OpLeq => op_leq(self, ctx, &mut reader, mc),
OpGeq => self.op_geq(ctx, &mut reader, mc), OpGeq => op_geq(self, ctx, &mut reader, mc),
OpConcat => self.op_concat(&mut reader, mc), OpConcat => op_concat(self, &mut reader, mc),
OpUpdate => self.op_update(&mut reader, mc), OpUpdate => op_update(self, &mut reader, mc),
OpNeg => self.op_neg(&mut reader, mc), OpNeg => op_neg(self, &mut reader, mc),
OpNot => self.op_not(&mut reader, mc), OpNot => op_not(self, &mut reader, mc),
JumpIfFalse => self.op_jump_if_false(&mut reader, mc), JumpIfFalse => op_jump_if_false(self, &mut reader, mc),
JumpIfTrue => self.op_jump_if_true(&mut reader, mc), JumpIfTrue => op_jump_if_true(self, &mut reader, mc),
Jump => self.op_jump(&mut reader), Jump => op_jump(self, &mut reader),
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc), ConcatStrings => op_concat_strings(self, ctx, &mut reader, mc),
CoerceToString => self.op_coerce_to_string(&mut reader, mc), CoerceToString => op_coerce_to_string(self, &mut reader, mc),
ResolvePath => self.op_resolve_path(ctx, &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(), LoadBuiltins => op_load_builtins(self),
LoadBuiltin => self.op_load_builtin(&mut reader), LoadBuiltin => op_load_builtin(self, &mut reader),
LoadReplBinding => self.op_load_repl_binding(&mut reader), LoadReplBinding => op_load_repl_binding(self, &mut reader),
LoadScopedBinding => self.op_load_scoped_binding(ctx, &mut reader, mc), LoadScopedBinding => op_load_scoped_binding(self, ctx, &mut reader, mc),
Illegal => unreachable!(), Illegal => unreachable!(),
}; };
@@ -2,16 +2,16 @@
//! `builtins.unsafeDiscardStringContext`, //! `builtins.unsafeDiscardStringContext`,
//! `builtins.unsafeDiscardOutputDependency`. //! `builtins.unsafeDiscardOutputDependency`.
//! //!
//! See `fix-abstract-vm/src/string_context.rs` for the //! See `fix-runtime/src/string_context.rs` for the
//! `StringContextElem` type. //! `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, AttrSet, BytecodeReader, List as VmList, Machine, MachineExt, NixString, NixType, Step,
StrictValue, StringContext, StringContextElem, Value, VmRuntimeCtx, VmRuntimeCtxExt, StrictValue, StringContext, StringContextElem, Value, VmRuntimeCtx, VmRuntimeCtxExt,
}; };
use fix_builtins::PrimOpPhase;
use fix_common::StringId;
use fix_error::Error;
use gc_arena::{Gc, Mutation}; use gc_arena::{Gc, Mutation};
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -22,11 +22,11 @@ pub fn has_context<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?; 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()); return m.finish_type_err(NixType::String, val.ty());
} }
let has_ctx = !ctx.get_string_context(val).is_empty(); 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>>( 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>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?; let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if let Some(sid) = val.as_inline::<StringId>() { if let Some(sid) = val.downcast::<StringId>() {
return m.return_from_primop(Value::new_inline(sid), reader); 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()); return m.finish_type_err(NixType::String, val.ty());
}; };
let sid = ctx.intern_string(ns.as_str()); 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>>( 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>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?; let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if let Some(sid) = val.as_inline::<StringId>() { if let Some(sid) = val.downcast::<StringId>() {
return m.return_from_primop(Value::new_inline(sid), reader); 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()); return m.finish_type_err(NixType::String, val.ty());
}; };
if ns.context().is_empty() { if ns.context().is_empty() {
let sid = ctx.intern_string(ns.as_str()); 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(); 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 s: Box<str> = ns.as_str().into();
let new_ns = Gc::new(mc, NixString::with_context(s, new_ctx)); 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>>( pub fn get_context<'gc, M: Machine<'gc>>(
@@ -87,7 +87,7 @@ pub fn get_context<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?; 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()); return m.finish_type_err(NixType::String, val.ty());
} }
let elems = ctx.get_string_context(val); 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(); let mut sub: SmallVec<[(StringId, Value<'gc>); 4]> = SmallVec::new();
if info.all_outputs { 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() { if !info.outputs.is_empty() {
let items: smallvec::SmallVec<[Value<'gc>; 4]> = info let items: smallvec::SmallVec<[Value<'gc>; 4]> = info
.outputs .outputs
.iter() .iter()
.map(|o| Value::new_inline(ctx.intern_string(o))) .map(|o| Value::new(ctx.intern_string(o)))
.collect(); .collect();
let list = VmList::new(mc, items); 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 { 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); sub.sort_by_key(|(k, _)| *k);
let sub_attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(sub)); 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); outer_entries.sort_by_key(|(k, _)| *k);
let outer = Gc::new(mc, AttrSet::from_sorted_unchecked(outer_entries)); 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 /// 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)); let acc = Gc::new(mc, NixString::with_context("", initial_ctx));
m.push(str_val.relax()); m.push(str_val.relax());
m.push(Value::new_gc(attrs)); m.push(Value::new(attrs));
m.push(Value::new_inline(0i32)); m.push(Value::new(0i32));
m.push(Value::new_gc(acc)); m.push(Value::new(acc));
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize); reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -207,9 +207,9 @@ pub fn append_context_loop<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
#[allow(clippy::unwrap_used)] #[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)] #[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() { if idx as usize >= attrs.entries.len() {
return append_context_finalize(m, ctx, reader, mc); return append_context_finalize(m, ctx, reader, mc);
@@ -221,9 +221,9 @@ pub fn append_context_loop<'gc, M: Machine<'gc>>(
0, 0,
reader, reader,
mc, 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(()) Step::Continue(())
} }
@@ -238,14 +238,14 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
// Evaluated value into the slot. // Evaluated value into the slot.
m.force_slot(0, reader, mc)?; m.force_slot(0, reader, mc)?;
let entry_val = m.peek_forced(0); 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()); return m.finish_type_err(NixType::AttrSet, entry_val.ty());
}; };
#[allow(clippy::unwrap_used)] #[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)] #[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_key = outer.entries[idx as usize].0;
let path_str_owned: Box<str> = ctx.resolve_string(path_key).into(); let path_str_owned: Box<str> = ctx.resolve_string(path_key).into();
if !path_str_owned.starts_with("/nix/store/") { 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"); let outputs_id = ctx.intern_string("outputs");
#[allow(clippy::unwrap_used)] #[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(); let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
if let Some(v) = entry_attrs.lookup(path_id) 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 { new_acc.insert(StringContextElem::Opaque {
path: path_str_owned.clone(), 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) 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") { if !path_str_owned.ends_with(".drv") {
return m.finish_err(Error::eval_error(format!( 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)); 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) { if let Some(outputs_val) = entry_attrs.lookup(outputs_id) {
m.replace(0, outputs_val); m.replace(0, outputs_val);
@@ -296,17 +296,17 @@ pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
0, 0,
reader, reader,
mc, 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(()); return Step::Continue(());
} }
let _ = m.pop(); let _ = m.pop();
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let idx_back = m.peek(1).as_inline::<i32>().unwrap(); let idx_back = m.peek(1).downcast::<i32>().unwrap();
m.replace(1, Value::new_inline(idx_back + 1)); m.replace(1, Value::new(idx_back + 1));
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize); reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -318,21 +318,21 @@ pub fn append_context_outputs_forced<'gc, M: Machine<'gc>>(
) -> Step { ) -> Step {
m.force_slot(0, reader, mc)?; m.force_slot(0, reader, mc)?;
let list_val = m.peek_forced(0); 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()); return m.finish_type_err(NixType::List, list_val.ty());
}; };
if list.inner.borrow().is_empty() { if list.inner.borrow().is_empty() {
// Stack: [strVal, attrs, idx, acc, list] -> drop list, bump idx. // Stack: [strVal, attrs, idx, acc, list] -> drop list, bump idx.
let _ = m.pop(); let _ = m.pop();
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let idx_back = m.peek(1).as_inline::<i32>().unwrap(); let idx_back = m.peek(1).downcast::<i32>().unwrap();
m.replace(1, Value::new_inline(idx_back + 1)); m.replace(1, Value::new(idx_back + 1));
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize); reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
return Step::Continue(()); return Step::Continue(());
} }
m.push(Value::new_inline(0i32)); m.push(Value::new(0i32));
reader.set_pc(PrimOpPhase::AppendContextOutputElementLoop.ip() as usize); reader.set_pc(Continuation::PAppendContextOutputElementLoop.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -343,9 +343,9 @@ pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
#[allow(clippy::unwrap_used)] #[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)] #[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(); let len = list.inner.borrow().len();
if oidx as usize >= len { if oidx as usize >= len {
// Stack: [strVal, attrs, idx, acc, list, oidx] -> drop oidx & list, // 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();
let _ = m.pop(); let _ = m.pop();
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let idx_back = m.peek(1).as_inline::<i32>().unwrap(); let idx_back = m.peek(1).downcast::<i32>().unwrap();
m.replace(1, Value::new_inline(idx_back + 1)); m.replace(1, Value::new(idx_back + 1));
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize); reader.set_pc(Continuation::PAppendContextLoop.ip() as usize);
return Step::Continue(()); return Step::Continue(());
} }
@@ -365,9 +365,9 @@ pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
0, 0,
reader, reader,
mc, 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(()) 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(); let output_name: Box<str> = output_name.into();
#[allow(clippy::unwrap_used)] #[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)] #[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_key = outer.entries[idx as usize].0;
let path_str: Box<str> = ctx.resolve_string(path_key).into(); let path_str: Box<str> = ctx.resolve_string(path_key).into();
if !path_str.ends_with(".drv") { 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)] #[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(); let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
new_acc.insert(StringContextElem::Built { new_acc.insert(StringContextElem::Built {
drv_path: path_str, drv_path: path_str,
output: output_name, output: output_name,
}); });
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc)); 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, // Stack: [strVal, attrs, idx, acc, list, oidx, outElem] -> drop outElem,
// bump oidx in place. // bump oidx in place.
let _ = m.pop(); let _ = m.pop();
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let oidx = m.peek(0).as_inline::<i32>().unwrap(); let oidx = m.peek(0).downcast::<i32>().unwrap();
m.replace(0, Value::new_inline(oidx + 1)); m.replace(0, Value::new(oidx + 1));
reader.set_pc(PrimOpPhase::AppendContextOutputElementLoop.ip() as usize); reader.set_pc(Continuation::PAppendContextOutputElementLoop.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -424,7 +424,7 @@ fn append_context_finalize<'gc, M: Machine<'gc>>(
) -> Step { ) -> Step {
// Stack: [strVal, attrs, idx, acc] // Stack: [strVal, attrs, idx, acc]
#[allow(clippy::unwrap_used)] #[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(); // idx
let _ = m.pop(); // attrs let _ = m.pop(); // attrs
let str_val_raw = m.pop(); 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 context: StringContext = acc_gc.context().iter().cloned().collect();
let result = if context.is_empty() { let result = if context.is_empty() {
let sid = ctx.intern_string(s_str); let sid = ctx.intern_string(s_str);
Value::new_inline(sid) Value::new(sid)
} else { } else {
let ns = Gc::new(mc, NixString::with_context(s_str, context)); let ns = Gc::new(mc, NixString::with_context(s_str, context));
Value::new_gc(ns) Value::new(ns)
}; };
m.return_from_primop(result, reader) 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, AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Step, StrictValue, Value,
VmRuntimeCtx, VmRuntimeCtxExt, VmRuntimeCtx, VmRuntimeCtxExt,
}; };
use fix_builtins::PrimOpPhase;
use fix_error::Error;
use gc_arena::{Gc, Mutation, RefLock}; use gc_arena::{Gc, Mutation, RefLock};
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -44,14 +44,14 @@ pub fn deep_seq_force_top<'gc, M: Machine<'gc>>(
let e1 = m.peek_forced(1); 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; let attrs = &attrs.entries;
if attrs.is_empty() { if attrs.is_empty() {
SmallVec::new() SmallVec::new()
} else { } else {
attrs.iter().map(|&(_, v)| v).collect() 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(); let inner = list.inner.borrow();
if inner.is_empty() { if inner.is_empty() {
SmallVec::new() SmallVec::new()
@@ -69,16 +69,16 @@ pub fn deep_seq_force_top<'gc, M: Machine<'gc>>(
} }
let count = children.len() as i32; let count = children.len() as i32;
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default()); let seen = Gc::new(mc, List::default());
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children); let worklist = List::new(mc, children);
let e2 = m.pop(); let e2 = m.pop();
let _ = m.pop(); let _ = m.pop();
m.push(e2); m.push(e2);
m.push(Value::new_gc(seen)); m.push(Value::new(seen));
m.push(Value::new_gc(worklist)); m.push(Value::new(worklist));
m.push(Value::new_inline(count)); m.push(Value::new(count));
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize); reader.set_pc(Continuation::PDeepSeqPush.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -89,7 +89,7 @@ pub fn deep_seq_push<'gc, M: Machine<'gc>>(
) -> Step { ) -> Step {
// stack: [e2, seen, worklist, counter] // stack: [e2, seen, worklist, counter]
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let counter = m.peek(0).as_inline::<i32>().unwrap(); let counter = m.peek(0).downcast::<i32>().unwrap();
if counter == 0 { if counter == 0 {
let _ = m.pop(); // counter let _ = m.pop(); // counter
let _ = m.pop(); // worklist let _ = m.pop(); // worklist
@@ -99,15 +99,15 @@ pub fn deep_seq_push<'gc, M: Machine<'gc>>(
} }
#[allow(clippy::unwrap_used)] #[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)] #[allow(clippy::unwrap_used)]
let item = worklist.unlock(mc).borrow_mut().pop().unwrap(); 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); m.push(item);
// force item at TOS, resume at DeepSeqLoop after force // force item at TOS, resume at DeepSeqLoop after force
m.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?; m.force_slot_to_pc(0, reader, mc, Continuation::PDeepSeqLoop.ip() as usize)?;
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize); reader.set_pc(Continuation::PDeepSeqLoop.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -119,17 +119,17 @@ pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
// stack after pop: [e2, seen, worklist, counter] // stack after pop: [e2, seen, worklist, counter]
let item = m.pop(); let item = m.pop();
#[allow(clippy::unwrap_used)] #[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; let mut added: usize = 0;
if let Some(attrs) = item.as_gc::<AttrSet>() { if let Some(attrs) = item.downcast::<AttrSet>() {
let attrs = &attrs.entries; let attrs = &attrs.entries;
#[allow(clippy::unwrap_used)] #[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) { if !is_value_in_seen(seen, item) {
add_value_to_seen(seen, mc, item); add_value_to_seen(seen, mc, item);
#[allow(clippy::unwrap_used)] #[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(); let mut wl = worklist.unlock(mc).borrow_mut();
for &(_, v) in attrs.iter() { for &(_, v) in attrs.iter() {
@@ -138,13 +138,13 @@ pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
added = attrs.len(); 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)] #[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) { if !is_value_in_seen(seen, item) {
add_value_to_seen(seen, mc, item); add_value_to_seen(seen, mc, item);
#[allow(clippy::unwrap_used)] #[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 inner = list.inner.borrow();
let mut wl = worklist.unlock(mc).borrow_mut(); 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)); m.replace(0, Value::new(counter + added as i32));
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize); reader.set_pc(Continuation::PDeepSeqPush.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -170,10 +170,10 @@ pub fn force_result_shallow<'gc, M: Machine<'gc>>(
m.force_slot(0, reader, mc)?; m.force_slot(0, reader, mc)?;
let val = m.peek_forced(0); 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(); let len = attrs.entries.len();
(len, len > 0) (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(); let len = list.inner.borrow().len();
(len, len > 0) (len, len > 0)
} else { } else {
@@ -185,9 +185,9 @@ pub fn force_result_shallow<'gc, M: Machine<'gc>>(
return m.finish_ok(ctx.convert_value(val)); return m.finish_ok(ctx.convert_value(val));
} }
m.push(Value::new_inline(0i32)); m.push(Value::new(0i32));
m.push(Value::new_inline(count as i32)); m.push(Value::new(count as i32));
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize); reader.set_pc(Continuation::ForceResultShallowPush.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -198,9 +198,9 @@ pub fn force_result_shallow_push<'gc, M: Machine<'gc>>(
mc: &Mutation<'gc>, mc: &Mutation<'gc>,
) -> Step { ) -> Step {
#[allow(clippy::unwrap_used)] #[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)] #[allow(clippy::unwrap_used)]
let len = m.peek(0).as_inline::<i32>().unwrap(); let len = m.peek(0).downcast::<i32>().unwrap();
if idx == len { if idx == len {
let _ = m.pop(); // 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 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) 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() list.inner.borrow().get(idx as usize).copied()
} else { } else {
None None
}; };
if let Some(child) = child { if let Some(child) = child {
m.replace(1, Value::new_inline(idx + 1)); m.replace(1, Value::new(idx + 1));
m.push(child); m.push(child);
m.force_slot_to_pc( m.force_slot_to_pc(
0, 0,
reader, reader,
mc, 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(()) Step::Continue(())
} }
@@ -238,7 +238,7 @@ pub fn force_result_shallow_loop<'gc, M: Machine<'gc>>(
_mc: &Mutation<'gc>, _mc: &Mutation<'gc>,
) -> Step { ) -> Step {
let _ = m.pop(); // forced child let _ = m.pop(); // forced child
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize); reader.set_pc(Continuation::ForceResultShallowPush.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -288,7 +288,7 @@ pub fn call_functor_1<'gc, M: Machine<'gc>>(
reader, reader,
mc, mc,
self_val, 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( let new_env = Gc::new(
mc, 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); reader.set_pc(ip as usize);
m.set_env(new_env); 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, BytecodeReader, Machine, MachineExt, NixString, NixType, Path, Step, StrictValue, Value,
VmRuntimeCtx, VmRuntimeCtx,
}; };
use fix_common::StringId;
use fix_error::Error;
use gc_arena::Mutation; use gc_arena::Mutation;
pub fn to_string<'gc, M: Machine<'gc>>( 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>() { if val.is::<StringId>() || val.is::<NixString>() {
return m.return_from_primop(val.relax(), reader); return m.return_from_primop(val.relax(), reader);
} }
if let Some(p) = val.as_inline::<Path>() { if let Some(p) = val.downcast::<Path>() {
return m.return_from_primop(Value::new_inline(p.0), reader); return m.return_from_primop(Value::new(p.0), reader);
} }
// TODO: derivations / `__toString` / `outPath`, // TODO: derivations / `__toString` / `outPath`,
// numbers, lists. // numbers, lists.
@@ -47,5 +47,5 @@ pub fn type_of<'gc, M: Machine<'gc>>(
NixType::Thunk => unreachable!("forced"), NixType::Thunk => unreachable!("forced"),
}; };
let sid = ctx.intern_string(name); 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, AttrSet, BytecodeReader, CallFrame, List, Machine, MachineExt, NixNum, Null, Path, Step,
StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
}; };
use fix_builtins::PrimOpPhase;
use gc_arena::{Gc, Mutation}; use gc_arena::{Gc, Mutation};
use smallvec::SmallVec; use smallvec::SmallVec;
@@ -17,11 +17,11 @@ pub fn start_eq<'gc, M: Machine<'gc>>(
) -> Step { ) -> Step {
match shallow_eq(ctx, lhs, rhs) { match shallow_eq(ctx, lhs, rhs) {
ShallowEq::True => { ShallowEq::True => {
m.push(Value::new_inline(!negate)); m.push(Value::new(!negate));
Step::Continue(()) Step::Continue(())
} }
ShallowEq::False => { ShallowEq::False => {
m.push(Value::new_inline(negate)); m.push(Value::new(negate));
Step::Continue(()) Step::Continue(())
} }
ShallowEq::RecurseList(la, lb) => { 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) enter_eq_machine(m, reader, mc, negate, lhs_init, rhs_init)
} }
ShallowEq::RecurseAttrs(a, b) => { ShallowEq::RecurseAttrs(a, b) => {
let lhs_init: SmallVec<[Value<'gc>; 4]> = let lhs_init: SmallVec<[Value<'gc>; 4]> = a.entries.iter().map(|&(_, v)| v).collect();
a.entries.iter().map(|&(_, v)| v).collect(); let rhs_init: SmallVec<[Value<'gc>; 4]> = b.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) enter_eq_machine(m, reader, mc, negate, lhs_init, rhs_init)
} }
} }
@@ -46,15 +44,15 @@ pub fn eq_step<'gc, M: Machine<'gc>>(
) -> Step { ) -> Step {
let rhs_q = m let rhs_q = m
.peek(0) .peek(0)
.as_gc::<List<'gc>>() .downcast::<List>()
.expect("eq state corrupted: rhs_queue"); .expect("eq state corrupted: rhs_queue");
let lhs_q = m let lhs_q = m
.peek(1) .peek(1)
.as_gc::<List<'gc>>() .downcast::<List>()
.expect("eq state corrupted: lhs_queue"); .expect("eq state corrupted: lhs_queue");
let result = m let result = m
.peek(2) .peek(2)
.as_inline::<bool>() .downcast::<bool>()
.expect("eq state corrupted: result"); .expect("eq state corrupted: result");
if !result || lhs_q.inner.borrow().is_empty() { 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"); .expect("non-empty rhs queue");
m.push(lhs); m.push(lhs);
m.push(rhs); m.push(rhs);
reader.set_pc(PrimOpPhase::EqForce.ip() as usize); reader.set_pc(Continuation::EqForce.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -85,7 +83,7 @@ pub fn eq_force<'gc, M: Machine<'gc>>(
) -> Step { ) -> Step {
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?; let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
apply_pair(m, ctx, mc, lhs, rhs); 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(()) Step::Continue(())
} }
@@ -94,13 +92,13 @@ fn finalize<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) ->
let _ = m.pop(); let _ = m.pop();
let result = m let result = m
.pop() .pop()
.as_inline::<bool>() .downcast::<bool>()
.expect("eq state corrupted: result"); .expect("eq state corrupted: result");
let negate = m let negate = m
.pop() .pop()
.as_inline::<bool>() .downcast::<bool>()
.expect("eq state corrupted: negate"); .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>>( fn apply_pair<'gc, M: Machine<'gc>>(
@@ -113,10 +111,15 @@ fn apply_pair<'gc, M: Machine<'gc>>(
match shallow_eq(ctx, lhs, rhs) { match shallow_eq(ctx, lhs, rhs) {
ShallowEq::True => {} ShallowEq::True => {}
ShallowEq::False => { ShallowEq::False => {
m.replace(2, Value::new_inline(false)); m.replace(2, Value::new(false));
} }
ShallowEq::RecurseList(la, lb) => { 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) => { ShallowEq::RecurseAttrs(a, b) => {
extend_queues( extend_queues(
@@ -137,11 +140,11 @@ where
{ {
let rhs_q = m let rhs_q = m
.peek(0) .peek(0)
.as_gc::<List<'gc>>() .downcast::<List>()
.expect("eq state corrupted: rhs_queue"); .expect("eq state corrupted: rhs_queue");
let lhs_q = m let lhs_q = m
.peek(1) .peek(1)
.as_gc::<List<'gc>>() .downcast::<List>()
.expect("eq state corrupted: lhs_queue"); .expect("eq state corrupted: lhs_queue");
let mut lq = lhs_q.unlock(mc).borrow_mut(); let mut lq = lhs_q.unlock(mc).borrow_mut();
let mut rq = rhs_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(), env: m.env(),
}); });
m.inc_call_depth(); m.inc_call_depth();
m.push(Value::new_inline(negate)); m.push(Value::new(negate));
m.push(Value::new_inline(true)); m.push(Value::new(true));
m.push(Value::new_gc(List::new(mc, lhs_init))); m.push(Value::new(List::new(mc, lhs_init)));
m.push(Value::new_gc(List::new(mc, rhs_init))); m.push(Value::new(List::new(mc, rhs_init)));
reader.set_pc(PrimOpPhase::EqStep.ip() as usize); reader.set_pc(Continuation::EqStep.ip() as usize);
Step::Continue(()) Step::Continue(())
} }
@@ -186,7 +189,7 @@ fn shallow_eq<'gc>(
lhs: StrictValue<'gc>, lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>, rhs: StrictValue<'gc>,
) -> ShallowEq<'gc> { ) -> ShallowEq<'gc> {
if let (Some(a), Some(b)) = (lhs.as_num(), rhs.as_num()) { if let (Some(a), Some(b)) = (lhs.downcast_num(), rhs.downcast_num()) {
let eq = match (a, b) { let eq = match (a, b) {
(NixNum::Int(a), NixNum::Int(b)) => a == b, (NixNum::Int(a), NixNum::Int(b)) => a == b,
(NixNum::Float(a), NixNum::Float(b)) => a == b, (NixNum::Float(a), NixNum::Float(b)) => a == b,
@@ -195,25 +198,28 @@ fn shallow_eq<'gc>(
}; };
return bool_outcome(eq); 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); return bool_outcome(a == b);
} }
if lhs.is::<Null>() && rhs.is::<Null>() { if lhs.is::<Null>() && rhs.is::<Null>() {
return ShallowEq::True; 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); return bool_outcome(a.0 == b.0);
} }
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
return bool_outcome(a == b); 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() { if a.inner.borrow().len() != b.inner.borrow().len() {
return ShallowEq::False; return ShallowEq::False;
} }
return ShallowEq::RecurseList(a, b); 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 ae = &a.entries;
let be = &b.entries; let be = &b.entries;
if ae.len() != be.len() { if ae.len() != be.len() {
@@ -230,9 +236,5 @@ fn shallow_eq<'gc>(
} }
fn bool_outcome<'gc>(b: bool) -> ShallowEq<'gc> { fn bool_outcome<'gc>(b: bool) -> ShallowEq<'gc> {
if b { if b { ShallowEq::True } else { ShallowEq::False }
ShallowEq::True
} else {
ShallowEq::False
}
} }
@@ -1,12 +1,12 @@
use std::path::PathBuf; 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, AttrSet, Break, BytecodeReader, CallFrame, Machine, MachineExt, Path, PendingLoad,
PendingScope, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, canon_path_str, 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 gc_arena::{Gc, Mutation};
use hashbrown::HashSet; 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 // finalizer can use it as the cache key. The slot we pop here was
// freed by `force_and_retry`, so we simply push. // freed by `force_and_retry`, so we simply push.
let path_sid = ctx.intern_string(abs.to_string_lossy()); 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(); let env = m.env();
m.push_call_frame(CallFrame { m.push_call_frame(CallFrame {
pc: PrimOpPhase::ImportFinalize.ip() as usize, pc: Continuation::PImportFinalize.ip() as usize,
thunk: None, thunk: None,
env, env,
}); });
@@ -63,7 +63,7 @@ pub fn import_finalize<'gc, M: Machine<'gc>>(
// stack: [path_sid, return_value] // stack: [path_sid, return_value]
let val = m.pop(); let val = m.pop();
#[allow(clippy::unwrap_used)] #[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 // The cache key is keyed by the absolute path string we interned in
// `import`. Resolve it back to the host PathBuf. // `import`. Resolve it back to the host PathBuf.
let path_str = ctx.resolve_string(path_sid).to_owned(); 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 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(); let env = m.env();
m.push_call_frame(CallFrame { m.push_call_frame(CallFrame {
pc: PrimOpPhase::ScopedImportFinalize.ip() as usize, pc: Continuation::PScopedImportFinalize.ip() as usize,
thunk: None, thunk: None,
env, env,
}); });
@@ -146,7 +146,7 @@ pub fn path_exists<'gc, M: Machine<'gc>>(
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?; let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
// pathExists requires an absolute path. A `Path` value is // pathExists requires an absolute path. A `Path` value is
// always absolute; a string is accepted only if it starts with `/`. // 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) (ctx.resolve_string(p.0).to_owned(), true)
} else if let Some(s) = ctx.get_string(path_val) { } else if let Some(s) = ctx.get_string(path_val) {
(s.to_owned(), false) (s.to_owned(), false)
@@ -171,7 +171,7 @@ pub fn path_exists<'gc, M: Machine<'gc>>(
} else { } else {
std::fs::symlink_metadata(p).is_ok() 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 /// 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, BytecodeReader, Machine, MachineExt, Path, Step, StrictValue, Value, VmRuntimeCtx,
VmRuntimeCtxExt, canon_path_str, VmRuntimeCtxExt, canon_path_str,
}; };
use fix_error::Error;
use gc_arena::Mutation; use gc_arena::Mutation;
pub fn to_path<'gc, M: Machine<'gc>>( pub fn to_path<'gc, M: Machine<'gc>>(
@@ -13,8 +13,8 @@ pub fn to_path<'gc, M: Machine<'gc>>(
) -> Step { ) -> Step {
// coerce to path THEN TO STRING // coerce to path THEN TO STRING
let val = m.force_and_retry::<StrictValue>(reader, mc)?; let val = m.force_and_retry::<StrictValue>(reader, mc)?;
if let Some(Path(s)) = val.as_inline::<Path>() { if let Some(Path(s)) = val.downcast::<Path>() {
return m.return_from_primop(Value::new_inline(s), reader); return m.return_from_primop(Value::new(s), reader);
} }
let Some(s) = ctx.get_string(val) else { let Some(s) = ctx.get_string(val) else {
return m.finish_err(Error::eval_error(format!( 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 canon = canon_path_str(s);
let sid = ctx.intern_string(canon); 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>>( pub fn is_path<'gc, M: Machine<'gc>>(
@@ -39,5 +39,5 @@ pub fn is_path<'gc, M: Machine<'gc>>(
) -> Step { ) -> Step {
let val = m.force_and_retry::<StrictValue>(reader, mc)?; let val = m.force_and_retry::<StrictValue>(reader, mc)?;
let is_path = val.is::<Path>(); 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" version = "0.1.0"
edition = "2024" edition = "2024"
[[bench]]
harness = false
name = "basic_ops"
[[bench]]
harness = false
name = "builtins"
[[bench]]
harness = false
name = "thunk_scope"
[dependencies] [dependencies]
mimalloc = "0.1" mimalloc = "0.1"
@@ -17,43 +29,24 @@ clap = { version = "4", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
miette = { version = "7.4", features = ["fancy"] }
# Error Reporting # Error Reporting
thiserror = "2" thiserror = "2"
miette = { version = "7.4", features = ["fancy"] }
# Data Structure # Data Structure
hashbrown = { workspace = true } hashbrown = { workspace = true }
string-interner = { workspace = true } string-interner = { workspace = true }
# Memory Management
bumpalo = { workspace = true }
rnix = { workspace = true }
ere = { workspace = true } ere = { workspace = true }
ghost-cell = { workspace = true }
fix-abstract-vm = { path = "../fix-abstract-vm" } fix-bytecode = { path = "../fix-bytecode" }
fix-builtins = { path = "../fix-builtins" } fix-compiler = { path = "../fix-compiler" }
fix-common = { path = "../fix-common" }
fix-codegen = { path = "../fix-codegen" }
fix-error = { path = "../fix-error" } 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" } fix-vm = { path = "../fix-vm" }
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] } criterion = { version = "0.8", features = ["html_reports"] }
tempfile = "3.24" tempfile = "3.24"
test-log = { version = "0.2", features = ["trace"] } 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)] #![allow(dead_code)]
use fix::Evaluator; use fix::Evaluator;
use fix_common::Value;
use fix_error::{Result, Source}; use fix_error::{Result, Source};
use fix_lang::Value;
pub fn eval(expr: &str) -> Value { pub fn eval(expr: &str) -> Value {
Evaluator::new() Evaluator::new()
+18 -589
View File
@@ -1,20 +1,13 @@
#![warn(clippy::unwrap_used)] #![warn(clippy::unwrap_used)]
#![allow(dead_code)] #![allow(dead_code)]
use bumpalo::Bump; use fix_bytecode::InstructionPtr;
use fix_abstract_vm::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx}; use fix_bytecode::disassembler::{Disassembler, DisassemblerContext};
use fix_builtins::PrimOpPhase; use fix_compiler::{CodeState, ExtraScope};
use fix_codegen::disassembler::{Disassembler, DisassemblerContext}; use fix_error::{Result, Source};
use fix_codegen::{BytecodeContext, InstructionPtr, Op}; use fix_lang::StringId;
use fix_common::{StringId, Symbol}; use fix_runtime::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx};
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_vm::Vm; use fix_vm::Vm;
use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use string_interner::{DefaultStringInterner, Symbol as _}; use string_interner::{DefaultStringInterner, Symbol as _};
@@ -29,15 +22,6 @@ pub struct RuntimeState {
pub constants: Constants, 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 struct Evaluator {
pub runtime: RuntimeState, pub runtime: RuntimeState,
pub code: CodeState, pub code: CodeState,
@@ -52,37 +36,25 @@ impl Default for Evaluator {
impl Evaluator { impl Evaluator {
pub fn new() -> Self { pub fn new() -> Self {
let mut strings = DefaultStringInterner::new(); let mut strings = DefaultStringInterner::new();
let global_env = fix_ir::new_global_env(&mut strings); let code = CodeState::new(&mut strings);
let mut bytecode = Vec::with_capacity(PrimOpPhase::Illegal as usize * 2);
for phase in 0..=PrimOpPhase::Illegal as u8 {
bytecode.push(Op::DispatchPrimOp as u8);
bytecode.push(phase);
}
Self { Self {
runtime: RuntimeState { runtime: RuntimeState {
strings, strings,
constants: Constants::default(), constants: Constants::default(),
}, },
code: CodeState { code,
sources: Vec::new(),
spans: Vec::new(),
thunk_count: 0,
bytecode,
global_env,
current_scope_slot: None,
},
} }
} }
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) 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) 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) self.do_eval(source, None, ForceMode::Deep)
} }
@@ -90,7 +62,7 @@ impl Evaluator {
&mut self, &mut self,
source: Source, source: Source,
scope: &HashSet<StringId>, scope: &HashSet<StringId>,
) -> Result<fix_common::Value> { ) -> Result<fix_lang::Value> {
self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow) self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow)
} }
@@ -99,14 +71,10 @@ impl Evaluator {
source: Source, source: Source,
extra_scope: Option<ExtraScope<'ctx>>, extra_scope: Option<ExtraScope<'ctx>>,
force_mode: ForceMode, force_mode: ForceMode,
) -> Result<fix_common::Value> { ) -> Result<fix_lang::Value> {
let ip = { let ip = self
let mut compiler = CompilerCtx { .code
code: &mut self.code, .compile_bytecode(source, extra_scope, &mut self.runtime)?;
runtime: &mut self.runtime,
};
compiler.compile_bytecode(source, extra_scope)?
};
Vm::run(self, ip, force_mode) Vm::run(self, ip, force_mode)
} }
@@ -115,16 +83,12 @@ impl Evaluator {
_ident: &str, _ident: &str,
_expr: &str, _expr: &str,
_scope: &mut HashSet<StringId>, _scope: &mut HashSet<StringId>,
) -> Result<fix_common::Value> { ) -> Result<fix_lang::Value> {
todo!("add_binding") todo!("add_binding")
} }
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> { pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
let mut compiler = CompilerCtx { self.code.compile_bytecode(source, None, &mut self.runtime)
code: &mut self.code,
runtime: &mut self.runtime,
};
compiler.compile_bytecode(source, None)
} }
pub fn disassemble_colored(&self, ip: InstructionPtr) -> String { 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 { impl VmContext for Evaluator {
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx) { fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx) {
(&mut self.code, &mut self.runtime) (&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)] #[derive(Default)]
pub struct Constants { pub struct Constants {
data: Vec<StaticValue>, 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 { impl DisassemblerContext for Evaluator {
fn get_code(&self) -> &[u8] { fn get_code(&self) -> &[u8] {
&self.code.bytecode &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}; use crate::utils::{eval_deep, eval_deep_result};
+1 -1
View File
@@ -1,6 +1,6 @@
use fix::Evaluator; use fix::Evaluator;
use fix_common::Value;
use fix_error::Source; use fix_error::Source;
use fix_lang::Value;
use crate::utils::{eval, eval_result}; use crate::utils::{eval, eval_result};
+1 -1
View File
@@ -3,8 +3,8 @@
use std::path::PathBuf; use std::path::PathBuf;
use fix::Evaluator; use fix::Evaluator;
use fix_common::Value;
use fix_error::{Source, SourceType}; use fix_error::{Source, SourceType};
use fix_lang::Value;
fn get_lang_dir() -> PathBuf { fn get_lang_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang") PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
+1 -1
View File
@@ -1,5 +1,5 @@
use fix::Evaluator; use fix::Evaluator;
use fix_common::Value; use fix_lang::Value;
use crate::utils::eval_result; use crate::utils::eval_result;
+1 -1
View File
@@ -1,8 +1,8 @@
#![allow(dead_code)] #![allow(dead_code)]
use fix::Evaluator; use fix::Evaluator;
use fix_common::Value;
use fix_error::{Result, Source}; use fix_error::{Result, Source};
use fix_lang::Value;
pub fn eval(expr: &str) -> Value { pub fn eval(expr: &str) -> Value {
Evaluator::new() Evaluator::new()
Generated
+17 -18
View File
@@ -45,16 +45,15 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1778445566, "lastModified": 1778446047,
"narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=", "narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "bun2nix", "repo": "bun2nix",
"rev": "2499dedd70744dba1815875b854818a3019e9e4c", "rev": "f2bc12af1a6369648aac41041ceeaa0b866599c6",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "owner": "nix-community",
"ref": "staging-2.1.0",
"repo": "bun2nix", "repo": "bun2nix",
"type": "github" "type": "github"
} }
@@ -67,11 +66,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1778919578, "lastModified": 1781343250,
"narHash": "sha256-+z+jgTly48gsAiX8rOe/vs8C/2G4vdCpcEtqMJUpFqw=", "narHash": "sha256-KBJktAwDG9+10j2wMfvOVkBEhZr3yS769xoqqdFI62s=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "ecd6d4ff22cfdb1339b2915455a2ff4dc85bf52e", "rev": "aad7d8bb6936d473c2b9d1a5846a1fe1bc92767a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -127,11 +126,11 @@
"treefmt-nix": "treefmt-nix" "treefmt-nix": "treefmt-nix"
}, },
"locked": { "locked": {
"lastModified": 1778929997, "lastModified": 1781330261,
"narHash": "sha256-iAfbBUHBbR0N4DFqFWr4Jtmpc1YcOK7kpVM4f0MK1V8=", "narHash": "sha256-2fFAGel2VVXr5mwrTXldqXva2ng3T3HHxyuBKRIxauI=",
"owner": "numtide", "owner": "numtide",
"repo": "llm-agents.nix", "repo": "llm-agents.nix",
"rev": "0da8f0313c9f68c155e0932f880fc1913e7be846", "rev": "24ec6b7b1ddf8896ac8df3b65dc564575e0a1928",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -142,11 +141,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1778443072, "lastModified": 1781074563,
"narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", "narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", "rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -167,11 +166,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1778854817, "lastModified": 1781294997,
"narHash": "sha256-iG+VuMy8W585geVVCUd7pR025WsY3ZkgSv5Yt5bxDmQ=", "narHash": "sha256-XjCyIvJw4JtcwItTKRdQz5h1pLF9hr8ZSYeMP+/1d3A=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "1a68212c5683555ad80f0eab71db9715c6d52145", "rev": "3f92cd1612268995d5667bd04fa03ba2916413d9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -204,11 +203,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1775636079, "lastModified": 1780220602,
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=", "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba", "rev": "db947814a175b7ca6ded66e21383d938df01c227",
"type": "github" "type": "github"
}, },
"original": { "original": {
+3 -2
View File
@@ -47,11 +47,12 @@
just just
samply samply
tokei tokei
tombi
llm-agents.codex # llm-agents.codex
llm-agents.claude-code llm-agents.claude-code
llm-agents.opencode llm-agents.opencode
llm-agents.forge # llm-agents.forge
]; ];
}; };
} }