Compare commits
6 Commits
main
..
0ca5e8af92
| Author | SHA1 | Date | |
|---|---|---|---|
|
0ca5e8af92
|
|||
|
260bea9ff1
|
|||
|
c85423ae79
|
|||
|
bc16596dd3
|
|||
|
ac76d4a9e4
|
|||
|
d77dcc8929
|
Generated
-183
@@ -449,7 +449,6 @@ dependencies = [
|
||||
"clap",
|
||||
"criterion",
|
||||
"ere",
|
||||
"fix-abstract-vm",
|
||||
"fix-builtins",
|
||||
"fix-codegen",
|
||||
"fix-common",
|
||||
@@ -470,23 +469,6 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fix-abstract-vm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fix-builtins",
|
||||
"fix-codegen",
|
||||
"fix-common",
|
||||
"fix-error",
|
||||
"gc-arena",
|
||||
"hashbrown 0.16.1",
|
||||
"likely_stable",
|
||||
"num_enum",
|
||||
"smallvec",
|
||||
"sptr",
|
||||
"string-interner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fix-builtins"
|
||||
version = "0.1.0"
|
||||
@@ -543,32 +525,14 @@ dependencies = [
|
||||
"string-interner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fix-primops"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fix-abstract-vm",
|
||||
"fix-builtins",
|
||||
"fix-codegen",
|
||||
"fix-common",
|
||||
"fix-error",
|
||||
"gc-arena",
|
||||
"hashbrown 0.16.1",
|
||||
"num_enum",
|
||||
"smallvec",
|
||||
"string-interner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fix-vm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fix-abstract-vm",
|
||||
"fix-builtins",
|
||||
"fix-codegen",
|
||||
"fix-common",
|
||||
"fix-error",
|
||||
"fix-primops",
|
||||
"gc-arena",
|
||||
"hashbrown 0.16.1",
|
||||
"likely_stable",
|
||||
@@ -576,7 +540,6 @@ dependencies = [
|
||||
"smallvec",
|
||||
"sptr",
|
||||
"string-interner",
|
||||
"sysinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -878,15 +841,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
@@ -927,25 +881,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
@@ -1351,20 +1286,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.38.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
@@ -1758,107 +1679,12 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@@ -1868,15 +1694,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.1"
|
||||
|
||||
+1
-3
@@ -2,13 +2,11 @@
|
||||
resolver = "3"
|
||||
members = [
|
||||
"fix",
|
||||
"fix-abstract-vm",
|
||||
"fix-builtins",
|
||||
"fix-codegen",
|
||||
"fix-common",
|
||||
"fix-error",
|
||||
"fix-ir",
|
||||
"fix-primops",
|
||||
"fix-vm",
|
||||
]
|
||||
|
||||
@@ -29,7 +27,7 @@ bumpalo = { version = "3.20", features = [
|
||||
ghost-cell = "0.2"
|
||||
hashbrown = "0.16"
|
||||
num_enum = "0.7.5"
|
||||
smallvec = { version = "1.15", features = ["const_new", "const_generics"] }
|
||||
smallvec = "1.15"
|
||||
ere = "0.2"
|
||||
string-interner = "0.19"
|
||||
rnix = "0.14"
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"includes": ["**", "!!**/dist"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": true,
|
||||
"attributePosition": "auto",
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 110,
|
||||
"lineEnding": "lf"
|
||||
},
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useNamingConvention": {
|
||||
"level": "warn",
|
||||
"options": {
|
||||
"strictCase": false,
|
||||
"conventions": [
|
||||
{
|
||||
"selector": { "kind": "objectLiteralProperty" },
|
||||
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
|
||||
},
|
||||
{
|
||||
"selector": { "kind": "typeProperty" },
|
||||
"formats": ["camelCase", "snake_case"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"includes": ["**/global.d.ts"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useNamingConvention": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"arrowParentheses": "always",
|
||||
"bracketSameLine": false,
|
||||
"bracketSpacing": true,
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"semicolons": "always",
|
||||
"trailingCommas": "all"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
"trailingCommas": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "fix-abstract-vm"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
gc-arena = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
num_enum = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
string-interner = { workspace = true }
|
||||
likely_stable = { workspace = true }
|
||||
sptr = "0.3"
|
||||
|
||||
fix-builtins = { path = "../fix-builtins" }
|
||||
fix-codegen = { path = "../fix-codegen" }
|
||||
fix-common = { path = "../fix-common" }
|
||||
fix-error = { path = "../fix-error" }
|
||||
@@ -1,164 +0,0 @@
|
||||
use fix_codegen::InstructionPtr;
|
||||
use fix_common::StringId;
|
||||
use fix_error::Source;
|
||||
use hashbrown::HashSet;
|
||||
|
||||
use crate::{
|
||||
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp, StaticValue, StrictValue, StringContext, Thunk, ThunkState, Value
|
||||
};
|
||||
|
||||
pub trait VmContext {
|
||||
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx);
|
||||
}
|
||||
|
||||
pub trait VmRuntimeCtx {
|
||||
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId;
|
||||
fn resolve_string(&self, id: StringId) -> &str;
|
||||
fn get_const(&self, id: u32) -> StaticValue;
|
||||
fn add_const(&mut self, val: StaticValue) -> u32;
|
||||
}
|
||||
|
||||
pub trait VmCode {
|
||||
fn bytecode(&self) -> &[u8];
|
||||
fn compile_with_scope(
|
||||
&mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<ExtraScope>,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
) -> fix_error::Result<InstructionPtr>;
|
||||
}
|
||||
|
||||
pub trait VmRuntimeCtxExt: VmRuntimeCtx {
|
||||
fn get_string<'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>;
|
||||
fn get_string_id<'a, 'gc: 'a>(
|
||||
&'a mut self,
|
||||
val: StrictValue<'gc>,
|
||||
) -> std::result::Result<StringId, NixType>;
|
||||
/// Returns the string context attached to `val`, or `&[]` if `val` is
|
||||
/// either a non-string or a string without context.
|
||||
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext;
|
||||
fn convert_value(&self, val: Value) -> fix_common::Value;
|
||||
}
|
||||
|
||||
impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
||||
if let Some(sid) = val.as_inline::<StringId>() {
|
||||
Some(self.resolve_string(sid))
|
||||
} else {
|
||||
val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `get_string`, but also accepts `Path` values (returning their
|
||||
/// underlying canonical-path string). Use this in places where Nix
|
||||
/// would coerce a path to a string (string interpolation, file IO
|
||||
/// builtins, etc.).
|
||||
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
||||
if let Some(p) = val.as_inline::<Path>() {
|
||||
Some(self.resolve_string(p.0))
|
||||
} else {
|
||||
self.get_string(val)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_string_id<'a, 'gc: 'a>(
|
||||
&'a mut self,
|
||||
val: StrictValue<'gc>,
|
||||
) -> std::result::Result<StringId, NixType> {
|
||||
if let Some(sid) = val.as_inline::<StringId>() {
|
||||
Ok(sid)
|
||||
} else if let Some(s) = val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str()) {
|
||||
Ok(self.intern_string(s))
|
||||
} else {
|
||||
Err(val.ty())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext {
|
||||
if let Some(ns) = val.as_gc::<NixString>() {
|
||||
ns.as_ref().context()
|
||||
} else {
|
||||
StringContext::empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_value(&self, val: Value) -> fix_common::Value {
|
||||
self.convert_value_with_seen(val, &mut HashSet::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ConvertValueWithSeen: VmRuntimeCtx {
|
||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value;
|
||||
}
|
||||
|
||||
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value {
|
||||
use fix_common::Value;
|
||||
if let Some(i) = val.as_inline::<i32>() {
|
||||
Value::Int(i as i64)
|
||||
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
||||
Value::Int(*gc_i)
|
||||
} else if let Some(f) = val.as_float() {
|
||||
Value::Float(f)
|
||||
} else if let Some(b) = val.as_inline::<bool>() {
|
||||
Value::Bool(b)
|
||||
} else if val.is::<Null>() {
|
||||
Value::Null
|
||||
} else if let Some(sid) = val.as_inline::<StringId>() {
|
||||
let s = self.resolve_string(sid).to_owned();
|
||||
Value::String(s)
|
||||
} else if let Some(ns) = val.as_gc::<NixString>() {
|
||||
Value::String(ns.as_str().to_owned())
|
||||
} else if let Some(p) = val.as_inline::<Path>() {
|
||||
Value::Path(self.resolve_string(p.0).to_owned())
|
||||
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
let bits = val.to_bits();
|
||||
if attrs.entries.is_empty() {
|
||||
return Value::AttrSet(Default::default());
|
||||
}
|
||||
if !seen.insert(bits) {
|
||||
return Value::Repeated;
|
||||
}
|
||||
let mut map = std::collections::BTreeMap::new();
|
||||
for &(key, val) in attrs.entries.iter() {
|
||||
let key = self.resolve_string(key).to_owned();
|
||||
let converted = self.convert_value_with_seen(val, seen);
|
||||
map.insert(fix_common::Symbol::from(key), converted);
|
||||
}
|
||||
Value::AttrSet(fix_common::AttrSet::new(map))
|
||||
} else if let Some(list) = val.as_gc::<List>() {
|
||||
let bits = val.to_bits();
|
||||
if list.inner.borrow().is_empty() {
|
||||
return Value::List(Default::default());
|
||||
}
|
||||
if !seen.insert(bits) {
|
||||
return Value::Repeated;
|
||||
}
|
||||
let items: Vec<_> = list
|
||||
.inner
|
||||
.borrow()
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|v| self.convert_value_with_seen(v, seen))
|
||||
.collect();
|
||||
Value::List(fix_common::List::new(items))
|
||||
} else if val.is::<Closure>() {
|
||||
Value::Func
|
||||
} else if let Some(thunk) = val.as_gc::<Thunk>() {
|
||||
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
||||
self.convert_value_with_seen(v.relax(), seen)
|
||||
} else {
|
||||
Value::Thunk
|
||||
}
|
||||
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
||||
let name = fix_builtins::BUILTINS[primop.id as usize].0;
|
||||
Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
|
||||
} else if let Some(app) = val.as_gc::<PrimOpApp>() {
|
||||
let name = fix_builtins::BUILTINS[app.primop.id as usize].0;
|
||||
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
|
||||
} else {
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
mod boxing;
|
||||
mod bytecode_reader;
|
||||
mod forced;
|
||||
mod host;
|
||||
mod machine;
|
||||
mod path_util;
|
||||
mod resolve;
|
||||
mod state;
|
||||
mod string_context;
|
||||
mod value;
|
||||
|
||||
pub use bytecode_reader::*;
|
||||
pub use forced::*;
|
||||
pub use host::*;
|
||||
pub use machine::*;
|
||||
pub use path_util::*;
|
||||
pub use resolve::*;
|
||||
pub use state::*;
|
||||
pub use string_context::*;
|
||||
pub use value::*;
|
||||
@@ -1,174 +0,0 @@
|
||||
use std::ops::ControlFlow;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use gc_arena::Mutation;
|
||||
|
||||
use crate::{
|
||||
Break, BytecodeReader, CallFrame, ForceMode, Forced, GcEnv, NixType, PendingLoad, Step,
|
||||
StrictValue, Value, VmError,
|
||||
};
|
||||
|
||||
/// Abstract VM-side operations consumed by instruction handlers and primops.
|
||||
///
|
||||
/// Implementors maintain a value stack, a call stack, an environment chain,
|
||||
/// pending result/error state, and a set of GC-allocated globals. Methods
|
||||
/// fall into a few groups:
|
||||
///
|
||||
/// - Stack ops (`push` / `pop` / `peek` / `replace` / `pop_forced` / ...)
|
||||
/// - Forcing primitives (`force_slot` / `force_slot_to_pc`)
|
||||
/// - Calling (`call` / `return_from_primop`)
|
||||
/// - Call-frame management (`push_call_frame` / `pop_call_frame` / call-depth)
|
||||
/// - Environment access (`env` / `set_env` / `local`)
|
||||
/// - Result finalization (`finish_ok` / `finish_err` / ...)
|
||||
/// - Global lookup (`builtins` / `empty_list` / `empty_attrs` / ...)
|
||||
/// - Imports and scope slots (`import_cache_*` / `scope_slot*` / `set_pending_load`)
|
||||
pub trait Machine<'gc> {
|
||||
fn push(&mut self, val: Value<'gc>);
|
||||
fn pop(&mut self) -> Value<'gc>;
|
||||
fn peek(&self, depth: usize) -> Value<'gc>;
|
||||
fn peek_forced(&self, depth: usize) -> StrictValue<'gc>;
|
||||
fn pop_forced(&mut self) -> StrictValue<'gc>;
|
||||
fn replace(&mut self, depth: usize, val: Value<'gc>);
|
||||
fn stack_len(&self) -> usize;
|
||||
|
||||
fn force_slot_to_pc(
|
||||
&mut self,
|
||||
depth: usize,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
resume_pc: usize,
|
||||
) -> Step;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_slot(
|
||||
&mut self,
|
||||
depth: usize,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let pc = reader.inst_start_pc();
|
||||
self.force_slot_to_pc(depth, reader, mc, pc)
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
arg: Value<'gc>,
|
||||
resume_pc: usize,
|
||||
) -> Step;
|
||||
|
||||
#[inline(always)]
|
||||
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
self.push(val);
|
||||
let Some(CallFrame {
|
||||
pc: ret_pc,
|
||||
thunk: _,
|
||||
env,
|
||||
}) = self.pop_call_frame()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
reader.set_pc(ret_pc);
|
||||
self.dec_call_depth();
|
||||
self.set_env(env);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
fn push_call_frame(&mut self, frame: CallFrame<'gc>);
|
||||
fn pop_call_frame(&mut self) -> Option<CallFrame<'gc>>;
|
||||
fn call_depth(&self) -> usize;
|
||||
fn inc_call_depth(&mut self);
|
||||
fn dec_call_depth(&mut self);
|
||||
|
||||
fn env(&self) -> GcEnv<'gc>;
|
||||
fn set_env(&mut self, env: GcEnv<'gc>);
|
||||
|
||||
#[inline(always)]
|
||||
fn local(&self, layer: u8, idx: u32) -> Value<'gc> {
|
||||
let mut cur = self.env();
|
||||
for _ in 0..layer {
|
||||
let prev = cur.borrow().prev.expect("env chain too short");
|
||||
cur = prev;
|
||||
}
|
||||
cur.borrow().locals[idx as usize]
|
||||
}
|
||||
|
||||
fn finish_ok(&mut self, val: fix_common::Value) -> Step;
|
||||
fn finish_err(&mut self, err: Box<Error>) -> Step;
|
||||
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step;
|
||||
|
||||
#[inline(always)]
|
||||
fn finish_vm_err(&mut self, err: VmError) -> Step {
|
||||
self.finish_err(err.into_error())
|
||||
}
|
||||
|
||||
fn builtins(&self) -> Value<'gc>;
|
||||
fn functor_sym(&self) -> StringId;
|
||||
fn empty_list(&self) -> Value<'gc>;
|
||||
fn empty_attrs(&self) -> Value<'gc>;
|
||||
fn force_mode(&self) -> ForceMode;
|
||||
|
||||
fn import_cache_get(&self, path: &Path) -> Option<Value<'gc>>;
|
||||
fn import_cache_insert(&mut self, path: PathBuf, val: Value<'gc>);
|
||||
fn scope_slot(&self, idx: u32) -> Value<'gc>;
|
||||
fn scope_slots_push(&mut self, val: Value<'gc>) -> u32;
|
||||
fn set_pending_load(&mut self, load: PendingLoad);
|
||||
}
|
||||
|
||||
/// Extension trait with convenience helpers built on top of [`Machine`].
|
||||
///
|
||||
/// Auto-implemented for every `Machine<'gc>` so callers just need to bring
|
||||
/// `MachineExt` (or `Machine`) into scope.
|
||||
pub trait MachineExt<'gc>: Machine<'gc> {
|
||||
/// 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.
|
||||
/// * The stack layout at the call site must be **identical** every time
|
||||
/// the handler is re-entered.
|
||||
/// * Propagate the return value with `?` so `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>,
|
||||
) -> ControlFlow<Break, T>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let pc = reader.inst_start_pc();
|
||||
self.force_and_retry_pc(reader, mc, 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,
|
||||
) -> ControlFlow<Break, T>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
T::force_and_check(self, reader, mc, 0, resume_pc)?;
|
||||
ControlFlow::Continue(T::pop_converted(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc, M: Machine<'gc>> MachineExt<'gc> for M {}
|
||||
@@ -1,18 +0,0 @@
|
||||
use std::path::{Component, PathBuf};
|
||||
|
||||
pub fn canon_path_str(path: impl AsRef<std::path::Path>) -> String {
|
||||
let p = path.as_ref();
|
||||
let mut normalized = PathBuf::new();
|
||||
for component in p.components() {
|
||||
match component {
|
||||
Component::Prefix(p) => normalized.push(p.as_os_str()),
|
||||
Component::RootDir => normalized.push("/"),
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
normalized.pop();
|
||||
}
|
||||
Component::Normal(c) => normalized.push(c),
|
||||
}
|
||||
}
|
||||
normalized.to_string_lossy().into_owned()
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
use gc_arena::{Gc, Mutation};
|
||||
|
||||
use crate::{AttrSet, Machine, OperandData, Value};
|
||||
|
||||
/// Resolve a decoded operand into a runtime [`Value`].
|
||||
///
|
||||
/// The operand decoder ([`crate::BytecodeReader::read_operand_data`])
|
||||
/// produces a static enum; this function materializes it against the
|
||||
/// running [`Machine`] (env chain, builtins, scope slots, ...).
|
||||
#[inline]
|
||||
pub fn resolve_operand<'gc, M: Machine<'gc>>(
|
||||
op: &OperandData,
|
||||
mc: &Mutation<'gc>,
|
||||
m: &M,
|
||||
) -> Value<'gc> {
|
||||
use OperandData::*;
|
||||
match *op {
|
||||
Const(sv) => sv.into(),
|
||||
BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
||||
Local { layer, idx } => m.local(layer, idx),
|
||||
#[allow(clippy::unwrap_used)]
|
||||
BuiltinConst(id) => m.builtins().as_gc::<AttrSet>().unwrap().lookup(id).unwrap(),
|
||||
Builtins => m.builtins(),
|
||||
ReplBinding(_id) => todo!(),
|
||||
ScopedImportBinding { slot_id, name } => {
|
||||
let scope = m.scope_slot(slot_id);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let attrs = scope.as_gc::<AttrSet>().expect("scope must be attrset");
|
||||
#[allow(clippy::unwrap_used)]
|
||||
attrs.lookup(name).expect("scoped binding not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
use std::ops::ControlFlow;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Collect, Gc};
|
||||
use hashbrown::HashSet;
|
||||
|
||||
use crate::{GcEnv, StaticValue, Thunk};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum VmError {
|
||||
Catchable(String),
|
||||
Uncatchable(Box<Error>),
|
||||
}
|
||||
|
||||
impl From<Box<Error>> for VmError {
|
||||
fn from(e: Box<Error>) -> Self {
|
||||
VmError::Uncatchable(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl VmError {
|
||||
pub fn into_error(self) -> Box<Error> {
|
||||
match self {
|
||||
VmError::Catchable(_) => todo!("Check for tryEval catch frames"),
|
||||
VmError::Uncatchable(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vm_err(msg: impl Into<String>) -> VmError {
|
||||
VmError::Uncatchable(Error::eval_error(msg.into()))
|
||||
}
|
||||
|
||||
#[derive(Collect, Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
#[collect(require_static)]
|
||||
pub enum ForceMode {
|
||||
#[default]
|
||||
AsIs,
|
||||
Shallow,
|
||||
Deep,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum Break {
|
||||
Force,
|
||||
Done,
|
||||
LoadFile,
|
||||
}
|
||||
|
||||
pub type Step = ControlFlow<Break>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct ErrorFrame {
|
||||
pub span_id: u32,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub struct CallFrame<'gc> {
|
||||
pub pc: usize,
|
||||
pub thunk: Option<Gc<'gc, Thunk<'gc>>>,
|
||||
pub env: GcEnv<'gc>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PendingLoad {
|
||||
pub path: PathBuf,
|
||||
pub scope: Option<PendingScope>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PendingScope {
|
||||
pub keys: HashSet<StringId>,
|
||||
pub slot_id: u32,
|
||||
}
|
||||
|
||||
/// Extra scope passed to a re-entrant compile from inside a running VM.
|
||||
///
|
||||
/// Currently only `ScopedImport` is produced (by the `scopedImport` builtin),
|
||||
/// but the variant is kept open so REPL bindings could later land here too.
|
||||
pub enum ExtraScope {
|
||||
ScopedImport {
|
||||
keys: HashSet<StringId>,
|
||||
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 },
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A string context element
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum StringContextElem {
|
||||
// Plain store path reference
|
||||
Opaque {
|
||||
path: Box<str>,
|
||||
},
|
||||
// All outputs of a derivation
|
||||
// encoded `=<drvPath>`
|
||||
DrvDeep {
|
||||
drv_path: Box<str>,
|
||||
},
|
||||
// A specific output of a derivation
|
||||
// encoded `!<output>!<drvPath>`
|
||||
Built {
|
||||
drv_path: Box<str>,
|
||||
output: Box<str>,
|
||||
},
|
||||
}
|
||||
|
||||
impl StringContextElem {
|
||||
/// Decode the CppNix wire form (`!out!/p`, `=/p`, `/p`). Falls back to
|
||||
/// `Opaque` for malformed `!`-prefixed inputs (matching nix-js).
|
||||
pub fn decode(encoded: &str) -> Self {
|
||||
if let Some(drv_path) = encoded.strip_prefix('=') {
|
||||
Self::DrvDeep {
|
||||
drv_path: drv_path.into(),
|
||||
}
|
||||
} else if let Some(rest) = encoded.strip_prefix('!') {
|
||||
if let Some(second_bang) = rest.find('!') {
|
||||
Self::Built {
|
||||
output: rest[..second_bang].into(),
|
||||
drv_path: rest[second_bang + 1..].into(),
|
||||
}
|
||||
} else {
|
||||
Self::Opaque {
|
||||
path: encoded.into(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Self::Opaque {
|
||||
path: encoded.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> String {
|
||||
match self {
|
||||
Self::Opaque { path } => path.to_string(),
|
||||
Self::DrvDeep { drv_path } => format!("={drv_path}"),
|
||||
Self::Built { drv_path, output } => format!("!{output}!{drv_path}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StringContext {
|
||||
data: SmallVec<[StringContextElem; 1]>,
|
||||
}
|
||||
|
||||
impl IntoIterator for StringContext {
|
||||
type Item = StringContextElem;
|
||||
type IntoIter = <SmallVec<[StringContextElem; 1]> as IntoIterator>::IntoIter;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.data.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a StringContext {
|
||||
type Item = &'a StringContextElem;
|
||||
type IntoIter = <&'a SmallVec<[StringContextElem; 1]> as IntoIterator>::IntoIter;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.data.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut StringContext {
|
||||
type Item = &'a mut StringContextElem;
|
||||
type IntoIter = <&'a mut SmallVec<[StringContextElem; 1]> as IntoIterator>::IntoIter;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.data.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<StringContextElem> for StringContext {
|
||||
fn from_iter<T: IntoIterator<Item = StringContextElem>>(iter: T) -> Self {
|
||||
Self {
|
||||
data: iter.into_iter().collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StringContext {
|
||||
pub fn empty() -> &'static Self {
|
||||
static EMPTY: StringContext = StringContext {
|
||||
data: SmallVec::new_const(),
|
||||
};
|
||||
&EMPTY
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, elem: StringContextElem) {
|
||||
match self.data.binary_search(&elem) {
|
||||
Ok(_) => {}
|
||||
Err(pos) => self.data.insert(pos, elem),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(&self, other: &Self) -> Self {
|
||||
if self.data.is_empty() {
|
||||
return other.clone();
|
||||
}
|
||||
if other.data.is_empty() {
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
let a = &self.data;
|
||||
let b = &other.data;
|
||||
let mut out = SmallVec::with_capacity(a.len() + b.len());
|
||||
let (mut i, mut j) = (0, 0);
|
||||
while i < a.len() && j < b.len() {
|
||||
match a[i].cmp(&b[j]) {
|
||||
Ordering::Less => {
|
||||
out.push(a[i].clone());
|
||||
i += 1;
|
||||
}
|
||||
Ordering::Greater => {
|
||||
out.push(b[j].clone());
|
||||
j += 1;
|
||||
}
|
||||
Ordering::Equal => {
|
||||
out.push(a[i].clone());
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
out.extend(a[i..].iter().cloned());
|
||||
out.extend(b[j..].iter().cloned());
|
||||
Self { data: out }
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
|
||||
self.into_iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> <&mut Self as IntoIterator>::IntoIter {
|
||||
self.into_iter()
|
||||
}
|
||||
}
|
||||
+1
-34
@@ -118,14 +118,13 @@ define_builtins! {
|
||||
("__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)]
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, TryFromPrimitive)]
|
||||
pub enum PrimOpPhase {
|
||||
Abort,
|
||||
Add,
|
||||
@@ -172,10 +171,6 @@ pub enum PrimOpPhase {
|
||||
FindFile,
|
||||
Floor,
|
||||
FoldlStrict,
|
||||
FoldlStrictEmpty,
|
||||
FoldlStrictCall1,
|
||||
FoldlStrictCall2,
|
||||
FoldlStrictUpdate,
|
||||
FromJSON,
|
||||
FromTOML,
|
||||
FunctionArgs,
|
||||
@@ -247,39 +242,12 @@ pub enum PrimOpPhase {
|
||||
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 {
|
||||
@@ -384,7 +352,6 @@ impl BuiltinId {
|
||||
TryEval => PrimOpPhase::TryEval,
|
||||
TypeOf => PrimOpPhase::TypeOf,
|
||||
UnsafeDiscardStringContext => PrimOpPhase::UnsafeDiscardStringContext,
|
||||
UnsafeDiscardOutputDependency => PrimOpPhase::UnsafeDiscardOutputDependency,
|
||||
UnsafeGetAttrPos => PrimOpPhase::UnsafeGetAttrPos,
|
||||
Warn => PrimOpPhase::Warn,
|
||||
ZipAttrsWith => PrimOpPhase::ZipAttrsWith,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use colored::Colorize;
|
||||
use fix_builtins::BuiltinId;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::{InstructionPtr, Op, OperandType};
|
||||
use crate::{AttrKeyType, InstructionPtr, Op, OperandType};
|
||||
|
||||
pub trait DisassemblerContext {
|
||||
fn resolve_string(&self, id: u32) -> &str;
|
||||
@@ -80,30 +79,19 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
|
||||
#[inline(always)]
|
||||
fn read_operand_data(&mut self) {
|
||||
use OperandType::*;
|
||||
let tag = self.read_u8();
|
||||
let ty = OperandType::try_from_primitive(tag).expect("invalid operand type");
|
||||
match ty {
|
||||
Const => {
|
||||
OperandType::Const => {
|
||||
self.read_u32();
|
||||
}
|
||||
BigInt => {
|
||||
self.read_i64();
|
||||
}
|
||||
Local => {
|
||||
OperandType::Local => {
|
||||
self.read_u8();
|
||||
self.read_u32();
|
||||
}
|
||||
BuiltinConst => {
|
||||
self.read_u32();
|
||||
}
|
||||
Builtins => {}
|
||||
ReplBinding => {
|
||||
self.read_u32();
|
||||
}
|
||||
ScopedImportBinding => {
|
||||
self.read_u32();
|
||||
self.read_u32();
|
||||
OperandType::Builtins => {}
|
||||
OperandType::BigInt => {
|
||||
self.read_i64();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,32 +283,33 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
Op::Call => {
|
||||
self.read_operand_data();
|
||||
("Call", "arg=?".into())
|
||||
}
|
||||
},
|
||||
Op::DispatchPrimOp => {
|
||||
let id = BuiltinId::try_from_primitive(self.read_u8()).expect("invalid builtin id");
|
||||
("DispatchPrimOp", format!("id={id:?}"))
|
||||
todo!();
|
||||
}
|
||||
|
||||
Op::MakeAttrs => {
|
||||
let static_count = self.read_u32();
|
||||
let dynamic_count = self.read_u32();
|
||||
let mut args = format!("static={} dynamic={}", static_count, dynamic_count);
|
||||
|
||||
for _ in 0..static_count {
|
||||
let key_id = self.read_u32();
|
||||
let _ = write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
||||
let count = self.read_u32();
|
||||
let mut args = format!("size={}", count);
|
||||
for _ in 0..count {
|
||||
let key_tag = self.read_u8();
|
||||
let key_ty =
|
||||
AttrKeyType::try_from_primitive(key_tag).expect("invalid attr key type");
|
||||
match key_ty {
|
||||
AttrKeyType::Static => {
|
||||
let key_id = self.read_u32();
|
||||
let _ =
|
||||
write!(args, " [{}={}", self.ctx.resolve_string(key_id), key_id);
|
||||
}
|
||||
AttrKeyType::Dynamic => {
|
||||
let _ = write!(args, " [dyn");
|
||||
self.read_operand_data();
|
||||
}
|
||||
}
|
||||
self.read_operand_data();
|
||||
let _span_id = self.read_u32();
|
||||
args.push(']');
|
||||
}
|
||||
|
||||
for _ in 0..dynamic_count {
|
||||
let _ = write!(args, " [dyn");
|
||||
self.read_operand_data();
|
||||
let _span_id = self.read_u32();
|
||||
args.push(']');
|
||||
}
|
||||
|
||||
("MakeAttrs", args)
|
||||
}
|
||||
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
||||
@@ -423,25 +412,19 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
let force = self.read_u8();
|
||||
("ConcatStrings", format!("count={} force={}", count, force))
|
||||
}
|
||||
Op::CoerceToString => ("CoerceToString", String::new()),
|
||||
Op::ResolvePath => {
|
||||
let dir_id = self.read_u32();
|
||||
let dir = self.ctx.resolve_string(dir_id);
|
||||
("ResolvePath", format!("dir={:?}", dir))
|
||||
}
|
||||
Op::ResolvePath => ("ResolvePath", String::new()),
|
||||
Op::Assert => {
|
||||
let raw_idx = self.read_u32();
|
||||
let span_id = self.read_u32();
|
||||
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
||||
}
|
||||
Op::PushWith => ("PushWith", String::new()),
|
||||
Op::PopWith => ("PopWith", String::new()),
|
||||
Op::PrepareWith => ("PrepareWith", String::new()),
|
||||
Op::LookupWith => {
|
||||
let idx = self.read_u32();
|
||||
let name = self.ctx.resolve_string(idx);
|
||||
let n = self.read_u8();
|
||||
for _ in 0..n {
|
||||
self.read_operand_data();
|
||||
}
|
||||
("LookupWith", format!("sym={:?} n={}", name, n))
|
||||
("LookupWith", format!("{:?}", name))
|
||||
}
|
||||
|
||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||
@@ -449,16 +432,19 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
let id = self.read_u8();
|
||||
("LoadBuiltin", format!("id={}", id))
|
||||
}
|
||||
Op::MkPos => {
|
||||
let span_id = self.read_u32();
|
||||
("MkPos", format!("id={}", span_id))
|
||||
}
|
||||
Op::LoadReplBinding => {
|
||||
let idx = self.read_u32();
|
||||
let name = self.ctx.resolve_string(idx);
|
||||
("LoadReplBinding", format!("{:?}", name))
|
||||
}
|
||||
Op::LoadScopedBinding => {
|
||||
let slot = self.read_u32();
|
||||
let idx = self.read_u32();
|
||||
let name = self.ctx.resolve_string(idx);
|
||||
("LoadScopedBinding", format!("slot={} {:?}", slot, name))
|
||||
("LoadScopedBinding", format!("{:?}", name))
|
||||
}
|
||||
Op::Return => ("Return", String::new()),
|
||||
Op::Illegal => ("Illegal", String::new()),
|
||||
|
||||
+215
-187
@@ -1,4 +1,6 @@
|
||||
use fix_builtins::{BUILTINS, BuiltinId};
|
||||
use std::ops::Deref;
|
||||
|
||||
use fix_builtins::BuiltinId;
|
||||
use fix_common::StringId;
|
||||
use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
|
||||
use hashbrown::HashMap;
|
||||
@@ -16,8 +18,6 @@ pub trait BytecodeContext {
|
||||
fn get_code(&self) -> &[u8];
|
||||
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
||||
fn add_constant(&mut self, val: Const) -> u32;
|
||||
fn current_source_dir(&mut self) -> StringId;
|
||||
fn current_scope_slot(&self) -> Option<u32>;
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
@@ -79,18 +79,21 @@ pub enum Op {
|
||||
JumpIfTrue,
|
||||
Jump,
|
||||
|
||||
CoerceToString,
|
||||
|
||||
ConcatStrings,
|
||||
ResolvePath,
|
||||
|
||||
Assert,
|
||||
|
||||
PushWith,
|
||||
PopWith,
|
||||
LookupWith,
|
||||
PrepareWith,
|
||||
|
||||
LoadBuiltins,
|
||||
LoadBuiltin,
|
||||
|
||||
MkPos,
|
||||
|
||||
LoadReplBinding,
|
||||
LoadScopedBinding,
|
||||
|
||||
@@ -100,7 +103,7 @@ pub enum Op {
|
||||
}
|
||||
|
||||
struct ScopeInfo {
|
||||
depth: u8,
|
||||
depth: u16,
|
||||
thunk_map: HashMap<ThunkId, u32>,
|
||||
}
|
||||
|
||||
@@ -113,12 +116,9 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
pub enum OperandType {
|
||||
Const,
|
||||
BigInt,
|
||||
Local,
|
||||
BuiltinConst,
|
||||
Builtins,
|
||||
ReplBinding,
|
||||
ScopedImportBinding,
|
||||
BigInt,
|
||||
}
|
||||
|
||||
pub enum Const {
|
||||
@@ -126,7 +126,6 @@ pub enum Const {
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(StringId),
|
||||
Path(StringId),
|
||||
PrimOp {
|
||||
id: BuiltinId,
|
||||
arity: u8,
|
||||
@@ -144,12 +143,9 @@ pub enum AttrKeyType {
|
||||
|
||||
pub enum InlineOperand {
|
||||
Const(Const),
|
||||
BigInt(i64),
|
||||
Local { layer: u8, local: u32 },
|
||||
BuiltinConst(StringId),
|
||||
Local { layer: u16, local: u32 },
|
||||
Builtins,
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
BigInt(i64),
|
||||
}
|
||||
|
||||
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
||||
@@ -168,12 +164,12 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn inline_maybe_thunk(&self, val: &MaybeThunk) -> InlineOperand {
|
||||
fn inline_maybe_thunk(&self, val: MaybeThunk) -> InlineOperand {
|
||||
use MaybeThunk::*;
|
||||
match *val {
|
||||
match val {
|
||||
Int(x) => {
|
||||
if let Ok(x) = x.try_into() {
|
||||
InlineOperand::Const(Const::Smi(x))
|
||||
if x <= i32::MAX as i64 {
|
||||
InlineOperand::Const(Const::Smi(x as i32))
|
||||
} else {
|
||||
InlineOperand::BigInt(x)
|
||||
}
|
||||
@@ -182,64 +178,37 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
Bool(b) => InlineOperand::Const(Const::Bool(b)),
|
||||
Null => InlineOperand::Const(Const::Null),
|
||||
Str(id) => InlineOperand::Const(Const::String(id)),
|
||||
Path(id) => InlineOperand::Const(Const::String(id)),
|
||||
Thunk(id) => {
|
||||
let (layer, local) = self.resolve_thunk(id);
|
||||
InlineOperand::Local { layer, local }
|
||||
}
|
||||
Arg { layer } => InlineOperand::Local { layer, local: 0 },
|
||||
Builtin(id) => {
|
||||
let (_, arity) = BUILTINS[id as usize];
|
||||
InlineOperand::Const(Const::PrimOp {
|
||||
id,
|
||||
arity,
|
||||
dispatch_ip: id.entry_phase().ip(),
|
||||
})
|
||||
}
|
||||
BuiltinConst(id) => InlineOperand::BuiltinConst(id),
|
||||
Builtins => InlineOperand::Builtins,
|
||||
ReplBinding(id) => InlineOperand::ReplBinding(id),
|
||||
ScopedImportBinding(id) => InlineOperand::ScopedImportBinding(id),
|
||||
Arg { layer } => InlineOperand::Local {
|
||||
layer: layer.try_into().expect("scope too deep!"),
|
||||
local: 0,
|
||||
},
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_maybe_thunk(&mut self, val: &MaybeThunk) {
|
||||
use InlineOperand::*;
|
||||
fn emit_maybe_thunk(&mut self, val: MaybeThunk) {
|
||||
let operand = self.inline_maybe_thunk(val);
|
||||
match operand {
|
||||
Const(val) => {
|
||||
InlineOperand::Const(val) => {
|
||||
let idx = self.ctx.add_constant(val);
|
||||
self.emit_u8(OperandType::Const as u8);
|
||||
self.emit_u32(idx);
|
||||
}
|
||||
BigInt(val) => {
|
||||
self.emit_u8(OperandType::BigInt as u8);
|
||||
self.emit_i64(val);
|
||||
}
|
||||
Local { layer, local } => {
|
||||
InlineOperand::Local { layer, local } => {
|
||||
self.emit_u8(OperandType::Local as u8);
|
||||
self.emit_u8(layer);
|
||||
self.emit_u8(layer as u8);
|
||||
self.emit_u32(local);
|
||||
}
|
||||
BuiltinConst(id) => {
|
||||
self.emit_u8(OperandType::BuiltinConst as u8);
|
||||
self.emit_str_id(id);
|
||||
}
|
||||
Builtins => {
|
||||
InlineOperand::Builtins => {
|
||||
self.emit_u8(OperandType::Builtins as u8);
|
||||
}
|
||||
ReplBinding(id) => {
|
||||
self.emit_u8(OperandType::ReplBinding as u8);
|
||||
self.emit_str_id(id);
|
||||
}
|
||||
ScopedImportBinding(id) => {
|
||||
self.emit_u8(OperandType::ScopedImportBinding as u8);
|
||||
let slot = self
|
||||
.ctx
|
||||
.current_scope_slot()
|
||||
.expect("ScopedImportBinding outside scoped compilation");
|
||||
self.emit_u32(slot);
|
||||
self.emit_str_id(id);
|
||||
InlineOperand::BigInt(val) => {
|
||||
self.emit_u8(OperandType::BigInt as u8);
|
||||
self.emit_i64(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,11 +218,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.ctx.get_code_mut().push(op as u8);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_bool(&mut self, val: bool) {
|
||||
self.emit_u8(u8::from(val));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_u8(&mut self, val: u8) {
|
||||
self.ctx.get_code_mut().push(val);
|
||||
@@ -325,11 +289,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
.extend_from_slice(&(id.0.to_usize() as u32).to_le_bytes());
|
||||
}
|
||||
|
||||
fn current_depth(&self) -> u8 {
|
||||
fn current_depth(&self) -> u16 {
|
||||
self.scope_stack.last().map_or(0, |s| s.depth)
|
||||
}
|
||||
|
||||
fn resolve_thunk(&self, id: ThunkId) -> (u8, u32) {
|
||||
fn resolve_thunk(&self, id: ThunkId) -> (u16, u32) {
|
||||
for scope in self.scope_stack.iter().rev() {
|
||||
if let Some(&local_idx) = scope.thunk_map.get(&id) {
|
||||
let layer = self.current_depth() - scope.depth;
|
||||
@@ -339,19 +303,111 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
panic!("ThunkId {:?} not found in any scope", id);
|
||||
}
|
||||
|
||||
fn emit_load(&mut self, layer: u8, local: u32) {
|
||||
fn emit_load(&mut self, layer: u16, local: u32) {
|
||||
if layer == 0 {
|
||||
self.emit_op(Op::LoadLocal);
|
||||
self.emit_u32(local);
|
||||
} else {
|
||||
self.emit_op(Op::LoadOuter);
|
||||
self.emit_u8(layer);
|
||||
self.emit_u8(layer as u8);
|
||||
self.emit_u32(local);
|
||||
}
|
||||
}
|
||||
|
||||
fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize {
|
||||
match ir.deref() {
|
||||
Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body),
|
||||
Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body),
|
||||
Ir::If { cond, consq, alter } => {
|
||||
self.count_with_thunks(*cond)
|
||||
+ self.count_with_thunks(*consq)
|
||||
+ self.count_with_thunks(*alter)
|
||||
}
|
||||
Ir::BinOp { lhs, rhs, .. } => {
|
||||
self.count_with_thunks(*lhs) + self.count_with_thunks(*rhs)
|
||||
}
|
||||
Ir::UnOp { rhs, .. } => self.count_with_thunks(*rhs),
|
||||
Ir::Call { func, .. } => self.count_with_thunks(*func),
|
||||
Ir::Assert {
|
||||
assertion, expr, ..
|
||||
} => self.count_with_thunks(*assertion) + self.count_with_thunks(*expr),
|
||||
Ir::Select { expr, .. } => self.count_with_thunks(*expr),
|
||||
Ir::HasAttr { lhs, .. } => self.count_with_thunks(*lhs),
|
||||
Ir::ConcatStrings { parts, .. } => {
|
||||
parts.iter().map(|p| self.count_with_thunks(*p)).sum()
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_all_thunks<'ir>(
|
||||
&self,
|
||||
own_thunks: &[(ThunkId, RawIrRef<'ir>)],
|
||||
body: RawIrRef<'ir>,
|
||||
) -> Vec<(ThunkId, RawIrRef<'ir>)> {
|
||||
let mut all = Vec::from(own_thunks);
|
||||
self.collect_with_thunks_recursive(body, &mut all);
|
||||
let mut i = 0;
|
||||
while i < all.len() {
|
||||
let thunk_body = all[i].1;
|
||||
self.collect_with_thunks_recursive(thunk_body, &mut all);
|
||||
i += 1;
|
||||
}
|
||||
all
|
||||
}
|
||||
|
||||
fn collect_with_thunks_recursive<'ir>(
|
||||
&self,
|
||||
ir: RawIrRef<'ir>,
|
||||
out: &mut Vec<(ThunkId, RawIrRef<'ir>)>,
|
||||
) {
|
||||
match ir.deref() {
|
||||
Ir::With { thunks, body, .. } => {
|
||||
for &(id, inner) in thunks.iter() {
|
||||
out.push((id, inner));
|
||||
}
|
||||
self.collect_with_thunks_recursive(*body, out);
|
||||
}
|
||||
Ir::TopLevel { thunks, body } => {
|
||||
for &(id, inner) in thunks.iter() {
|
||||
out.push((id, inner));
|
||||
}
|
||||
self.collect_with_thunks_recursive(*body, out);
|
||||
}
|
||||
Ir::If { cond, consq, alter } => {
|
||||
self.collect_with_thunks_recursive(*cond, out);
|
||||
self.collect_with_thunks_recursive(*consq, out);
|
||||
self.collect_with_thunks_recursive(*alter, out);
|
||||
}
|
||||
Ir::BinOp { lhs, rhs, .. } => {
|
||||
self.collect_with_thunks_recursive(*lhs, out);
|
||||
self.collect_with_thunks_recursive(*rhs, out);
|
||||
}
|
||||
Ir::UnOp { rhs, .. } => self.collect_with_thunks_recursive(*rhs, out),
|
||||
Ir::Call { func, .. } => {
|
||||
self.collect_with_thunks_recursive(*func, out);
|
||||
}
|
||||
Ir::Assert {
|
||||
assertion, expr, ..
|
||||
} => {
|
||||
self.collect_with_thunks_recursive(*assertion, out);
|
||||
self.collect_with_thunks_recursive(*expr, out);
|
||||
}
|
||||
Ir::Select { expr, .. } => {
|
||||
self.collect_with_thunks_recursive(*expr, out);
|
||||
}
|
||||
Ir::HasAttr { lhs, .. } => self.collect_with_thunks_recursive(*lhs, out),
|
||||
Ir::ConcatStrings { parts, .. } => {
|
||||
for p in parts.iter() {
|
||||
self.collect_with_thunks_recursive(*p, out);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_scope(&mut self, has_arg: bool, thunk_ids: &[ThunkId]) {
|
||||
let depth = self.scope_stack.len().try_into().expect("scope too deep!");
|
||||
let depth = self.scope_stack.len() as u16;
|
||||
let thunk_base = if has_arg { 1u32 } else { 0u32 };
|
||||
let thunk_map = thunk_ids
|
||||
.iter()
|
||||
@@ -366,13 +422,19 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
|
||||
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
|
||||
match ir {
|
||||
match ir.deref() {
|
||||
&Ir::TopLevel { body, ref thunks } => {
|
||||
let thunk_ids: Vec<ThunkId> = thunks.iter().map(|&(id, _)| id).collect();
|
||||
let with_thunk_count = self.count_with_thunks(body);
|
||||
let total_slots = thunks.len() + with_thunk_count;
|
||||
|
||||
let all_thunks = self.collect_all_thunks(thunks, body);
|
||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||
|
||||
self.push_scope(false, &thunk_ids);
|
||||
if !thunks.is_empty() {
|
||||
|
||||
if total_slots > 0 {
|
||||
self.emit_op(Op::AllocLocals);
|
||||
self.emit_u32(thunks.len().try_into().expect("too many thunks"));
|
||||
self.emit_u32(total_slots as u32);
|
||||
}
|
||||
|
||||
self.emit_scope_thunks(thunks);
|
||||
@@ -406,11 +468,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
|
||||
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
||||
match ir {
|
||||
match ir.deref() {
|
||||
&Ir::Int(x) => {
|
||||
if let Ok(x) = x.try_into() {
|
||||
if x <= i32::MAX as i64 {
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_i32(x);
|
||||
self.emit_i32(x as i32);
|
||||
} else {
|
||||
self.emit_op(Op::PushBigInt);
|
||||
self.emit_i64(x);
|
||||
@@ -430,8 +492,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
&Ir::Path(p) => {
|
||||
self.emit_expr(p);
|
||||
self.emit_op(Op::ResolvePath);
|
||||
let dir_id = self.ctx.current_source_dir();
|
||||
self.emit_str_id(dir_id);
|
||||
}
|
||||
&Ir::If { cond, consq, alter } => {
|
||||
self.emit_expr(cond);
|
||||
@@ -494,7 +554,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_maybe_thunk(arg);
|
||||
}
|
||||
&Ir::Arg { layer } => {
|
||||
self.emit_load(layer, 0);
|
||||
self.emit_load(layer.try_into().expect("scope too deep!"), 0);
|
||||
}
|
||||
&Ir::TopLevel { body, ref thunks } => {
|
||||
self.emit_toplevel_inner(body, thunks);
|
||||
@@ -507,6 +567,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
} => {
|
||||
self.emit_select(expr, attrpath, default, span);
|
||||
}
|
||||
&Ir::Thunk(id) => {
|
||||
let (layer, local) = self.resolve_thunk(id);
|
||||
self.emit_load(layer, local);
|
||||
}
|
||||
Ir::Builtins => {
|
||||
self.emit_op(Op::LoadBuiltins);
|
||||
}
|
||||
@@ -516,23 +580,24 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
&Ir::BuiltinConst(id) => {
|
||||
self.emit_select(
|
||||
&Ir::Builtins,
|
||||
RawIrRef(&Ir::Builtins),
|
||||
&[Attr::Str(id, TextRange::default())],
|
||||
None,
|
||||
TextRange::default(),
|
||||
);
|
||||
}
|
||||
&Ir::ConcatStrings {
|
||||
ref parts,
|
||||
force_string,
|
||||
parts: _,
|
||||
force_string: _,
|
||||
} => {
|
||||
for &part in parts.iter() {
|
||||
self.emit_expr(part);
|
||||
self.emit_op(Op::CoerceToString);
|
||||
}
|
||||
self.emit_op(Op::ConcatStrings);
|
||||
self.emit_u16(parts.len() as u16);
|
||||
self.emit_bool(force_string);
|
||||
todo!("redesign ConcatStrings");
|
||||
// self.emit_op(Op::ConcatStrings);
|
||||
// self.emit_u16(parts.len() as u16);
|
||||
// self.emit_u8(if force_string { 1 } else { 0 });
|
||||
// for &part in parts.iter() {
|
||||
// let operand = self.inline_maybe_thunk(part);
|
||||
// self.emit_inline_operand(operand);
|
||||
// }
|
||||
}
|
||||
&Ir::HasAttr { lhs, ref rhs } => {
|
||||
self.emit_has_attr(lhs, rhs);
|
||||
@@ -546,10 +611,15 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
let raw_idx = self.ctx.intern_string(assertion_raw);
|
||||
let span_id = self.ctx.register_span(*span);
|
||||
self.emit_expr(*assertion);
|
||||
self.emit_expr(*expr);
|
||||
self.emit_op(Op::Assert);
|
||||
self.emit_str_id(raw_idx);
|
||||
self.emit_u32(span_id);
|
||||
self.emit_expr(*expr);
|
||||
}
|
||||
&Ir::CurPos(span) => {
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_op(Op::MkPos);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
&Ir::ReplBinding(name) => {
|
||||
self.emit_op(Op::LoadReplBinding);
|
||||
@@ -557,88 +627,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
&Ir::ScopedImportBinding(name) => {
|
||||
self.emit_op(Op::LoadScopedBinding);
|
||||
let slot = self
|
||||
.ctx
|
||||
.current_scope_slot()
|
||||
.expect("ScopedImportBinding outside scoped compilation");
|
||||
self.emit_u32(slot);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
Ir::WithLookup { sym, namespaces } => {
|
||||
// counter
|
||||
self.emit_expr(&Ir::Int(0));
|
||||
self.emit_op(Op::LookupWith);
|
||||
self.emit_str_id(*sym);
|
||||
self.emit_u8(
|
||||
namespaces
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("too many `with` namespaces"),
|
||||
);
|
||||
for namespace in namespaces {
|
||||
self.emit_maybe_thunk(namespace);
|
||||
}
|
||||
&Ir::With {
|
||||
namespace,
|
||||
body,
|
||||
ref thunks,
|
||||
} => {
|
||||
self.emit_with(namespace, body, thunks);
|
||||
}
|
||||
&Ir::MaybeThunk(thunk) => {
|
||||
use MaybeThunk::*;
|
||||
match *thunk {
|
||||
Int(x) => {
|
||||
if let Ok(x) = x.try_into() {
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_i32(x);
|
||||
} else {
|
||||
self.emit_op(Op::PushBigInt);
|
||||
self.emit_i64(x);
|
||||
}
|
||||
}
|
||||
Float(x) => {
|
||||
self.emit_op(Op::PushFloat);
|
||||
self.emit_f64(x);
|
||||
}
|
||||
Bool(true) => self.emit_op(Op::PushTrue),
|
||||
Bool(false) => self.emit_op(Op::PushFalse),
|
||||
Null => self.emit_op(Op::PushNull),
|
||||
Str(id) => {
|
||||
self.emit_op(Op::PushString);
|
||||
self.emit_str_id(id);
|
||||
}
|
||||
Path(id) => {
|
||||
self.emit_op(Op::PushString);
|
||||
self.emit_str_id(id);
|
||||
self.emit_op(Op::ResolvePath);
|
||||
let dir_id = self.ctx.current_source_dir();
|
||||
self.emit_str_id(dir_id);
|
||||
}
|
||||
Thunk(id) => {
|
||||
let (layer, local) = self.resolve_thunk(id);
|
||||
self.emit_load(layer, local);
|
||||
}
|
||||
Arg { layer } => self.emit_load(layer, 0),
|
||||
Builtin(id) => {
|
||||
self.emit_op(Op::LoadBuiltin);
|
||||
self.emit_u8(id as u8);
|
||||
}
|
||||
BuiltinConst(id) => self.emit_select(
|
||||
&Ir::Builtins,
|
||||
&[Attr::Str(id, TextRange::default())],
|
||||
None,
|
||||
TextRange::default(),
|
||||
),
|
||||
Builtins => self.emit_op(Op::LoadBuiltins),
|
||||
ReplBinding(name) => {
|
||||
self.emit_op(Op::LoadReplBinding);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
ScopedImportBinding(name) => {
|
||||
self.emit_op(Op::LoadScopedBinding);
|
||||
let slot = self
|
||||
.ctx
|
||||
.current_scope_slot()
|
||||
.expect("ScopedImportBinding outside scoped compilation");
|
||||
self.emit_u32(slot);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
}
|
||||
&Ir::WithLookup(name) => {
|
||||
// TODO: specialize shallow with lookups
|
||||
self.emit_op(Op::PrepareWith);
|
||||
self.emit_op(Op::LookupWith);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -703,6 +705,18 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||
self.patch_i32(end_placeholder, end_offset);
|
||||
}
|
||||
PipeL => {
|
||||
todo!("new call");
|
||||
// self.emit_expr(rhs);
|
||||
// self.emit_expr(lhs);
|
||||
// self.emit_op(Op::Call);
|
||||
}
|
||||
PipeR => {
|
||||
todo!("new call");
|
||||
// self.emit_expr(lhs);
|
||||
// self.emit_expr(rhs);
|
||||
// self.emit_op(Op::Call);
|
||||
}
|
||||
_ => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_expr(rhs);
|
||||
@@ -725,13 +739,17 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_func<'ir>(
|
||||
fn emit_func(
|
||||
&mut self,
|
||||
thunks: &[(ThunkId, RawIrRef<'ir>)],
|
||||
param: &Option<Param<'ir>>,
|
||||
body: RawIrRef<'ir>,
|
||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||
param: &Option<Param<'_>>,
|
||||
body: RawIrRef<'_>,
|
||||
) {
|
||||
let thunk_ids: Vec<ThunkId> = thunks.iter().map(|&(id, _)| id).collect();
|
||||
let with_thunk_count = self.count_with_thunks(body);
|
||||
let total_slots = thunks.len() + with_thunk_count;
|
||||
|
||||
let all_thunks = self.collect_all_thunks(thunks, body);
|
||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||
|
||||
let skip_patch = self.emit_jump_placeholder();
|
||||
let entry_point = self.ctx.get_code().len() as u32;
|
||||
@@ -750,10 +768,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
{
|
||||
self.emit_op(Op::MakePatternClosure);
|
||||
self.emit_u32(entry_point);
|
||||
self.emit_u32(thunks.len().try_into().expect("too many thunks"));
|
||||
self.emit_u32(total_slots as u32);
|
||||
self.emit_u16(required.len() as u16);
|
||||
self.emit_u16(optional.len() as u16);
|
||||
self.emit_bool(*ellipsis);
|
||||
self.emit_u8(if *ellipsis { 1 } else { 0 });
|
||||
|
||||
for &(sym, _) in required.iter() {
|
||||
self.emit_str_id(sym);
|
||||
@@ -769,36 +787,33 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
} else {
|
||||
self.emit_op(Op::MakeClosure);
|
||||
self.emit_u32(entry_point);
|
||||
self.emit_u32(thunks.len().try_into().expect("too many thunks"));
|
||||
self.emit_u32(total_slots as u32);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_attrset(
|
||||
&mut self,
|
||||
stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>,
|
||||
dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)],
|
||||
stcs: &fix_ir::HashMap<'_, StringId, (MaybeThunk, TextRange)>,
|
||||
dyns: &[(RawIrRef<'_>, MaybeThunk, TextRange)],
|
||||
) {
|
||||
if stcs.is_empty() && dyns.is_empty() {
|
||||
self.emit_op(Op::MakeEmptyAttrs);
|
||||
return;
|
||||
}
|
||||
|
||||
for &(key_expr, _val, _span) in dyns.iter() {
|
||||
self.emit_expr(key_expr);
|
||||
}
|
||||
|
||||
let total = stcs.len() + dyns.len();
|
||||
self.emit_op(Op::MakeAttrs);
|
||||
self.emit_u32(stcs.len() as u32);
|
||||
self.emit_u32(dyns.len() as u32);
|
||||
self.emit_u32(total as u32);
|
||||
|
||||
for (&sym, &(val, span)) in stcs.iter() {
|
||||
self.emit_u8(AttrKeyType::Static as u8);
|
||||
self.emit_str_id(sym);
|
||||
self.emit_maybe_thunk(val);
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
|
||||
for &(_key, val, span) in dyns.iter() {
|
||||
self.emit_u8(AttrKeyType::Dynamic as u8);
|
||||
self.emit_maybe_thunk(val);
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_u32(span_id);
|
||||
@@ -896,6 +911,19 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_op(Op::HasAttrResolve);
|
||||
}
|
||||
|
||||
fn emit_with(
|
||||
&mut self,
|
||||
namespace: MaybeThunk,
|
||||
body: RawIrRef<'_>,
|
||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||
) {
|
||||
self.emit_op(Op::PushWith);
|
||||
self.emit_maybe_thunk(namespace);
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(body);
|
||||
self.emit_op(Op::PopWith);
|
||||
}
|
||||
|
||||
fn emit_toplevel_inner(&mut self, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(body);
|
||||
|
||||
+68
-160
@@ -39,12 +39,12 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T, E: std::fmt::Display>
|
||||
}
|
||||
|
||||
pub trait DowngradeContext<'id: 'ir, 'ir> {
|
||||
fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir>;
|
||||
fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir>;
|
||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>;
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk;
|
||||
|
||||
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId;
|
||||
fn resolve_sym(&self, id: StringId) -> Symbol<'_>;
|
||||
fn lookup(&mut self, sym: StringId, span: TextRange) -> Result<GhostRoMaybeThunkRef<'id, 'ir>>;
|
||||
fn lookup(&self, sym: StringId, span: TextRange) -> Result<MaybeThunk>;
|
||||
|
||||
fn get_current_source(&self) -> Source;
|
||||
|
||||
@@ -53,11 +53,11 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
|
||||
F: FnOnce(&mut Self) -> R;
|
||||
fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> Result<(Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>, R)>;
|
||||
fn with_with_scope<F, R>(&mut self, namespace: GhostRoMaybeThunkRef<'id, 'ir>, f: F) -> R
|
||||
F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, R)>;
|
||||
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R;
|
||||
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>)
|
||||
fn with_thunk_scope<F, R>(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>)
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R;
|
||||
|
||||
@@ -65,11 +65,11 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
|
||||
}
|
||||
|
||||
pub trait Downgrade<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>>;
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>>;
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for Expr {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
use Expr::*;
|
||||
match self {
|
||||
Apply(apply) => apply.downgrade(ctx),
|
||||
@@ -98,7 +98,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
AttrSet(attrs) => attrs.downgrade(ctx),
|
||||
UnaryOp(op) => op.downgrade(ctx),
|
||||
Ident(ident) => ident.downgrade(ctx),
|
||||
CurPos(curpos) => curpos.downgrade(ctx),
|
||||
CurPos(curpos) => Ok(ctx.new_expr(Ir::CurPos(curpos.syntax().text_range()))),
|
||||
With(with) => with.downgrade(ctx),
|
||||
HasAttr(has) => has.downgrade(ctx),
|
||||
Paren(paren) => paren
|
||||
@@ -114,7 +114,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Assert {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let assertion = self.condition().require(ctx, span)?;
|
||||
let assertion_raw = assertion.to_string();
|
||||
@@ -130,7 +130,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::IfElse {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let cond = self.condition().require(ctx, span)?.downgrade(ctx)?;
|
||||
let consq = self.body().require(ctx, span)?.downgrade(ctx)?;
|
||||
@@ -142,7 +142,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
macro_rules! path {
|
||||
($ty:ident) => {
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::$ty {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
downgrade_path(self.parts(), ctx)
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ path!(PathAbs);
|
||||
path!(PathRel);
|
||||
path!(PathHome);
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::PathSearch {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let path = {
|
||||
let temp = self.content().require(ctx, span)?;
|
||||
@@ -180,7 +180,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Str {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let normalized = self.normalized_parts();
|
||||
let is_single_literal = normalized.len() == 1
|
||||
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
|
||||
@@ -210,7 +210,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Literal {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let expr = match self.kind() {
|
||||
ast::LiteralKind::Integer(int) => Ir::Int(int.value().require(ctx, span)?),
|
||||
@@ -225,73 +225,16 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Ident {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let text = self.ident_token().require(ctx, span)?.to_string();
|
||||
let sym = ctx.intern_string(text);
|
||||
ctx.lookup(sym, span)
|
||||
.map(|thunk| ctx.new_expr(Ir::MaybeThunk(thunk)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::CurPos {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) {
|
||||
let mut line = 1u32;
|
||||
let mut col = 1u32;
|
||||
|
||||
for (idx, ch) in content.char_indices() {
|
||||
if idx >= offset {
|
||||
break;
|
||||
}
|
||||
if ch == '\n' {
|
||||
line += 1;
|
||||
col = 1;
|
||||
} else {
|
||||
col += 1;
|
||||
}
|
||||
}
|
||||
|
||||
(line, col)
|
||||
}
|
||||
|
||||
let span = self.syntax().text_range();
|
||||
let source = ctx.get_current_source();
|
||||
let (line, column) = byte_offset_to_line_col(&source.src, span.start().into());
|
||||
|
||||
let file_sym = ctx.intern_string("file");
|
||||
let line_sym = ctx.intern_string("line");
|
||||
let column_sym = ctx.intern_string("column");
|
||||
|
||||
let file: GhostRoMaybeThunkRef = ctx
|
||||
.bump()
|
||||
.alloc(GhostCell::new(MaybeThunk::Str(ctx.intern_string(source.get_name()))).into());
|
||||
let line = ctx
|
||||
.bump()
|
||||
.alloc(GhostCell::new(MaybeThunk::Int(i64::from(line))).into());
|
||||
let column = ctx
|
||||
.bump()
|
||||
.alloc(GhostCell::new(MaybeThunk::Int(i64::from(column))).into());
|
||||
|
||||
let map = {
|
||||
let mut map = HashMap::new_in(ctx.bump());
|
||||
|
||||
map.insert(file_sym, (file, TextRange::default()));
|
||||
map.insert(line_sym, (line, TextRange::default()));
|
||||
map.insert(column_sym, (column, TextRange::default()));
|
||||
|
||||
map
|
||||
};
|
||||
|
||||
Ok(ctx.new_expr(Ir::AttrSet {
|
||||
stcs: map,
|
||||
dyns: Vec::new_in(ctx.bump()),
|
||||
}))
|
||||
ctx.lookup(sym, span).map(|thunk| thunk.to_ir(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::AttrSet {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let rec = self.rec_token().is_some();
|
||||
if !rec {
|
||||
let attrs = downgrade_attrs(self, ctx)?;
|
||||
@@ -307,7 +250,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::List {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let bump = ctx.bump();
|
||||
let items = self
|
||||
.items()
|
||||
@@ -321,52 +264,17 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::BinOp {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
use BinOpKind::*;
|
||||
use ast::BinOpKind as Kind;
|
||||
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let lhs = self.lhs().require(ctx, span)?.downgrade(ctx)?;
|
||||
let rhs = self.rhs().require(ctx, span)?.downgrade(ctx)?;
|
||||
let kind = match self.operator().require(ctx, span)? {
|
||||
Kind::Concat => Con,
|
||||
Kind::Update => Upd,
|
||||
Kind::Add => Add,
|
||||
Kind::Sub => Sub,
|
||||
Kind::Mul => Mul,
|
||||
Kind::Div => Div,
|
||||
Kind::And => And,
|
||||
Kind::Equal => Eq,
|
||||
Kind::Implication => Impl,
|
||||
Kind::Less => Lt,
|
||||
Kind::LessOrEq => Leq,
|
||||
Kind::More => Gt,
|
||||
Kind::MoreOrEq => Geq,
|
||||
Kind::NotEqual => Neq,
|
||||
Kind::Or => Or,
|
||||
Kind::PipeLeft => {
|
||||
let arg = ctx.maybe_thunk(rhs);
|
||||
return Ok(ctx.new_expr(Ir::Call {
|
||||
func: lhs,
|
||||
arg,
|
||||
span,
|
||||
}));
|
||||
}
|
||||
Kind::PipeRight => {
|
||||
let arg = ctx.maybe_thunk(lhs);
|
||||
return Ok(ctx.new_expr(Ir::Call {
|
||||
func: rhs,
|
||||
arg,
|
||||
span,
|
||||
}));
|
||||
}
|
||||
};
|
||||
let kind = self.operator().require(ctx, span)?.into();
|
||||
Ok(ctx.new_expr(Ir::BinOp { lhs, rhs, kind }))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::HasAttr {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let lhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||
let rhs = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
||||
@@ -375,7 +283,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::UnaryOp {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let rhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||
let kind = self.operator().require(ctx, span)?.into();
|
||||
@@ -384,7 +292,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Select {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let expr = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||
let attrpath = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
||||
@@ -403,7 +311,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LegacyLet {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
||||
let attrset_expr = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| {
|
||||
@@ -432,7 +340,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LetIn {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump());
|
||||
let span = self.syntax().text_range();
|
||||
let body_expr = self.body().require(ctx, span)?;
|
||||
@@ -442,25 +350,33 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::With {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let namespace = self.namespace().require(ctx, span)?.downgrade(ctx)?;
|
||||
let namespace = ctx.maybe_thunk(namespace);
|
||||
|
||||
let body_expr = self.body().require(ctx, span)?;
|
||||
ctx.with_with_scope(namespace, |ctx| body_expr.downgrade(ctx))
|
||||
let (body, thunks) =
|
||||
ctx.with_thunk_scope(|ctx| ctx.with_with_scope(|ctx| body_expr.downgrade(ctx)));
|
||||
let body = body?;
|
||||
|
||||
Ok(ctx.new_expr(Ir::With {
|
||||
namespace,
|
||||
body,
|
||||
thunks,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Lambda {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let raw_param = self.param().require(ctx, span)?;
|
||||
let body_ast = self.body().require(ctx, span)?;
|
||||
|
||||
struct Ret<'id, 'ir> {
|
||||
param: Option<Param<'ir>>,
|
||||
body: GhostRoIrRef<'id, 'ir>,
|
||||
body: IrRef<'id, 'ir>,
|
||||
}
|
||||
|
||||
let (ret, thunks) = ctx.with_thunk_scope(|ctx| {
|
||||
@@ -517,7 +433,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
}
|
||||
|
||||
impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Apply {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<IrRef<'id, 'ir>> {
|
||||
let span = self.syntax().text_range();
|
||||
let func = self.lambda().require(ctx, span)?.downgrade(ctx)?;
|
||||
let arg = self.argument().require(ctx, span)?.downgrade(ctx)?;
|
||||
@@ -919,15 +835,8 @@ fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) -
|
||||
}
|
||||
|
||||
struct FinalizedAttrSet<'id, 'ir> {
|
||||
stcs: HashMap<'ir, StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||
dyns: Vec<
|
||||
'ir,
|
||||
(
|
||||
GhostRoIrRef<'id, 'ir>,
|
||||
GhostRoMaybeThunkRef<'id, 'ir>,
|
||||
TextRange,
|
||||
),
|
||||
>,
|
||||
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>,
|
||||
dyns: Vec<'ir, (IrRef<'id, 'ir>, MaybeThunk, TextRange)>,
|
||||
}
|
||||
|
||||
fn downgrade_attrs<'id, 'ir>(
|
||||
@@ -942,7 +851,7 @@ fn downgrade_attrs<'id, 'ir>(
|
||||
fn downgrade_attr<'id, 'ir>(
|
||||
attr: ast::Attr,
|
||||
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
||||
) -> Result<Attr<GhostRoIrRef<'id, 'ir>>> {
|
||||
) -> Result<Attr<IrRef<'id, 'ir>>> {
|
||||
use ast::Attr::*;
|
||||
use ast::InterpolPart::*;
|
||||
match attr {
|
||||
@@ -1003,7 +912,7 @@ fn downgrade_attr<'id, 'ir>(
|
||||
fn downgrade_attrpath<'id, 'ir>(
|
||||
attrpath: ast::Attrpath,
|
||||
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
||||
) -> Result<Vec<'ir, Attr<GhostRoIrRef<'id, 'ir>>>> {
|
||||
) -> Result<Vec<'ir, Attr<IrRef<'id, 'ir>>>> {
|
||||
let bump = ctx.bump();
|
||||
attrpath
|
||||
.attrs()
|
||||
@@ -1012,7 +921,7 @@ fn downgrade_attrpath<'id, 'ir>(
|
||||
}
|
||||
|
||||
struct PatternBindings<'id, 'ir> {
|
||||
body: GhostRoIrRef<'id, 'ir>,
|
||||
body: IrRef<'id, 'ir>,
|
||||
required: Vec<'ir, (StringId, TextRange)>,
|
||||
optional: Vec<'ir, (StringId, TextRange)>,
|
||||
}
|
||||
@@ -1021,7 +930,7 @@ fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
|
||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||
alias: Option<StringId>,
|
||||
ctx: &mut Ctx,
|
||||
body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<GhostRoIrRef<'id, 'ir>>,
|
||||
body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
|
||||
) -> Result<PatternBindings<'id, 'ir>>
|
||||
where
|
||||
Ctx: DowngradeContext<'id, 'ir>,
|
||||
@@ -1080,7 +989,6 @@ where
|
||||
}
|
||||
|
||||
let arg = ctx.new_expr(Ir::Arg { layer: 0 });
|
||||
let arg_thunk = ctx.maybe_thunk(arg);
|
||||
ctx.with_let_scope(&keys, |ctx| {
|
||||
let vals = params
|
||||
.into_iter()
|
||||
@@ -1092,16 +1000,21 @@ where
|
||||
span,
|
||||
} = param;
|
||||
let default = default.map(|default| default.downgrade(ctx)).transpose()?;
|
||||
// let default = if let Some(default) = default {
|
||||
// let default = default.clone().downgrade(ctx)?;
|
||||
// Some(ctx.maybe_thunk(default))
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
|
||||
let expr = ctx.new_expr(Ir::Select {
|
||||
Ok(ctx.new_expr(Ir::Select {
|
||||
expr: arg,
|
||||
attrpath: Vec::from_iter_in([Attr::Str(sym, sym_span)], bump),
|
||||
default,
|
||||
span,
|
||||
});
|
||||
Ok(ctx.maybe_thunk(expr))
|
||||
}))
|
||||
})
|
||||
.chain(alias.into_iter().map(|_| Ok(arg_thunk)))
|
||||
.chain(alias.into_iter().map(|_| Ok(arg)))
|
||||
.collect_in::<Result<_>>(bump)?;
|
||||
|
||||
let body = body_fn(ctx, &keys)?;
|
||||
@@ -1121,10 +1034,10 @@ fn downgrade_let_bindings<'id, 'ir, Ctx, F>(
|
||||
entries: Vec<'ir, ast::Entry>,
|
||||
ctx: &mut Ctx,
|
||||
body_fn: F,
|
||||
) -> Result<GhostRoIrRef<'id, 'ir>>
|
||||
) -> Result<IrRef<'id, 'ir>>
|
||||
where
|
||||
Ctx: DowngradeContext<'id, 'ir>,
|
||||
F: FnOnce(&mut Ctx, &[StringId]) -> Result<GhostRoIrRef<'id, 'ir>>,
|
||||
F: FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
|
||||
{
|
||||
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| {
|
||||
body_fn(ctx, binding_keys)
|
||||
@@ -1134,7 +1047,7 @@ where
|
||||
fn downgrade_rec_bindings<'id, 'ir, Ctx>(
|
||||
entries: Vec<'ir, ast::Entry>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<GhostRoIrRef<'id, 'ir>>
|
||||
) -> Result<IrRef<'id, 'ir>>
|
||||
where
|
||||
Ctx: DowngradeContext<'id, 'ir>,
|
||||
{
|
||||
@@ -1155,18 +1068,14 @@ fn downgrade_rec_attrs_impl<'id, 'ir, Ctx, F, const ALLOW_DYN: bool>(
|
||||
entries: Vec<'ir, ast::Entry>,
|
||||
ctx: &mut Ctx,
|
||||
body_fn: F,
|
||||
) -> Result<GhostRoIrRef<'id, 'ir>>
|
||||
) -> Result<IrRef<'id, 'ir>>
|
||||
where
|
||||
Ctx: DowngradeContext<'id, 'ir>,
|
||||
F: FnOnce(
|
||||
&mut Ctx,
|
||||
&[StringId],
|
||||
&[(
|
||||
GhostRoIrRef<'id, 'ir>,
|
||||
GhostRoMaybeThunkRef<'id, 'ir>,
|
||||
TextRange,
|
||||
)],
|
||||
) -> Result<GhostRoIrRef<'id, 'ir>>,
|
||||
&[(IrRef<'id, 'ir>, MaybeThunk, TextRange)],
|
||||
) -> Result<IrRef<'id, 'ir>>,
|
||||
{
|
||||
let mut pending = PendingAttrSet::new_in(ctx.bump());
|
||||
pending.collect_entries(entries.iter().cloned(), ctx)?;
|
||||
@@ -1181,7 +1090,7 @@ where
|
||||
let vals = {
|
||||
let mut temp = Vec::with_capacity_in(keys.len(), ctx.bump());
|
||||
for sym in &keys {
|
||||
temp.push(finalized.stcs.get(sym).expect("WTF").0);
|
||||
temp.push(finalized.stcs.get(sym).expect("WTF").0.to_ir(ctx));
|
||||
}
|
||||
temp
|
||||
};
|
||||
@@ -1192,7 +1101,7 @@ where
|
||||
fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
||||
entries: &[ast::Entry],
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<HashMap<'ir, StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>> {
|
||||
) -> Result<HashMap<'ir, StringId, (MaybeThunk, TextRange)>> {
|
||||
let mut inherit_lookups = HashMap::new_in(ctx.bump());
|
||||
for entry in entries {
|
||||
if let ast::Entry::Inherit(inherit) = entry
|
||||
@@ -1232,7 +1141,7 @@ fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const AL
|
||||
|
||||
fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
||||
pending: PendingAttrSet,
|
||||
inherit_lookups: &HashMap<StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
||||
let mut stcs = HashMap::new_in(ctx.bump());
|
||||
@@ -1260,9 +1169,9 @@ fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_D
|
||||
|
||||
fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
||||
value: PendingValue,
|
||||
inherit_lookups: &HashMap<StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
) -> Result<IrRef<'id, 'ir>> {
|
||||
match value {
|
||||
PendingValue::Expr(expr) => expr.downgrade(ctx),
|
||||
PendingValue::InheritFrom(from_expr, sym, span) => {
|
||||
@@ -1276,10 +1185,9 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW
|
||||
}
|
||||
PendingValue::InheritScope(sym, span) => {
|
||||
if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
|
||||
Ok(ctx.new_expr(Ir::MaybeThunk(expr)))
|
||||
Ok(expr.to_ir(ctx))
|
||||
} else {
|
||||
ctx.lookup(sym, span)
|
||||
.map(|val| ctx.new_expr(Ir::MaybeThunk(val)))
|
||||
ctx.lookup(sym, span).map(|val| val.to_ir(ctx))
|
||||
}
|
||||
}
|
||||
PendingValue::Set(set) => {
|
||||
@@ -1302,7 +1210,7 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW
|
||||
fn downgrade_path<'id, 'ir>(
|
||||
parts: impl IntoIterator<Item = ast::InterpolPart<ast::PathContent>>,
|
||||
ctx: &mut impl DowngradeContext<'id, 'ir>,
|
||||
) -> Result<GhostRoIrRef<'id, 'ir>> {
|
||||
) -> Result<IrRef<'id, 'ir>> {
|
||||
let bump = ctx.bump();
|
||||
let parts = parts
|
||||
.into_iter()
|
||||
|
||||
+135
-125
@@ -1,5 +1,5 @@
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use bumpalo::collections::Vec;
|
||||
@@ -10,70 +10,56 @@ use num_enum::TryFromPrimitive as _;
|
||||
use rnix::{TextRange, ast};
|
||||
use string_interner::DefaultStringInterner;
|
||||
|
||||
use crate::downgrade::DowngradeContext;
|
||||
|
||||
pub mod downgrade;
|
||||
|
||||
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
|
||||
|
||||
pub type GhostIrRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::IrRef;
|
||||
pub type GhostRoIrRef<'id, 'ir> = <GhostRoRef<'id, 'ir> as RefExt<'ir>>::IrRef;
|
||||
pub type RawIrRef<'ir> = <RawRef<'ir> as RefExt<'ir>>::IrRef;
|
||||
pub type GhostMaybeThunkRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::MaybeThunkRef;
|
||||
pub type GhostRoMaybeThunkRef<'id, 'ir> = <GhostRoRef<'id, 'ir> as RefExt<'ir>>::MaybeThunkRef;
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>);
|
||||
|
||||
impl<'id, 'ir> IrRef<'id, 'ir> {
|
||||
pub fn new(ir: &'ir GhostCell<'id, Ir<'ir, Self>>) -> Self {
|
||||
Self(ir)
|
||||
}
|
||||
|
||||
pub fn alloc(bump: &'ir Bump, ir: Ir<'ir, Self>) -> Self {
|
||||
Self(bump.alloc(GhostCell::new(ir)))
|
||||
}
|
||||
|
||||
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a Ir<'ir, Self> {
|
||||
self.0.borrow(token)
|
||||
}
|
||||
|
||||
impl<'id, 'ir> Ir<'ir, GhostRoRef<'id, 'ir>> {
|
||||
/// Freeze a mutable IR reference into a read-only one, consuming the
|
||||
/// `GhostToken` to prevent any further mutation.
|
||||
pub fn freeze(this: GhostRoIrRef<'id, 'ir>, _: GhostToken<'id>) -> RawIrRef<'ir> {
|
||||
// SAFETY: The transmute is sound because:
|
||||
// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`, so
|
||||
// `&'ir GhostCell<'id, T>` and `&'ir T` have identical layout.
|
||||
// - `Ir<'ir, R>` is `#[repr(C)]`, and for every field that depends on
|
||||
// `R`, instantiating `R = GhostRef<'id, 'ir>` vs `R = RawRef<'ir>`
|
||||
// produces types of identical layout:
|
||||
// - `R::IrRef` becomes `&'ir GhostCell<'id, Ir<...>>` vs `&'ir Ir<...>`
|
||||
// - `R::MaybeThunkRef` becomes `&'ir GhostCell<'id, MaybeThunk>`
|
||||
// vs `&'ir MaybeThunk`
|
||||
// - `R::Ref<Ir<'ir, R>>` (used in `ConcatStrings::parts`) reduces
|
||||
// to the same case as `R::IrRef`
|
||||
// - Therefore `IrRef<'id, 'ir>` and `RawIrRef<'ir>` are both
|
||||
// pointer-sized references with the same layout.
|
||||
//
|
||||
// Consuming the `GhostToken` guarantees no `borrow_mut` calls can
|
||||
// occur afterwards, so the shared `&Ir` references reachable from a
|
||||
// `RawIrRef<'ir>` can never alias with mutable references.
|
||||
unsafe { std::mem::transmute::<GhostRoIrRef<'id, 'ir>, RawIrRef<'ir>>(this) }
|
||||
///
|
||||
/// # Safety
|
||||
/// The transmute is sound because:
|
||||
/// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`
|
||||
/// - `IrRef<'id, 'ir>` is `#[repr(transparent)]` over
|
||||
/// `&'ir GhostCell<'id, Ir<'ir, Self>>`
|
||||
/// - `RawIrRef<'ir>` is `#[repr(transparent)]` over `&'ir Ir<'ir, Self>`
|
||||
/// - `Ir<'ir, Ref>` is `#[repr(C)]` and both ref types are pointer-sized
|
||||
///
|
||||
/// Consuming the `GhostToken` guarantees no `borrow_mut` calls can occur
|
||||
/// afterwards, so the shared `&Ir` references from `RawIrRef::Deref` can
|
||||
/// never alias with mutable references.
|
||||
pub fn freeze(self, _token: GhostToken<'id>) -> RawIrRef<'ir> {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct GhostRoCell<'id, T: ?Sized>(GhostCell<'id, T>);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RawIrRef<'ir>(pub &'ir Ir<'ir, Self>);
|
||||
|
||||
impl<'id, T> From<GhostCell<'id, T>> for GhostRoCell<'id, T> {
|
||||
fn from(value: GhostCell<'id, T>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, T: ?Sized> From<&GhostCell<'id, T>> for &GhostRoCell<'id, T> {
|
||||
fn from(value: &GhostCell<'id, T>) -> Self {
|
||||
// SAFETY: `GhostRoCell` is `#[repr(transparent)]` over `GhostCell`
|
||||
// TODO: document mutability
|
||||
unsafe { std::mem::transmute(value) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, T: ?Sized> From<&T> for &GhostRoCell<'id, T> {
|
||||
fn from(value: &T) -> Self {
|
||||
// SAFETY: `GhostRoCell` is `#[repr(transparent)]` over `GhostCell`,
|
||||
// which is `#[repr(transparent)]` over `T`
|
||||
// TODO: document mutability
|
||||
unsafe { std::mem::transmute(value) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, T: ?Sized> GhostRoCell<'id, T> {
|
||||
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a T {
|
||||
self.0.borrow(token)
|
||||
impl<'ir> Deref for RawIrRef<'ir> {
|
||||
type Target = Ir<'ir, RawIrRef<'ir>>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,117 +73,110 @@ pub enum MaybeThunk {
|
||||
Str(StringId),
|
||||
Path(StringId),
|
||||
Thunk(ThunkId),
|
||||
Arg { layer: u8 },
|
||||
Arg { layer: usize },
|
||||
Builtin(BuiltinId),
|
||||
BuiltinConst(StringId),
|
||||
Builtins,
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
WithLookup(StringId),
|
||||
}
|
||||
|
||||
pub trait Ref<'ir> {
|
||||
type Ref<T>
|
||||
where
|
||||
T: 'ir;
|
||||
}
|
||||
|
||||
pub trait RefExt<'ir>: Ref<'ir> {
|
||||
type Ir;
|
||||
type IrRef;
|
||||
type MaybeThunkRef;
|
||||
}
|
||||
impl<'ir, T: Ref<'ir> + 'ir> RefExt<'ir> for T {
|
||||
type Ir = Ir<'ir, Self>;
|
||||
type IrRef = Self::Ref<Self::Ir>;
|
||||
type MaybeThunkRef = Self::Ref<MaybeThunk>;
|
||||
}
|
||||
|
||||
pub struct GhostRef<'id, 'ir>(PhantomData<&'ir GhostCell<'id, ()>>);
|
||||
pub struct GhostRoRef<'id, 'ir>(PhantomData<&'ir GhostRoCell<'id, ()>>);
|
||||
pub struct RawRef<'ir>(PhantomData<&'ir ()>);
|
||||
|
||||
impl<'id, 'ir> Ref<'ir> for GhostRef<'id, 'ir> {
|
||||
type Ref<T: 'ir> = &'ir GhostCell<'id, T>;
|
||||
}
|
||||
impl<'id, 'ir> Ref<'ir> for GhostRoRef<'id, 'ir> {
|
||||
type Ref<T: 'ir> = &'ir GhostRoCell<'id, T>;
|
||||
}
|
||||
impl<'ir> Ref<'ir> for RawRef<'ir> {
|
||||
type Ref<T: 'ir> = &'ir T;
|
||||
impl MaybeThunk {
|
||||
fn to_ir<'id, 'ir>(self, ctx: &mut impl DowngradeContext<'id, 'ir>) -> IrRef<'id, 'ir> {
|
||||
use MaybeThunk::*;
|
||||
let ir = match self {
|
||||
Int(x) => Ir::Int(x),
|
||||
Float(x) => Ir::Float(x),
|
||||
Bool(x) => Ir::Bool(x),
|
||||
Null => Ir::Null,
|
||||
Str(x) => Ir::Str(x),
|
||||
Path(x) => Ir::Path(ctx.new_expr(Ir::Str(x))),
|
||||
Thunk(x) => Ir::Thunk(x),
|
||||
Arg { layer } => Ir::Arg { layer },
|
||||
Builtin(x) => Ir::Builtin(x),
|
||||
Builtins => Ir::Builtins,
|
||||
ReplBinding(x) => Ir::ReplBinding(x),
|
||||
ScopedImportBinding(x) => Ir::ScopedImportBinding(x),
|
||||
WithLookup(x) => Ir::WithLookup(x),
|
||||
};
|
||||
ctx.new_expr(ir)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
|
||||
pub enum Ir<'ir, Ref> {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Null,
|
||||
Str(StringId),
|
||||
Path(R::IrRef),
|
||||
Path(Ref),
|
||||
AttrSet {
|
||||
stcs: HashMap<'ir, StringId, (R::MaybeThunkRef, TextRange)>,
|
||||
dyns: Vec<'ir, (R::IrRef, R::MaybeThunkRef, TextRange)>,
|
||||
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>,
|
||||
dyns: Vec<'ir, (Ref, MaybeThunk, TextRange)>,
|
||||
},
|
||||
List {
|
||||
items: Vec<'ir, R::MaybeThunkRef>,
|
||||
items: Vec<'ir, MaybeThunk>,
|
||||
},
|
||||
ConcatStrings {
|
||||
parts: Vec<'ir, R::Ref<Ir<'ir, R>>>,
|
||||
parts: Vec<'ir, Ref>,
|
||||
force_string: bool,
|
||||
},
|
||||
|
||||
// OPs
|
||||
UnOp {
|
||||
rhs: R::IrRef,
|
||||
rhs: Ref,
|
||||
kind: UnOpKind,
|
||||
},
|
||||
BinOp {
|
||||
lhs: R::IrRef,
|
||||
rhs: R::IrRef,
|
||||
lhs: Ref,
|
||||
rhs: Ref,
|
||||
kind: BinOpKind,
|
||||
},
|
||||
HasAttr {
|
||||
lhs: R::IrRef,
|
||||
rhs: Vec<'ir, Attr<R::IrRef>>,
|
||||
lhs: Ref,
|
||||
rhs: Vec<'ir, Attr<Ref>>,
|
||||
},
|
||||
Select {
|
||||
expr: R::IrRef,
|
||||
attrpath: Vec<'ir, Attr<R::IrRef>>,
|
||||
default: Option<R::IrRef>,
|
||||
expr: Ref,
|
||||
attrpath: Vec<'ir, Attr<Ref>>,
|
||||
default: Option<Ref>,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
// Conditionals
|
||||
If {
|
||||
cond: R::IrRef,
|
||||
consq: R::IrRef,
|
||||
alter: R::IrRef,
|
||||
cond: Ref,
|
||||
consq: Ref,
|
||||
alter: Ref,
|
||||
},
|
||||
Assert {
|
||||
assertion: R::IrRef,
|
||||
expr: R::IrRef,
|
||||
assertion: Ref,
|
||||
expr: Ref,
|
||||
assertion_raw: String,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
WithLookup {
|
||||
sym: StringId,
|
||||
namespaces: Vec<'ir, R::MaybeThunkRef>,
|
||||
With {
|
||||
namespace: MaybeThunk,
|
||||
body: Ref,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
},
|
||||
WithLookup(StringId),
|
||||
|
||||
// Function related
|
||||
Func {
|
||||
body: R::IrRef,
|
||||
body: Ref,
|
||||
param: Option<Param<'ir>>,
|
||||
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
},
|
||||
Arg {
|
||||
layer: u8,
|
||||
layer: usize,
|
||||
},
|
||||
Call {
|
||||
func: R::IrRef,
|
||||
arg: R::MaybeThunkRef,
|
||||
func: Ref,
|
||||
arg: MaybeThunk,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
@@ -208,10 +187,11 @@ pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
|
||||
|
||||
// Misc
|
||||
TopLevel {
|
||||
body: R::IrRef,
|
||||
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
|
||||
body: Ref,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
},
|
||||
MaybeThunk(R::MaybeThunkRef),
|
||||
Thunk(ThunkId),
|
||||
CurPos(TextRange),
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
}
|
||||
@@ -261,6 +241,36 @@ pub enum BinOpKind {
|
||||
// Set/String/Path operations
|
||||
Con, // List concatenation (`++`)
|
||||
Upd, // AttrSet update (`//`)
|
||||
|
||||
// Not standard, but part of rnix AST
|
||||
PipeL,
|
||||
PipeR,
|
||||
}
|
||||
|
||||
impl From<ast::BinOpKind> for BinOpKind {
|
||||
fn from(op: ast::BinOpKind) -> Self {
|
||||
use BinOpKind::*;
|
||||
use ast::BinOpKind as kind;
|
||||
match op {
|
||||
kind::Concat => Con,
|
||||
kind::Update => Upd,
|
||||
kind::Add => Add,
|
||||
kind::Sub => Sub,
|
||||
kind::Mul => Mul,
|
||||
kind::Div => Div,
|
||||
kind::And => And,
|
||||
kind::Equal => Eq,
|
||||
kind::Implication => Impl,
|
||||
kind::Less => Lt,
|
||||
kind::LessOrEq => Leq,
|
||||
kind::More => Gt,
|
||||
kind::MoreOrEq => Geq,
|
||||
kind::NotEqual => Neq,
|
||||
kind::Or => Or,
|
||||
kind::PipeLeft => PipeL,
|
||||
kind::PipeRight => PipeR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The kinds of unary operations.
|
||||
@@ -289,38 +299,38 @@ pub struct Param<'ir> {
|
||||
|
||||
pub fn new_global_env(
|
||||
strings: &mut DefaultStringInterner,
|
||||
) -> hashbrown::HashMap<StringId, MaybeThunk> {
|
||||
) -> hashbrown::HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
|
||||
let mut global_env = hashbrown::HashMap::new();
|
||||
let builtins_sym = StringId(strings.get_or_intern("builtins"));
|
||||
global_env.insert(builtins_sym, MaybeThunk::Builtins);
|
||||
global_env.insert(builtins_sym, Ir::Builtins);
|
||||
|
||||
for (idx, &(name, _)) in BUILTINS.iter().enumerate() {
|
||||
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
|
||||
let name = StringId(strings.get_or_intern(name));
|
||||
global_env.insert(name, MaybeThunk::Builtin(id));
|
||||
global_env.insert(name, Ir::Builtin(id));
|
||||
}
|
||||
|
||||
let consts = [
|
||||
(
|
||||
"__currentSystem",
|
||||
MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))),
|
||||
Ir::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))),
|
||||
),
|
||||
("__langVersion", MaybeThunk::Int(6)),
|
||||
("__langVersion", Ir::Int(6)),
|
||||
(
|
||||
"__nixVersion",
|
||||
MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))),
|
||||
Ir::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))),
|
||||
),
|
||||
(
|
||||
"__storeDir",
|
||||
MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("storeDir"))),
|
||||
Ir::BuiltinConst(StringId(strings.get_or_intern("storeDir"))),
|
||||
),
|
||||
(
|
||||
"__nixPath",
|
||||
MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("nixPath"))),
|
||||
Ir::BuiltinConst(StringId(strings.get_or_intern("nixPath"))),
|
||||
),
|
||||
("null", MaybeThunk::Null),
|
||||
("true", MaybeThunk::Bool(true)),
|
||||
("false", MaybeThunk::Bool(false)),
|
||||
("null", Ir::Null),
|
||||
("true", Ir::Bool(true)),
|
||||
("false", Ir::Bool(false)),
|
||||
];
|
||||
|
||||
for (name, ir) in consts {
|
||||
|
||||
@@ -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" }
|
||||
@@ -1,447 +0,0 @@
|
||||
//! `builtins.hasContext`, `builtins.getContext`, `builtins.appendContext`,
|
||||
//! `builtins.unsafeDiscardStringContext`,
|
||||
//! `builtins.unsafeDiscardOutputDependency`.
|
||||
//!
|
||||
//! See `fix-abstract-vm/src/string_context.rs` for the
|
||||
//! `StringContextElem` type.
|
||||
|
||||
use fix_abstract_vm::{
|
||||
AttrSet, BytecodeReader, List as VmList, Machine, MachineExt, NixString, NixType, Step,
|
||||
StrictValue, StringContext, StringContextElem, Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
};
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Gc, Mutation};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub fn has_context<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
if !val.is::<StringId>() && val.as_gc::<NixString>().is_none() {
|
||||
return m.finish_type_err(NixType::String, val.ty());
|
||||
}
|
||||
let has_ctx = !ctx.get_string_context(val).is_empty();
|
||||
m.return_from_primop(Value::new_inline(has_ctx), reader)
|
||||
}
|
||||
|
||||
pub fn unsafe_discard_string_context<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
if let Some(sid) = val.as_inline::<StringId>() {
|
||||
return m.return_from_primop(Value::new_inline(sid), reader);
|
||||
}
|
||||
let Some(ns) = val.as_gc::<NixString>() else {
|
||||
return m.finish_type_err(NixType::String, val.ty());
|
||||
};
|
||||
let sid = ctx.intern_string(ns.as_str());
|
||||
m.return_from_primop(Value::new_inline(sid), reader)
|
||||
}
|
||||
|
||||
pub fn unsafe_discard_output_dependency<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
if let Some(sid) = val.as_inline::<StringId>() {
|
||||
return m.return_from_primop(Value::new_inline(sid), reader);
|
||||
}
|
||||
let Some(ns) = val.as_gc::<NixString>() else {
|
||||
return m.finish_type_err(NixType::String, val.ty());
|
||||
};
|
||||
if ns.context().is_empty() {
|
||||
let sid = ctx.intern_string(ns.as_str());
|
||||
return m.return_from_primop(Value::new_inline(sid), reader);
|
||||
}
|
||||
|
||||
let mut new_ctx = StringContext::new();
|
||||
for elem in ns.context() {
|
||||
let replacement = match elem {
|
||||
StringContextElem::DrvDeep { drv_path } => StringContextElem::Opaque {
|
||||
path: drv_path.clone(),
|
||||
},
|
||||
other => other.clone(),
|
||||
};
|
||||
new_ctx.insert(replacement);
|
||||
}
|
||||
|
||||
let s: Box<str> = ns.as_str().into();
|
||||
let new_ns = Gc::new(mc, NixString::with_context(s, new_ctx));
|
||||
m.return_from_primop(Value::new_gc(new_ns), reader)
|
||||
}
|
||||
|
||||
pub fn get_context<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
if !val.is::<StringId>() && val.as_gc::<NixString>().is_none() {
|
||||
return m.finish_type_err(NixType::String, val.ty());
|
||||
}
|
||||
let elems = ctx.get_string_context(val);
|
||||
|
||||
struct Info {
|
||||
path: bool,
|
||||
all_outputs: bool,
|
||||
outputs: SmallVec<[Box<str>; 2]>,
|
||||
}
|
||||
impl Info {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
path: false,
|
||||
all_outputs: false,
|
||||
outputs: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut by_path: std::collections::BTreeMap<Box<str>, Info> = std::collections::BTreeMap::new();
|
||||
for elem in elems {
|
||||
match elem {
|
||||
StringContextElem::Opaque { path } => {
|
||||
by_path.entry(path.clone()).or_insert_with(Info::new).path = true;
|
||||
}
|
||||
StringContextElem::DrvDeep { drv_path } => {
|
||||
by_path
|
||||
.entry(drv_path.clone())
|
||||
.or_insert_with(Info::new)
|
||||
.all_outputs = true;
|
||||
}
|
||||
StringContextElem::Built { drv_path, output } => {
|
||||
by_path
|
||||
.entry(drv_path.clone())
|
||||
.or_insert_with(Info::new)
|
||||
.outputs
|
||||
.push(output.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut outer_entries: SmallVec<[(StringId, Value<'gc>); 4]> = SmallVec::new();
|
||||
for (path, mut info) in by_path {
|
||||
info.outputs.sort();
|
||||
info.outputs.dedup();
|
||||
|
||||
let mut sub: SmallVec<[(StringId, Value<'gc>); 4]> = SmallVec::new();
|
||||
if info.all_outputs {
|
||||
sub.push((ctx.intern_string("allOutputs"), Value::new_inline(true)));
|
||||
}
|
||||
if !info.outputs.is_empty() {
|
||||
let items: smallvec::SmallVec<[Value<'gc>; 4]> = info
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|o| Value::new_inline(ctx.intern_string(o)))
|
||||
.collect();
|
||||
let list = VmList::new(mc, items);
|
||||
sub.push((ctx.intern_string("outputs"), Value::new_gc(list)));
|
||||
}
|
||||
if info.path {
|
||||
sub.push((ctx.intern_string("path"), Value::new_inline(true)));
|
||||
}
|
||||
sub.sort_by_key(|(k, _)| *k);
|
||||
let sub_attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(sub));
|
||||
|
||||
outer_entries.push((ctx.intern_string(&path), Value::new_gc(sub_attrs)));
|
||||
}
|
||||
outer_entries.sort_by_key(|(k, _)| *k);
|
||||
|
||||
let outer = Gc::new(mc, AttrSet::from_sorted_unchecked(outer_entries));
|
||||
m.return_from_primop(Value::new_gc(outer), reader)
|
||||
}
|
||||
|
||||
/// appendContext :: String -> AttrSet -> String
|
||||
/// The context AttrSet maps store-path strings to `{ path?: Bool, allOutputs?:
|
||||
/// Bool, outputs?: [String] }`. Each present field contributes one
|
||||
/// StringContextElem to the result.
|
||||
///
|
||||
/// Requires forcing nested attrset values and list elements lazily, so it's
|
||||
/// structured as a state machine with the following stack layout:
|
||||
///
|
||||
/// [strVal, attrs, idx, acc] - outer loop
|
||||
/// [strVal, attrs, idx, acc, entryAttrs] - after entry forced
|
||||
/// [strVal, attrs, idx, acc, list] - after `outputs` forced
|
||||
/// [strVal, attrs, idx, acc, list, oidx] - output-element loop
|
||||
/// [strVal, attrs, idx, acc, list, oidx, outElem] - after element forced
|
||||
///
|
||||
/// `acc` is a sentinel `NixString` whose `data` is empty and whose `context`
|
||||
/// is the accumulator. The string value itself is preserved in `strVal` and
|
||||
/// retrieved at finalization.
|
||||
///
|
||||
// TODO: handle thunk-valued `path` and `allOutputs` sub-attrs; currently they
|
||||
// must be already-evaluated booleans.
|
||||
pub fn append_context<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (str_val, attrs) = m.force_and_retry::<(StrictValue, Gc<AttrSet>)>(reader, mc)?;
|
||||
|
||||
let initial_ctx: StringContext = ctx.get_string_context(str_val).clone();
|
||||
let acc = Gc::new(mc, NixString::with_context("", initial_ctx));
|
||||
|
||||
m.push(str_val.relax());
|
||||
m.push(Value::new_gc(attrs));
|
||||
m.push(Value::new_inline(0i32));
|
||||
m.push(Value::new_gc(acc));
|
||||
|
||||
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn append_context_loop<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let attrs = m.peek_forced(2).as_gc::<AttrSet>().unwrap();
|
||||
|
||||
if idx as usize >= attrs.entries.len() {
|
||||
return append_context_finalize(m, ctx, reader, mc);
|
||||
}
|
||||
|
||||
let entry_val = attrs.entries[idx as usize].1;
|
||||
m.push(entry_val);
|
||||
m.force_slot_to_pc(
|
||||
0,
|
||||
reader,
|
||||
mc,
|
||||
PrimOpPhase::AppendContextEntryForced.ip() as usize,
|
||||
)?;
|
||||
reader.set_pc(PrimOpPhase::AppendContextEntryForced.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn append_context_entry_forced<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// Stack: [strVal, attrs, idx, acc, entryAttrs(thunk)]
|
||||
// The slot still holds the Thunk pointer; re-force to extract the now-
|
||||
// Evaluated value into the slot.
|
||||
m.force_slot(0, reader, mc)?;
|
||||
let entry_val = m.peek_forced(0);
|
||||
let Some(entry_attrs) = entry_val.as_gc::<AttrSet>() else {
|
||||
return m.finish_type_err(NixType::AttrSet, entry_val.ty());
|
||||
};
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx = m.peek(2).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let outer = m.peek_forced(3).as_gc::<AttrSet>().unwrap();
|
||||
let path_key = outer.entries[idx as usize].0;
|
||||
let path_str_owned: Box<str> = ctx.resolve_string(path_key).into();
|
||||
if !path_str_owned.starts_with("/nix/store/") {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"context key '{path_str_owned}' is not a store path"
|
||||
)));
|
||||
}
|
||||
|
||||
// Eagerly handle `path` and `allOutputs` (assumed already-forced
|
||||
// booleans - most callers either set them to literal `true` or omit
|
||||
// them entirely).
|
||||
// TODO: force these two attributes correctly
|
||||
let path_id = ctx.intern_string("path");
|
||||
let all_outputs_id = ctx.intern_string("allOutputs");
|
||||
let outputs_id = ctx.intern_string("outputs");
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let acc_gc = m.peek(1).as_gc::<NixString>().unwrap();
|
||||
let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
|
||||
|
||||
if let Some(v) = entry_attrs.lookup(path_id)
|
||||
&& v.as_inline::<bool>() == Some(true)
|
||||
{
|
||||
new_acc.insert(StringContextElem::Opaque {
|
||||
path: path_str_owned.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(v) = entry_attrs.lookup(all_outputs_id)
|
||||
&& v.as_inline::<bool>() == Some(true)
|
||||
{
|
||||
if !path_str_owned.ends_with(".drv") {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"tried to add all-outputs context of {path_str_owned}, which is not a derivation, to a string"
|
||||
)));
|
||||
}
|
||||
new_acc.insert(StringContextElem::DrvDeep {
|
||||
drv_path: path_str_owned.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc));
|
||||
m.replace(1, Value::new_gc(new_acc_gc));
|
||||
|
||||
if let Some(outputs_val) = entry_attrs.lookup(outputs_id) {
|
||||
m.replace(0, outputs_val);
|
||||
m.force_slot_to_pc(
|
||||
0,
|
||||
reader,
|
||||
mc,
|
||||
PrimOpPhase::AppendContextOutputsForced.ip() as usize,
|
||||
)?;
|
||||
reader.set_pc(PrimOpPhase::AppendContextOutputsForced.ip() as usize);
|
||||
return Step::Continue(());
|
||||
}
|
||||
|
||||
let _ = m.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
|
||||
m.replace(1, Value::new_inline(idx_back + 1));
|
||||
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn append_context_outputs_forced<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
_ctx: &mut impl VmRuntimeCtx,
|
||||
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::<VmList>() else {
|
||||
return m.finish_type_err(NixType::List, list_val.ty());
|
||||
};
|
||||
if list.inner.borrow().is_empty() {
|
||||
// Stack: [strVal, attrs, idx, acc, list] -> drop list, bump idx.
|
||||
let _ = m.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
|
||||
m.replace(1, Value::new_inline(idx_back + 1));
|
||||
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
|
||||
return Step::Continue(());
|
||||
}
|
||||
|
||||
m.push(Value::new_inline(0i32));
|
||||
reader.set_pc(PrimOpPhase::AppendContextOutputElementLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn append_context_output_element_loop<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
_ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let oidx = m.peek(0).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let list = m.peek_forced(1).as_gc::<VmList>().unwrap();
|
||||
let len = list.inner.borrow().len();
|
||||
if oidx as usize >= len {
|
||||
// Stack: [strVal, attrs, idx, acc, list, oidx] -> drop oidx & list,
|
||||
// bump idx in place.
|
||||
let _ = m.pop();
|
||||
let _ = m.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx_back = m.peek(1).as_inline::<i32>().unwrap();
|
||||
m.replace(1, Value::new_inline(idx_back + 1));
|
||||
reader.set_pc(PrimOpPhase::AppendContextLoop.ip() as usize);
|
||||
return Step::Continue(());
|
||||
}
|
||||
|
||||
let elem = list.inner.borrow()[oidx as usize];
|
||||
m.push(elem);
|
||||
m.force_slot_to_pc(
|
||||
0,
|
||||
reader,
|
||||
mc,
|
||||
PrimOpPhase::AppendContextOutputElementForced.ip() as usize,
|
||||
)?;
|
||||
reader.set_pc(PrimOpPhase::AppendContextOutputElementForced.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn append_context_output_element_forced<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
m.force_slot(0, reader, mc)?;
|
||||
let elem = m.peek_forced(0);
|
||||
let Some(output_name) = ctx.get_string(elem) else {
|
||||
return m.finish_type_err(NixType::String, elem.ty());
|
||||
};
|
||||
let output_name: Box<str> = output_name.into();
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx = m.peek(4).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let outer = m.peek_forced(5).as_gc::<AttrSet>().unwrap();
|
||||
let path_key = outer.entries[idx as usize].0;
|
||||
let path_str: Box<str> = ctx.resolve_string(path_key).into();
|
||||
if !path_str.ends_with(".drv") {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"tried to add derivation output context of {path_str}, which is not a derivation, to a string"
|
||||
)));
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let acc_gc = m.peek(3).as_gc::<NixString>().unwrap();
|
||||
let mut new_acc: StringContext = acc_gc.context().iter().cloned().collect();
|
||||
new_acc.insert(StringContextElem::Built {
|
||||
drv_path: path_str,
|
||||
output: output_name,
|
||||
});
|
||||
let new_acc_gc = Gc::new(mc, NixString::with_context("", new_acc));
|
||||
m.replace(3, Value::new_gc(new_acc_gc));
|
||||
|
||||
// Stack: [strVal, attrs, idx, acc, list, oidx, outElem] -> drop outElem,
|
||||
// bump oidx in place.
|
||||
let _ = m.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let oidx = m.peek(0).as_inline::<i32>().unwrap();
|
||||
m.replace(0, Value::new_inline(oidx + 1));
|
||||
reader.set_pc(PrimOpPhase::AppendContextOutputElementLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
fn append_context_finalize<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// Stack: [strVal, attrs, idx, acc]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let acc_gc = m.pop().as_gc::<NixString>().unwrap();
|
||||
let _ = m.pop(); // idx
|
||||
let _ = m.pop(); // attrs
|
||||
let str_val_raw = m.pop();
|
||||
|
||||
// The strVal was already forced at entry; restrict() is infallible here.
|
||||
let str_val = str_val_raw
|
||||
.restrict()
|
||||
.unwrap_or_else(|_| panic!("appendContext: strVal unexpectedly a thunk"));
|
||||
|
||||
let s_str = ctx.get_string(str_val).unwrap_or("").to_owned();
|
||||
let context: StringContext = acc_gc.context().iter().cloned().collect();
|
||||
let result = if context.is_empty() {
|
||||
let sid = ctx.intern_string(s_str);
|
||||
Value::new_inline(sid)
|
||||
} else {
|
||||
let ns = Gc::new(mc, NixString::with_context(s_str, context));
|
||||
Value::new_gc(ns)
|
||||
};
|
||||
m.return_from_primop(result, reader)
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
use fix_abstract_vm::{
|
||||
AttrSet, BytecodeReader, Closure, Env, List, Machine, MachineExt, Step, StrictValue, Value,
|
||||
VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
};
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Gc, Mutation, RefLock};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub fn seq<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
m.force_slot(1, reader, mc)?;
|
||||
let e2 = m.pop();
|
||||
let _ = m.pop();
|
||||
m.return_from_primop(e2, reader)
|
||||
}
|
||||
|
||||
pub fn abort<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [msg] - force msg, then abort with it
|
||||
m.force_slot(0, reader, mc)?;
|
||||
let msg_val = m.peek_forced(0);
|
||||
let msg = ctx.get_string(msg_val).unwrap_or("<non-string-value>");
|
||||
m.finish_err(Error::eval_error(format!(
|
||||
"evaluation aborted with the following error message: '{msg}'"
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn deep_seq_force_top<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
m.force_slot(1, reader, mc)?;
|
||||
|
||||
let e1 = m.peek_forced(1);
|
||||
|
||||
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
||||
let attrs = &attrs.entries;
|
||||
if attrs.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
attrs.iter().map(|&(_, v)| v).collect()
|
||||
}
|
||||
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
|
||||
let inner = list.inner.borrow();
|
||||
if inner.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
inner.iter().copied().collect()
|
||||
}
|
||||
} else {
|
||||
SmallVec::new()
|
||||
};
|
||||
|
||||
if children.is_empty() {
|
||||
let e2 = m.pop();
|
||||
let _ = m.pop();
|
||||
return m.return_from_primop(e2, reader);
|
||||
}
|
||||
|
||||
let count = children.len() as i32;
|
||||
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default());
|
||||
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children);
|
||||
|
||||
let e2 = m.pop();
|
||||
let _ = m.pop();
|
||||
m.push(e2);
|
||||
m.push(Value::new_gc(seen));
|
||||
m.push(Value::new_gc(worklist));
|
||||
m.push(Value::new_inline(count));
|
||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn deep_seq_push<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e2, seen, worklist, counter]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = m.peek(0).as_inline::<i32>().unwrap();
|
||||
if counter == 0 {
|
||||
let _ = m.pop(); // counter
|
||||
let _ = m.pop(); // worklist
|
||||
let _ = m.pop(); // seen
|
||||
let val = m.pop();
|
||||
return m.return_from_primop(val, reader);
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
|
||||
m.replace(0, Value::new_inline(counter - 1));
|
||||
m.push(item);
|
||||
|
||||
// force item at TOS, resume at DeepSeqLoop after force
|
||||
m.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
|
||||
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn deep_seq_loop<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack after pop: [e2, seen, worklist, counter]
|
||||
let item = m.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = m.peek(0).as_inline::<i32>().unwrap();
|
||||
|
||||
let mut added: usize = 0;
|
||||
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
||||
let attrs = &attrs.entries;
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let seen = m.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||
if !is_value_in_seen(seen, item) {
|
||||
add_value_to_seen(seen, mc, item);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
{
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &(_, v) in attrs.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = attrs.len();
|
||||
}
|
||||
}
|
||||
} else if let Some(list) = item.as_gc::<List<'gc>>() {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let seen = m.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||
if !is_value_in_seen(seen, item) {
|
||||
add_value_to_seen(seen, mc, item);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = m.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
{
|
||||
let inner = list.inner.borrow();
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &v in inner.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = inner.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.replace(0, Value::new_inline(counter + added as i32));
|
||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn force_result_shallow<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
m.force_slot(0, reader, mc)?;
|
||||
let val = m.peek_forced(0);
|
||||
|
||||
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
let len = attrs.entries.len();
|
||||
(len, len > 0)
|
||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||
let len = list.inner.borrow().len();
|
||||
(len, len > 0)
|
||||
} else {
|
||||
(0, false)
|
||||
};
|
||||
|
||||
if !has_children {
|
||||
let val = m.pop();
|
||||
return m.finish_ok(ctx.convert_value(val));
|
||||
}
|
||||
|
||||
m.push(Value::new_inline(0i32));
|
||||
m.push(Value::new_inline(count as i32));
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn force_result_shallow_push<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx = m.peek(1).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let len = m.peek(0).as_inline::<i32>().unwrap();
|
||||
|
||||
if idx == len {
|
||||
let _ = m.pop(); // len
|
||||
let _ = m.pop(); // idx
|
||||
let val = m.pop();
|
||||
return m.finish_ok(ctx.convert_value(val));
|
||||
}
|
||||
|
||||
let val = m.peek_forced(2);
|
||||
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
attrs.entries.get(idx as usize).map(|&(_, v)| v)
|
||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||
list.inner.borrow().get(idx as usize).copied()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(child) = child {
|
||||
m.replace(1, Value::new_inline(idx + 1));
|
||||
m.push(child);
|
||||
m.force_slot_to_pc(
|
||||
0,
|
||||
reader,
|
||||
mc,
|
||||
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
|
||||
)?;
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
|
||||
}
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn force_result_shallow_loop<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let _ = m.pop(); // forced child
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn force_result_deep_finish<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
m.finish_ok(ctx.convert_value(val.relax()))
|
||||
}
|
||||
|
||||
fn is_value_in_seen<'gc>(seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
|
||||
if !is_container(val) {
|
||||
return false;
|
||||
}
|
||||
let target = val.to_bits();
|
||||
for &v in seen.inner.borrow().iter() {
|
||||
if v.to_bits() == target {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn add_value_to_seen<'gc>(seen: Gc<'gc, List<'gc>>, mc: &Mutation<'gc>, val: Value<'gc>) {
|
||||
if is_container(val) {
|
||||
seen.unlock(mc).borrow_mut().push(val);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_functor_1<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// Stack invariant on every (re-)entry: [..., orig_arg, self, functor]
|
||||
// where `functor` is TOS. Retries during force land back here safely.
|
||||
let functor = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
// Stack now: [..., orig_arg, self]
|
||||
let self_val = m.pop();
|
||||
m.push(functor.relax());
|
||||
// Stack: [..., orig_arg, functor]
|
||||
// Call 1: functor(self). Resume into CallFunctor2 once it returns.
|
||||
m.call(
|
||||
reader,
|
||||
mc,
|
||||
self_val,
|
||||
PrimOpPhase::CallFunctor2.ip() as usize,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn call_functor_2<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// Stack on entry: [..., orig_arg, intermediate]
|
||||
// call_stack top: synthetic frame with caller's resume_pc.
|
||||
let intermediate = m.pop();
|
||||
let orig_arg = m.pop();
|
||||
let saved = m.pop_call_frame().expect("functor outer frame missing");
|
||||
m.set_env(saved.env);
|
||||
m.push(intermediate);
|
||||
// Call 2: intermediate(orig_arg). Resume to caller.
|
||||
m.call(reader, mc, orig_arg, saved.pc)
|
||||
}
|
||||
|
||||
pub fn call_pattern<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (func, attrset) = m.force_and_retry::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
|
||||
|
||||
let Closure {
|
||||
ip,
|
||||
n_locals,
|
||||
env,
|
||||
pattern,
|
||||
} = *func;
|
||||
let Some(pattern) = pattern else {
|
||||
unreachable!()
|
||||
};
|
||||
// TODO: get function name
|
||||
// TODO: param spans
|
||||
if !pattern.ellipsis {
|
||||
for key in pattern.required.iter().copied() {
|
||||
if attrset.lookup(key).is_none() {
|
||||
let name = ctx.resolve_string(key);
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"function 'anonymous lambda' called without required argument '{name}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
for &(key, _) in attrset.entries.iter() {
|
||||
let is_expected = pattern.required.contains(&key) || pattern.optional.contains(&key);
|
||||
if !is_expected {
|
||||
let name = ctx.resolve_string(key);
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"function 'anonymous lambda' called with unexpected argument '{name}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let new_env = Gc::new(
|
||||
mc,
|
||||
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
|
||||
);
|
||||
reader.set_pc(ip as usize);
|
||||
m.set_env(new_env);
|
||||
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
fn is_container(val: Value<'_>) -> bool {
|
||||
val.is::<AttrSet>() || val.is::<List<'_>>()
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
use fix_abstract_vm::{
|
||||
BytecodeReader, Machine, MachineExt, NixString, NixType, Path, Step, StrictValue, Value,
|
||||
VmRuntimeCtx,
|
||||
};
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use gc_arena::Mutation;
|
||||
|
||||
pub fn to_string<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
_ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
if val.is::<StringId>() || val.is::<NixString>() {
|
||||
return m.return_from_primop(val.relax(), reader);
|
||||
}
|
||||
if let Some(p) = val.as_inline::<Path>() {
|
||||
return m.return_from_primop(Value::new_inline(p.0), reader);
|
||||
}
|
||||
// TODO: derivations / `__toString` / `outPath`,
|
||||
// numbers, lists.
|
||||
m.finish_err(Error::eval_error(format!(
|
||||
"cannot coerce {} to a string",
|
||||
val.ty()
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn type_of<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let name: &str = match val.ty() {
|
||||
NixType::Int => "int",
|
||||
NixType::Float => "float",
|
||||
NixType::Bool => "bool",
|
||||
NixType::Null => "null",
|
||||
NixType::String => "string",
|
||||
NixType::Path => "path",
|
||||
NixType::AttrSet => "set",
|
||||
NixType::List => "list",
|
||||
NixType::Closure | NixType::PrimOp | NixType::PrimOpApp => "lambda",
|
||||
NixType::Thunk => unreachable!("forced"),
|
||||
};
|
||||
let sid = ctx.intern_string(name);
|
||||
m.return_from_primop(Value::new_inline(sid), reader)
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
use fix_abstract_vm::{
|
||||
AttrSet, BytecodeReader, CallFrame, List, Machine, MachineExt, NixNum, Null, Path, Step,
|
||||
StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
};
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use gc_arena::{Gc, Mutation};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub fn start_eq<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
lhs: StrictValue<'gc>,
|
||||
rhs: StrictValue<'gc>,
|
||||
negate: bool,
|
||||
) -> Step {
|
||||
match shallow_eq(ctx, lhs, rhs) {
|
||||
ShallowEq::True => {
|
||||
m.push(Value::new_inline(!negate));
|
||||
Step::Continue(())
|
||||
}
|
||||
ShallowEq::False => {
|
||||
m.push(Value::new_inline(negate));
|
||||
Step::Continue(())
|
||||
}
|
||||
ShallowEq::RecurseList(la, lb) => {
|
||||
let lhs_init: SmallVec<[Value<'gc>; 4]> = la.inner.borrow().iter().copied().collect();
|
||||
let rhs_init: SmallVec<[Value<'gc>; 4]> = lb.inner.borrow().iter().copied().collect();
|
||||
enter_eq_machine(m, reader, mc, negate, lhs_init, rhs_init)
|
||||
}
|
||||
ShallowEq::RecurseAttrs(a, b) => {
|
||||
let lhs_init: SmallVec<[Value<'gc>; 4]> =
|
||||
a.entries.iter().map(|&(_, v)| v).collect();
|
||||
let rhs_init: SmallVec<[Value<'gc>; 4]> =
|
||||
b.entries.iter().map(|&(_, v)| v).collect();
|
||||
enter_eq_machine(m, reader, mc, negate, lhs_init, rhs_init)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_step<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let rhs_q = m
|
||||
.peek(0)
|
||||
.as_gc::<List<'gc>>()
|
||||
.expect("eq state corrupted: rhs_queue");
|
||||
let lhs_q = m
|
||||
.peek(1)
|
||||
.as_gc::<List<'gc>>()
|
||||
.expect("eq state corrupted: lhs_queue");
|
||||
let result = m
|
||||
.peek(2)
|
||||
.as_inline::<bool>()
|
||||
.expect("eq state corrupted: result");
|
||||
|
||||
if !result || lhs_q.inner.borrow().is_empty() {
|
||||
return finalize(m, reader);
|
||||
}
|
||||
|
||||
let lhs = lhs_q
|
||||
.unlock(mc)
|
||||
.borrow_mut()
|
||||
.pop()
|
||||
.expect("non-empty lhs queue");
|
||||
let rhs = rhs_q
|
||||
.unlock(mc)
|
||||
.borrow_mut()
|
||||
.pop()
|
||||
.expect("non-empty rhs queue");
|
||||
m.push(lhs);
|
||||
m.push(rhs);
|
||||
reader.set_pc(PrimOpPhase::EqForce.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn eq_force<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (lhs, rhs) = m.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
apply_pair(m, ctx, mc, lhs, rhs);
|
||||
reader.set_pc(PrimOpPhase::EqStep.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
fn finalize<'gc, M: Machine<'gc>>(m: &mut M, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
let _ = m.pop();
|
||||
let _ = m.pop();
|
||||
let result = m
|
||||
.pop()
|
||||
.as_inline::<bool>()
|
||||
.expect("eq state corrupted: result");
|
||||
let negate = m
|
||||
.pop()
|
||||
.as_inline::<bool>()
|
||||
.expect("eq state corrupted: negate");
|
||||
m.return_from_primop(Value::new_inline(result ^ negate), reader)
|
||||
}
|
||||
|
||||
fn apply_pair<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
mc: &Mutation<'gc>,
|
||||
lhs: StrictValue<'gc>,
|
||||
rhs: StrictValue<'gc>,
|
||||
) {
|
||||
match shallow_eq(ctx, lhs, rhs) {
|
||||
ShallowEq::True => {}
|
||||
ShallowEq::False => {
|
||||
m.replace(2, Value::new_inline(false));
|
||||
}
|
||||
ShallowEq::RecurseList(la, lb) => {
|
||||
extend_queues(m, mc, la.inner.borrow().iter().copied(), lb.inner.borrow().iter().copied());
|
||||
}
|
||||
ShallowEq::RecurseAttrs(a, b) => {
|
||||
extend_queues(
|
||||
m,
|
||||
mc,
|
||||
a.entries.iter().map(|&(_, v)| v),
|
||||
b.entries.iter().map(|&(_, v)| v),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_queues<'gc, M, L, R>(m: &mut M, mc: &Mutation<'gc>, lhs_iter: L, rhs_iter: R)
|
||||
where
|
||||
M: Machine<'gc>,
|
||||
L: IntoIterator<Item = Value<'gc>>,
|
||||
R: IntoIterator<Item = Value<'gc>>,
|
||||
{
|
||||
let rhs_q = m
|
||||
.peek(0)
|
||||
.as_gc::<List<'gc>>()
|
||||
.expect("eq state corrupted: rhs_queue");
|
||||
let lhs_q = m
|
||||
.peek(1)
|
||||
.as_gc::<List<'gc>>()
|
||||
.expect("eq state corrupted: lhs_queue");
|
||||
let mut lq = lhs_q.unlock(mc).borrow_mut();
|
||||
let mut rq = rhs_q.unlock(mc).borrow_mut();
|
||||
for (x, y) in lhs_iter.into_iter().zip(rhs_iter) {
|
||||
lq.push(x);
|
||||
rq.push(y);
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_eq_machine<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
negate: bool,
|
||||
lhs_init: SmallVec<[Value<'gc>; 4]>,
|
||||
rhs_init: SmallVec<[Value<'gc>; 4]>,
|
||||
) -> Step {
|
||||
let resume_pc = reader.pc();
|
||||
m.push_call_frame(CallFrame {
|
||||
pc: resume_pc,
|
||||
thunk: None,
|
||||
env: m.env(),
|
||||
});
|
||||
m.inc_call_depth();
|
||||
m.push(Value::new_inline(negate));
|
||||
m.push(Value::new_inline(true));
|
||||
m.push(Value::new_gc(List::new(mc, lhs_init)));
|
||||
m.push(Value::new_gc(List::new(mc, rhs_init)));
|
||||
reader.set_pc(PrimOpPhase::EqStep.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
enum ShallowEq<'gc> {
|
||||
True,
|
||||
False,
|
||||
RecurseList(Gc<'gc, List<'gc>>, Gc<'gc, List<'gc>>),
|
||||
RecurseAttrs(Gc<'gc, AttrSet<'gc>>, Gc<'gc, AttrSet<'gc>>),
|
||||
}
|
||||
|
||||
fn shallow_eq<'gc>(
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
lhs: StrictValue<'gc>,
|
||||
rhs: StrictValue<'gc>,
|
||||
) -> ShallowEq<'gc> {
|
||||
if let (Some(a), Some(b)) = (lhs.as_num(), rhs.as_num()) {
|
||||
let eq = match (a, b) {
|
||||
(NixNum::Int(a), NixNum::Int(b)) => a == b,
|
||||
(NixNum::Float(a), NixNum::Float(b)) => a == b,
|
||||
(NixNum::Int(a), NixNum::Float(b)) => a as f64 == b,
|
||||
(NixNum::Float(a), NixNum::Int(b)) => a == b as f64,
|
||||
};
|
||||
return bool_outcome(eq);
|
||||
}
|
||||
if let (Some(a), Some(b)) = (lhs.as_inline::<bool>(), rhs.as_inline::<bool>()) {
|
||||
return bool_outcome(a == b);
|
||||
}
|
||||
if lhs.is::<Null>() && rhs.is::<Null>() {
|
||||
return ShallowEq::True;
|
||||
}
|
||||
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
|
||||
return bool_outcome(a.0 == b.0);
|
||||
}
|
||||
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
||||
return bool_outcome(a == b);
|
||||
}
|
||||
if let (Some(a), Some(b)) = (lhs.as_gc::<List<'gc>>(), rhs.as_gc::<List<'gc>>()) {
|
||||
if a.inner.borrow().len() != b.inner.borrow().len() {
|
||||
return ShallowEq::False;
|
||||
}
|
||||
return ShallowEq::RecurseList(a, b);
|
||||
}
|
||||
if let (Some(a), Some(b)) = (lhs.as_gc::<AttrSet<'gc>>(), rhs.as_gc::<AttrSet<'gc>>()) {
|
||||
let ae = &a.entries;
|
||||
let be = &b.entries;
|
||||
if ae.len() != be.len() {
|
||||
return ShallowEq::False;
|
||||
}
|
||||
for (l, r) in ae.iter().zip(be.iter()) {
|
||||
if l.0 != r.0 {
|
||||
return ShallowEq::False;
|
||||
}
|
||||
}
|
||||
return ShallowEq::RecurseAttrs(a, b);
|
||||
}
|
||||
ShallowEq::False
|
||||
}
|
||||
|
||||
fn bool_outcome<'gc>(b: bool) -> ShallowEq<'gc> {
|
||||
if b {
|
||||
ShallowEq::True
|
||||
} else {
|
||||
ShallowEq::False
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use fix_abstract_vm::{
|
||||
AttrSet, Break, BytecodeReader, CallFrame, Machine, MachineExt, Path, PendingLoad,
|
||||
PendingScope, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt, canon_path_str,
|
||||
};
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Gc, Mutation};
|
||||
use hashbrown::HashSet;
|
||||
|
||||
pub fn import<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [path]
|
||||
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let path_str = match ctx.get_string_or_path(path_val) {
|
||||
Some(s) => s.to_owned(),
|
||||
None => {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"expected a path or string, got {}",
|
||||
path_val.ty()
|
||||
)));
|
||||
}
|
||||
};
|
||||
let abs = match resolve_import_target(&path_str) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return m.finish_err(e),
|
||||
};
|
||||
|
||||
if let Some(cached) = m.import_cache_get(&abs) {
|
||||
return m.return_from_primop(cached, reader);
|
||||
}
|
||||
|
||||
// Stash the resolved path on the stack as a string-id so the
|
||||
// finalizer can use it as the cache key. The slot we pop here was
|
||||
// freed by `force_and_retry`, so we simply push.
|
||||
let path_sid = ctx.intern_string(abs.to_string_lossy());
|
||||
m.push(Value::new_inline(path_sid));
|
||||
let env = m.env();
|
||||
m.push_call_frame(CallFrame {
|
||||
pc: PrimOpPhase::ImportFinalize.ip() as usize,
|
||||
thunk: None,
|
||||
env,
|
||||
});
|
||||
|
||||
m.set_pending_load(PendingLoad {
|
||||
path: abs,
|
||||
scope: None,
|
||||
});
|
||||
Step::Break(Break::LoadFile)
|
||||
}
|
||||
|
||||
pub fn import_finalize<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
) -> Step {
|
||||
// stack: [path_sid, return_value]
|
||||
let val = m.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let path_sid = m.pop().as_inline::<StringId>().unwrap();
|
||||
// The cache key is keyed by the absolute path string we interned in
|
||||
// `import`. Resolve it back to the host PathBuf.
|
||||
let path_str = ctx.resolve_string(path_sid).to_owned();
|
||||
m.import_cache_insert(PathBuf::from(path_str), val);
|
||||
m.push(val);
|
||||
let Some(CallFrame {
|
||||
pc: ret_pc,
|
||||
thunk: _,
|
||||
env,
|
||||
}) = m.pop_call_frame()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
reader.set_pc(ret_pc);
|
||||
// FIXME:
|
||||
// m.dec_call_depth();
|
||||
m.set_env(env);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub fn scoped_import<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [scope, path]
|
||||
let (scope_attrs, path_val) = m.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
||||
let path_str = match ctx.get_string_or_path(path_val) {
|
||||
Some(s) => s.to_owned(),
|
||||
None => {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"expected a path or string, got {}",
|
||||
path_val.ty()
|
||||
)));
|
||||
}
|
||||
};
|
||||
let abs = match resolve_import_target(&path_str) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return m.finish_err(e),
|
||||
};
|
||||
|
||||
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 env = m.env();
|
||||
m.push_call_frame(CallFrame {
|
||||
pc: PrimOpPhase::ScopedImportFinalize.ip() as usize,
|
||||
thunk: None,
|
||||
env,
|
||||
});
|
||||
|
||||
m.set_pending_load(PendingLoad {
|
||||
path: abs,
|
||||
scope: Some(PendingScope { keys, slot_id }),
|
||||
});
|
||||
Step::Break(Break::LoadFile)
|
||||
}
|
||||
|
||||
pub fn scoped_import_finalize<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
_ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [return_value]
|
||||
// We intentionally do NOT pop the slot from `scope_slots` so that
|
||||
// closures or thunks created inside the imported file can still
|
||||
// resolve their scope after `scopedImport` returns.
|
||||
let val = m.pop();
|
||||
m.return_from_primop(val, reader)
|
||||
}
|
||||
|
||||
pub fn path_exists<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let path_val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
// pathExists requires an absolute path. A `Path` value is
|
||||
// always absolute; a string is accepted only if it starts with `/`.
|
||||
let (path, is_path_value) = if let Some(p) = path_val.as_inline::<Path>() {
|
||||
(ctx.resolve_string(p.0).to_owned(), true)
|
||||
} else if let Some(s) = ctx.get_string(path_val) {
|
||||
(s.to_owned(), false)
|
||||
} else {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"expected a path or string, got {}",
|
||||
path_val.ty()
|
||||
)));
|
||||
};
|
||||
if !is_path_value && !path.starts_with('/') {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"string '{path}' doesn't represent an absolute path"
|
||||
)));
|
||||
}
|
||||
// CppNix collapses consecutive slashes and resolves `.` / `..` lexically
|
||||
// before checking. Trailing-slash / trailing-dot mean "must be a directory".
|
||||
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
||||
let canon = canon_path_str(&path);
|
||||
let p = std::path::Path::new(&canon);
|
||||
let exists = if must_be_dir {
|
||||
std::fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
|
||||
} else {
|
||||
std::fs::symlink_metadata(p).is_ok()
|
||||
};
|
||||
m.return_from_primop(Value::new_inline(exists), reader)
|
||||
}
|
||||
|
||||
/// Convert the user-supplied path string into an absolute, dotted-segment
|
||||
/// resolved `PathBuf` and append `default.nix` if the target is a directory.
|
||||
fn resolve_import_target(path: &str) -> Result<PathBuf, Box<Error>> {
|
||||
let mut abs = PathBuf::from(path);
|
||||
if !abs.is_absolute() {
|
||||
return Err(Error::eval_error(format!(
|
||||
"import: expected an absolute path, got '{path}'"
|
||||
)));
|
||||
}
|
||||
if abs.is_dir() {
|
||||
abs.push("default.nix");
|
||||
}
|
||||
Ok(abs)
|
||||
}
|
||||
@@ -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:?}"),
|
||||
}
|
||||
}
|
||||
@@ -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,43 +0,0 @@
|
||||
use fix_abstract_vm::{
|
||||
BytecodeReader, Machine, MachineExt, Path, Step, StrictValue, Value, VmRuntimeCtx,
|
||||
VmRuntimeCtxExt, canon_path_str,
|
||||
};
|
||||
use fix_error::Error;
|
||||
use gc_arena::Mutation;
|
||||
|
||||
pub fn to_path<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// coerce to path THEN TO STRING
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
if let Some(Path(s)) = val.as_inline::<Path>() {
|
||||
return m.return_from_primop(Value::new_inline(s), reader);
|
||||
}
|
||||
let Some(s) = ctx.get_string(val) else {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"cannot coerce {} to a path",
|
||||
val.ty()
|
||||
)));
|
||||
};
|
||||
if !s.starts_with('/') {
|
||||
return m.finish_err(Error::eval_error(format!(
|
||||
"string '{s}' doesn't represent an absolute path"
|
||||
)));
|
||||
}
|
||||
let canon = canon_path_str(s);
|
||||
let sid = ctx.intern_string(canon);
|
||||
m.return_from_primop(Value::new_inline(sid), reader)
|
||||
}
|
||||
|
||||
pub fn is_path<'gc, M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = m.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let is_path = val.is::<Path>();
|
||||
m.return_from_primop(Value::new_inline(is_path), reader)
|
||||
}
|
||||
@@ -14,11 +14,8 @@ smallvec = { workspace = true }
|
||||
string-interner = { workspace = true }
|
||||
likely_stable = { workspace = true }
|
||||
sptr = "0.3"
|
||||
sysinfo = { version = "0.38", default-features = false, features = ["system"] }
|
||||
|
||||
fix-builtins = { path = "../fix-builtins" }
|
||||
fix-codegen = { path = "../fix-codegen" }
|
||||
fix-common = { path = "../fix-common" }
|
||||
fix-error = { path = "../fix-error" }
|
||||
fix-abstract-vm = { path = "../fix-abstract-vm" }
|
||||
fix-primops = { path = "../fix-primops" }
|
||||
|
||||
@@ -24,7 +24,7 @@ impl<T: Default + Copy, const N: usize> ArrayExt<N> for [T; N] {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RawStore: Sized {
|
||||
pub(crate) trait RawStore: Sized {
|
||||
fn to_val(self, value: &mut Value);
|
||||
fn from_val(value: &Value) -> Self;
|
||||
}
|
||||
@@ -157,24 +157,24 @@ enum TagVal {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct RawTag(TagVal);
|
||||
pub(crate) struct RawTag(TagVal);
|
||||
|
||||
impl RawTag {
|
||||
pub const P1: RawTag = RawTag(TagVal::_P1);
|
||||
pub const P2: RawTag = RawTag(TagVal::_P2);
|
||||
pub const P3: RawTag = RawTag(TagVal::_P3);
|
||||
pub const P4: RawTag = RawTag(TagVal::_P4);
|
||||
pub const P5: RawTag = RawTag(TagVal::_P5);
|
||||
pub const P6: RawTag = RawTag(TagVal::_P6);
|
||||
pub const P7: RawTag = RawTag(TagVal::_P7);
|
||||
pub(crate) const P1: RawTag = RawTag(TagVal::_P1);
|
||||
pub(crate) const P2: RawTag = RawTag(TagVal::_P2);
|
||||
pub(crate) const P3: RawTag = RawTag(TagVal::_P3);
|
||||
pub(crate) const P4: RawTag = RawTag(TagVal::_P4);
|
||||
pub(crate) const P5: RawTag = RawTag(TagVal::_P5);
|
||||
pub(crate) const P6: RawTag = RawTag(TagVal::_P6);
|
||||
pub(crate) const P7: RawTag = RawTag(TagVal::_P7);
|
||||
|
||||
pub const N1: RawTag = RawTag(TagVal::_N1);
|
||||
pub const N2: RawTag = RawTag(TagVal::_N2);
|
||||
pub const N3: RawTag = RawTag(TagVal::_N3);
|
||||
pub const N4: RawTag = RawTag(TagVal::_N4);
|
||||
pub const N5: RawTag = RawTag(TagVal::_N5);
|
||||
pub const N6: RawTag = RawTag(TagVal::_N6);
|
||||
pub const N7: RawTag = RawTag(TagVal::_N7);
|
||||
pub(crate) const N1: RawTag = RawTag(TagVal::_N1);
|
||||
pub(crate) const N2: RawTag = RawTag(TagVal::_N2);
|
||||
pub(crate) const N3: RawTag = RawTag(TagVal::_N3);
|
||||
pub(crate) const N4: RawTag = RawTag(TagVal::_N4);
|
||||
pub(crate) const N5: RawTag = RawTag(TagVal::_N5);
|
||||
pub(crate) const N6: RawTag = RawTag(TagVal::_N6);
|
||||
pub(crate) const N7: RawTag = RawTag(TagVal::_N7);
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@@ -260,7 +260,7 @@ impl RawTag {
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn neg_val(self) -> (bool, u8) {
|
||||
pub(crate) const fn neg_val(self) -> (bool, u8) {
|
||||
match self.0 {
|
||||
TagVal::_P1 => (false, 1),
|
||||
TagVal::_P2 => (false, 2),
|
||||
@@ -323,7 +323,7 @@ impl Header {
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[repr(C, align(8))]
|
||||
pub struct Value {
|
||||
pub(crate) struct Value {
|
||||
#[cfg(target_endian = "big")]
|
||||
header: Header,
|
||||
data: [u8; 6],
|
||||
@@ -373,7 +373,7 @@ impl Value {
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn data(&self) -> &[u8; 6] {
|
||||
pub(crate) fn data(&self) -> &[u8; 6] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use fix_codegen::OperandType;
|
||||
use fix_common::StringId;
|
||||
use num_enum::TryFromPrimitive;
|
||||
use string_interner::Symbol as _;
|
||||
|
||||
use crate::{OperandData, VmRuntimeCtx};
|
||||
use crate::{AttrKeyData, OperandData, VmRuntimeCtx};
|
||||
|
||||
pub struct BytecodeReader<'a> {
|
||||
pub(crate) 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 {
|
||||
#[cfg_attr(feature = "tailcall", allow(dead_code))]
|
||||
pub(crate) fn new(bytecode: &'a [u8], pc: usize) -> Self {
|
||||
Self {
|
||||
bytecode,
|
||||
pc,
|
||||
@@ -23,7 +22,8 @@ impl<'a> BytecodeReader<'a> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
|
||||
#[cfg_attr(not(feature = "tailcall"), allow(dead_code))]
|
||||
pub(crate) fn from_after_op(bytecode: &'a [u8], inst_start_pc: usize) -> Self {
|
||||
Self {
|
||||
bytecode,
|
||||
pc: inst_start_pc + 1,
|
||||
@@ -42,7 +42,8 @@ impl<'a> BytecodeReader<'a> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_op(&mut self) -> fix_codegen::Op {
|
||||
#[cfg_attr(feature = "tailcall", allow(dead_code))]
|
||||
pub(crate) fn read_op(&mut self) -> fix_codegen::Op {
|
||||
use fix_codegen::Op;
|
||||
self.inst_start_pc = self.pc;
|
||||
let byte = self.bytecode[self.pc];
|
||||
@@ -54,46 +55,46 @@ impl<'a> BytecodeReader<'a> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_u8(&mut self) -> u8 {
|
||||
pub(crate) 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 {
|
||||
pub(crate) fn read_u16(&mut self) -> u16 {
|
||||
u16::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_u32(&mut self) -> u32 {
|
||||
pub(crate) fn read_u32(&mut self) -> u32 {
|
||||
u32::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_i32(&mut self) -> i32 {
|
||||
pub(crate) fn read_i32(&mut self) -> i32 {
|
||||
i32::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_i64(&mut self) -> i64 {
|
||||
pub(crate) fn read_i64(&mut self) -> i64 {
|
||||
i64::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_f64(&mut self) -> f64 {
|
||||
pub(crate) fn read_f64(&mut self) -> f64 {
|
||||
f64::from_le_bytes(self.read_array())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read_string_id(&mut self) -> StringId {
|
||||
pub(crate) 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 {
|
||||
pub(crate) fn read_operand_data<C: VmRuntimeCtx>(&mut self, ctx: &C) -> OperandData {
|
||||
let tag = self.read_u8();
|
||||
let Ok(ty) = OperandType::try_from_primitive(tag)
|
||||
.map_err(|err| panic!("unknown operand tag: {:#04x}", err.number));
|
||||
@@ -102,41 +103,40 @@ impl<'a> BytecodeReader<'a> {
|
||||
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 }
|
||||
OperandType::BigInt => {
|
||||
let val = self.read_i64();
|
||||
OperandData::BigInt(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pc(&self) -> usize {
|
||||
#[inline(always)]
|
||||
pub(crate) fn read_attr_key_data(&mut self) -> crate::AttrKeyData {
|
||||
use fix_codegen::AttrKeyType;
|
||||
let tag = self.read_u8();
|
||||
let ty = AttrKeyType::try_from_primitive(tag)
|
||||
.unwrap_or_else(|err| panic!("unknown key tag: {:#04x}", err.number));
|
||||
match ty {
|
||||
AttrKeyType::Static => AttrKeyData::Static(self.read_string_id()),
|
||||
AttrKeyType::Dynamic => AttrKeyData::Dynamic,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pc(&self) -> usize {
|
||||
self.pc
|
||||
}
|
||||
|
||||
pub fn set_pc(&mut self, pc: usize) {
|
||||
pub(crate) fn set_pc(&mut self, pc: usize) {
|
||||
self.pc = pc;
|
||||
}
|
||||
|
||||
pub fn inst_start_pc(&self) -> usize {
|
||||
pub(crate) fn inst_start_pc(&self) -> usize {
|
||||
self.inst_start_pc
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use crate::{Break, BytecodeReader, Step, Vm, VmRuntimeCtx};
|
||||
pub(crate) enum TailResult {
|
||||
YieldFuel(u32),
|
||||
Done,
|
||||
LoadFile,
|
||||
}
|
||||
|
||||
pub(crate) type OpFn<'gc, C> = extern "rust-preserve-none" fn(
|
||||
@@ -38,7 +37,6 @@ macro_rules! tail_dispatch_after {
|
||||
($result:expr, $new_pc:expr, $vm:ident, $mc:ident, $ctx:ident, $bc:ident, $table:ident, $fuel:ident) => {{
|
||||
match $result {
|
||||
Step::Continue(()) | Step::Break(Break::Force) => {}
|
||||
Step::Break(Break::LoadFile) => return TailResult::LoadFile,
|
||||
Step::Break(Break::Done) => return TailResult::Done,
|
||||
}
|
||||
let new_pc: u32 = $new_pc;
|
||||
@@ -182,20 +180,22 @@ tail_fn!(op_jump_if_false, (reader, mc));
|
||||
tail_fn!(op_jump_if_true, (reader, mc));
|
||||
tail_fn!(op_jump, (reader));
|
||||
|
||||
tail_fn!(op_coerce_to_string, (reader, mc));
|
||||
|
||||
tail_fn!(op_concat_strings, (ctx, reader, mc));
|
||||
tail_fn!(op_resolve_path, (ctx, reader, mc));
|
||||
tail_fn!(op_resolve_path, (ctx));
|
||||
|
||||
tail_fn!(op_assert, (ctx, reader, mc));
|
||||
tail_fn!(op_assert, (reader));
|
||||
|
||||
tail_fn!(op_push_with, (ctx, reader, mc));
|
||||
tail_fn!(op_pop_with, ());
|
||||
tail_fn!(op_lookup_with, (ctx, reader, mc));
|
||||
tail_fn!(op_prepare_with, ());
|
||||
|
||||
tail_fn!(op_load_builtins, ());
|
||||
tail_fn!(op_load_builtin, (reader));
|
||||
|
||||
tail_fn!(op_mk_pos, (reader));
|
||||
tail_fn!(op_load_repl_binding, (reader));
|
||||
tail_fn!(op_load_scoped_binding, (ctx, reader, mc));
|
||||
tail_fn!(op_load_scoped_binding, (reader));
|
||||
|
||||
macro_rules! table {
|
||||
($($variant:ident => $fn:ident),* $(,)?) => {
|
||||
@@ -273,18 +273,20 @@ table! {
|
||||
JumpIfTrue => op_jump_if_true,
|
||||
Jump => op_jump,
|
||||
|
||||
CoerceToString => op_coerce_to_string,
|
||||
|
||||
ConcatStrings => op_concat_strings,
|
||||
ResolvePath => op_resolve_path,
|
||||
|
||||
Assert => op_assert,
|
||||
|
||||
PushWith => op_push_with,
|
||||
PopWith => op_pop_with,
|
||||
LookupWith => op_lookup_with,
|
||||
PrepareWith => op_prepare_with,
|
||||
|
||||
LoadBuiltins => op_load_builtins,
|
||||
LoadBuiltin => op_load_builtin,
|
||||
|
||||
MkPos => op_mk_pos,
|
||||
LoadReplBinding => op_load_repl_binding,
|
||||
LoadScopedBinding => op_load_scoped_binding,
|
||||
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
use fix_common::StringId;
|
||||
use gc_arena::{Gc, Mutation};
|
||||
|
||||
use crate::{
|
||||
AttrSet, Break, BytecodeReader, Closure, List, Machine, NixNum, NixString, NixType, Null,
|
||||
PrimOp, PrimOpApp, Step, StrictValue,
|
||||
};
|
||||
use crate::value::*;
|
||||
use crate::{Break, BytecodeReader, NixNum, Step, Vm};
|
||||
|
||||
pub trait Forced<'gc>: Sized {
|
||||
pub(crate) trait Forced<'gc>: Sized {
|
||||
const WIDTH: usize;
|
||||
|
||||
/// Force and type-check the `WIDTH` slots starting at `base_depth` from
|
||||
/// TOS, deepest-first. If a slot holds a thunk, enter it and return
|
||||
/// `Break::Force`. If a slot holds a value of the wrong type, call
|
||||
/// `finish_type_err` and return `Break::Done`.
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base_depth: usize,
|
||||
@@ -24,26 +22,26 @@ pub trait Forced<'gc>: Sized {
|
||||
/// After `force_and_check` returned `Continue`, pop `WIDTH` slots
|
||||
/// (TOS first) and convert. Type assertions are infallible because
|
||||
/// `force_and_check` already validated every slot.
|
||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self;
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self;
|
||||
}
|
||||
|
||||
impl<'gc> Forced<'gc> for StrictValue<'gc> {
|
||||
const WIDTH: usize = 1;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base_depth: usize,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)
|
||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||
m.pop_forced()
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
||||
vm.pop_forced()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,25 +52,25 @@ macro_rules! impl_forced_inline {
|
||||
const WIDTH: usize = 1;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base_depth: usize,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||
let v = m.peek_forced(base_depth);
|
||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||
let v = vm.peek_forced(base_depth);
|
||||
if v.as_inline::<$ty>().is_none() {
|
||||
let _: Step = m.finish_type_err($nix_ty, v.ty());
|
||||
let _: Step = vm.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()
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
||||
vm.pop_forced()
|
||||
.as_inline::<$ty>()
|
||||
.expect("type checked in force_and_check")
|
||||
}
|
||||
@@ -88,25 +86,25 @@ macro_rules! impl_forced_gc {
|
||||
const WIDTH: usize = 1;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base_depth: usize,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||
let v = m.peek_forced(base_depth);
|
||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||
let v = vm.peek_forced(base_depth);
|
||||
if v.as_gc::<$ty>().is_none() {
|
||||
let _: Step = m.finish_type_err($nix_ty, v.ty());
|
||||
let _: Step = vm.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()
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
||||
vm.pop_forced()
|
||||
.as_gc::<$ty>()
|
||||
.expect("type checked in force_and_check")
|
||||
}
|
||||
@@ -136,25 +134,25 @@ impl<'gc> Forced<'gc> for NixNum {
|
||||
const WIDTH: usize = 1;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base_depth: usize,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||
let v = m.peek_forced(base_depth);
|
||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||
let v = vm.peek_forced(base_depth);
|
||||
if v.as_num().is_none() {
|
||||
let _: Step = m.finish_type_err(NixType::Int, v.ty());
|
||||
let _: Step = vm.finish_type_err(NixType::Int, v.ty());
|
||||
return Step::Break(Break::Done);
|
||||
}
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||
m.pop_forced()
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
||||
vm.pop_forced()
|
||||
.as_num()
|
||||
.expect("type checked in force_and_check")
|
||||
}
|
||||
@@ -164,25 +162,25 @@ impl<'gc> Forced<'gc> for f64 {
|
||||
const WIDTH: usize = 1;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base_depth: usize,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
m.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||
let v = m.peek_forced(base_depth);
|
||||
vm.force_slot_to_pc(base_depth, reader, mc, resume_pc)?;
|
||||
let v = vm.peek_forced(base_depth);
|
||||
if v.as_float().is_none() {
|
||||
let _: Step = m.finish_type_err(NixType::Float, v.ty());
|
||||
let _: Step = vm.finish_type_err(NixType::Float, v.ty());
|
||||
return Step::Break(Break::Done);
|
||||
}
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||
m.pop_forced()
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
||||
vm.pop_forced()
|
||||
.as_float()
|
||||
.expect("type checked in force_and_check")
|
||||
}
|
||||
@@ -192,21 +190,21 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>> Forced<'gc> for (A, B) {
|
||||
const WIDTH: usize = A::WIDTH + B::WIDTH;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base: usize,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
A::force_and_check(m, reader, mc, base + B::WIDTH, resume_pc)?;
|
||||
B::force_and_check(m, reader, mc, base, resume_pc)
|
||||
A::force_and_check(vm, reader, mc, base + B::WIDTH, resume_pc)?;
|
||||
B::force_and_check(vm, reader, mc, base, resume_pc)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||
let b = B::pop_converted(m);
|
||||
let a = A::pop_converted(m);
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
||||
let b = B::pop_converted(vm);
|
||||
let a = A::pop_converted(vm);
|
||||
(a, b)
|
||||
}
|
||||
}
|
||||
@@ -215,23 +213,23 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>> Forced<'gc> for (A, B,
|
||||
const WIDTH: usize = A::WIDTH + B::WIDTH + C::WIDTH;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base: usize,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
A::force_and_check(m, reader, mc, base + B::WIDTH + C::WIDTH, resume_pc)?;
|
||||
B::force_and_check(m, reader, mc, base + C::WIDTH, resume_pc)?;
|
||||
C::force_and_check(m, reader, mc, base, resume_pc)
|
||||
A::force_and_check(vm, reader, mc, base + B::WIDTH + C::WIDTH, resume_pc)?;
|
||||
B::force_and_check(vm, reader, mc, base + C::WIDTH, resume_pc)?;
|
||||
C::force_and_check(vm, reader, mc, base, resume_pc)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||
let c = C::pop_converted(m);
|
||||
let b = B::pop_converted(m);
|
||||
let a = A::pop_converted(m);
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
||||
let c = C::pop_converted(vm);
|
||||
let b = B::pop_converted(vm);
|
||||
let a = A::pop_converted(vm);
|
||||
(a, b, c)
|
||||
}
|
||||
}
|
||||
@@ -242,31 +240,31 @@ impl<'gc, A: Forced<'gc>, B: Forced<'gc>, C: Forced<'gc>, D: Forced<'gc>> Forced
|
||||
const WIDTH: usize = A::WIDTH + B::WIDTH + C::WIDTH + D::WIDTH;
|
||||
|
||||
#[inline(always)]
|
||||
fn force_and_check<M: Machine<'gc>>(
|
||||
m: &mut M,
|
||||
fn force_and_check(
|
||||
vm: &mut Vm<'gc>,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
base: usize,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
A::force_and_check(
|
||||
m,
|
||||
vm,
|
||||
reader,
|
||||
mc,
|
||||
base + B::WIDTH + C::WIDTH + D::WIDTH,
|
||||
resume_pc,
|
||||
)?;
|
||||
B::force_and_check(m, reader, mc, base + C::WIDTH + D::WIDTH, resume_pc)?;
|
||||
C::force_and_check(m, reader, mc, base + D::WIDTH, resume_pc)?;
|
||||
D::force_and_check(m, reader, mc, base, resume_pc)
|
||||
B::force_and_check(vm, reader, mc, base + C::WIDTH + D::WIDTH, resume_pc)?;
|
||||
C::force_and_check(vm, reader, mc, base + D::WIDTH, resume_pc)?;
|
||||
D::force_and_check(vm, reader, mc, base, resume_pc)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_converted<M: Machine<'gc>>(m: &mut M) -> Self {
|
||||
let d = D::pop_converted(m);
|
||||
let c = C::pop_converted(m);
|
||||
let b = B::pop_converted(m);
|
||||
let a = A::pop_converted(m);
|
||||
fn pop_converted(vm: &mut Vm<'gc>) -> Self {
|
||||
let d = D::pop_converted(vm);
|
||||
let c = C::pop_converted(vm);
|
||||
let b = B::pop_converted(vm);
|
||||
let a = A::pop_converted(vm);
|
||||
(a, b, c, d)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use fix_error::Error;
|
||||
|
||||
use crate::VmError;
|
||||
|
||||
pub(crate) fn vm_err(msg: impl Into<String>) -> VmError {
|
||||
VmError::Uncatchable(Error::eval_error(msg.into()))
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use fix_abstract_vm::*;
|
||||
use gc_arena::{Gc, Mutation, RefLock};
|
||||
|
||||
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx};
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
@@ -13,33 +13,9 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
// if the LHS is a path, the result is a path obtained by
|
||||
// canonicalizing the concatenated string. RHS may be a path or a
|
||||
// string. (A `string + path` keeps the string-typed result, handled
|
||||
// by the next branch.)
|
||||
if lhs.is::<Path>() {
|
||||
let (Some(ls), Some(rs)) = (ctx.get_string_or_path(lhs), ctx.get_string_or_path(rhs))
|
||||
else {
|
||||
return self.finish_err(fix_error::Error::eval_error(format!(
|
||||
"cannot append {} to a path",
|
||||
rhs.ty()
|
||||
)));
|
||||
};
|
||||
let combined = format!("{ls}{rs}");
|
||||
let canon = canon_path_str(&combined);
|
||||
let sid = ctx.intern_string(canon);
|
||||
self.push(Value::new_inline(fix_abstract_vm::Path(sid)));
|
||||
return Step::Continue(());
|
||||
}
|
||||
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
|
||||
let merged = ctx
|
||||
.get_string_context(lhs)
|
||||
.merge(ctx.get_string_context(rhs));
|
||||
let ns = Gc::new(
|
||||
mc,
|
||||
crate::NixString::with_context(format!("{ls}{rs}"), merged),
|
||||
);
|
||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
||||
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
|
||||
self.push(Value::new_gc(ns));
|
||||
return Step::Continue(());
|
||||
}
|
||||
@@ -71,7 +47,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
int_op: fn(i64, i64) -> i64,
|
||||
float_op: fn(f64, f64) -> f64,
|
||||
) -> Step {
|
||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
let res = numeric_binop(lhs, rhs, mc, int_op, float_op);
|
||||
match res {
|
||||
Ok(val) => {
|
||||
@@ -84,7 +60,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
|
||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
match (get_num(lhs), get_num(rhs)) {
|
||||
(_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => {
|
||||
return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error(
|
||||
@@ -110,8 +86,13 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, false)
|
||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
let eq = match self.values_equal(ctx, lhs, rhs) {
|
||||
Ok(eq) => eq,
|
||||
Err(e) => return self.finish_vm_err(e),
|
||||
};
|
||||
self.push(Value::new_inline(eq));
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -121,8 +102,13 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, true)
|
||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
let eq = match self.values_equal(ctx, lhs, rhs) {
|
||||
Ok(eq) => eq,
|
||||
Err(e) => return self.finish_vm_err(e),
|
||||
};
|
||||
self.push(Value::new_inline(!eq));
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -172,7 +158,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
mc: &Mutation<'gc>,
|
||||
pred: fn(Ordering) -> bool,
|
||||
) -> Step {
|
||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
match self.compare_values_inner(ctx, pred, lhs, rhs) {
|
||||
Ok(()) => Step::Continue(()),
|
||||
Err(e) => self.finish_vm_err(e),
|
||||
@@ -185,7 +171,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (l, r) = self.force_and_retry::<(Gc<List>, Gc<List>)>(reader, mc)?;
|
||||
let (l, r) = self.try_force::<(Gc<List>, Gc<List>)>(reader, mc)?;
|
||||
let mut items = smallvec::SmallVec::new();
|
||||
items.extend_from_slice(&l.inner.borrow());
|
||||
items.extend_from_slice(&r.inner.borrow());
|
||||
@@ -204,14 +190,14 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (l, r) = self.force_and_retry::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
|
||||
let (l, r) = self.try_force::<(Gc<AttrSet>, Gc<AttrSet>)>(reader, mc)?;
|
||||
self.push(Value::new_gc(l.merge(&r, mc)));
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_neg(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
|
||||
let rhs = self.force_and_retry::<NixNum>(reader, mc)?;
|
||||
let rhs = self.try_force::<NixNum>(reader, mc)?;
|
||||
match rhs {
|
||||
NixNum::Int(int) => self.push(Value::make_int(-int, mc)),
|
||||
NixNum::Float(float) => self.push(Value::new_float(-float)),
|
||||
@@ -221,11 +207,68 @@ impl<'gc> crate::Vm<'gc> {
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_not(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step {
|
||||
let rhs = self.force_and_retry::<bool>(reader, mc)?;
|
||||
let rhs = self.try_force::<bool>(reader, mc)?;
|
||||
self.push(Value::new_inline(!rhs));
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn values_equal(
|
||||
&mut self,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
lhs: StrictValue<'gc>,
|
||||
rhs: StrictValue<'gc>,
|
||||
) -> crate::VmResult<bool> {
|
||||
if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) {
|
||||
return Ok(match (a, b) {
|
||||
(NixNum::Int(a), NixNum::Int(b)) => a == b,
|
||||
(NixNum::Float(a), NixNum::Float(b)) => a == b,
|
||||
(NixNum::Int(a), NixNum::Float(b)) => a as f64 == b,
|
||||
(NixNum::Float(a), NixNum::Int(b)) => a == b as f64,
|
||||
});
|
||||
}
|
||||
if let (Some(a), Some(b)) = (lhs.as_inline::<bool>(), rhs.as_inline::<bool>()) {
|
||||
return Ok(a == b);
|
||||
}
|
||||
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
|
||||
return Ok(true);
|
||||
}
|
||||
if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) {
|
||||
return Ok(a == b);
|
||||
}
|
||||
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::List>(), rhs.as_gc::<crate::List>()) {
|
||||
if a.inner.borrow().len() != b.inner.borrow().len() {
|
||||
return Ok(false);
|
||||
}
|
||||
for (x, y) in a.inner.borrow().iter().zip(b.inner.borrow().iter()) {
|
||||
let lx = x.restrict().expect("forced");
|
||||
let ly = y.restrict().expect("forced");
|
||||
if !self.values_equal(ctx, lx, ly)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
if let (Some(a), Some(b)) = (lhs.as_gc::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
|
||||
let a = a.entries.borrow();
|
||||
let b = b.entries.borrow();
|
||||
if a.len() != b.len() {
|
||||
return Ok(false);
|
||||
}
|
||||
for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()) {
|
||||
if k1 != k2 {
|
||||
return Ok(false);
|
||||
}
|
||||
let lv1 = v1.restrict().expect("forced");
|
||||
let lv2 = v2.restrict().expect("forced");
|
||||
if !self.values_equal(ctx, lv1, lv2)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn compare_values_inner(
|
||||
&mut self,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
@@ -251,12 +294,6 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.push(Value::new_inline(pred(a.cmp(b))));
|
||||
return Ok(());
|
||||
}
|
||||
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
|
||||
let a = ctx.resolve_string(a.0);
|
||||
let b = ctx.resolve_string(b.0);
|
||||
self.push(Value::new_inline(pred(a.cmp(b))));
|
||||
return Ok(());
|
||||
}
|
||||
// TODO: compare other types
|
||||
Err(crate::vm_err("cannot compare these types"))
|
||||
}
|
||||
@@ -289,9 +326,6 @@ fn numeric_binop<'gc>(
|
||||
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
|
||||
Ok(Value::new_float(float_op(a, b as f64)))
|
||||
}
|
||||
_ => Err(crate::vm_err(format!(
|
||||
"cannot perform arithmetic on non-numbers: {:?}",
|
||||
(lhs.ty(), rhs.ty())
|
||||
))),
|
||||
_ => Err(crate::vm_err("cannot perform arithmetic on non-numbers")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Gc, Mutation, RefLock};
|
||||
use num_enum::TryFromPrimitive as _;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, CallFrame, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||
|
||||
impl<'gc> Vm<'gc> {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn op_dispatch_primop(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
use PrimOpPhase::*;
|
||||
let phase_disc = reader.read_u8();
|
||||
let Ok(phase) = PrimOpPhase::try_from_primitive(phase_disc) else {
|
||||
return self.finish_err(Error::eval_error("invalid primop phase"));
|
||||
};
|
||||
match phase {
|
||||
Abort => self.primop_abort(ctx, reader, mc),
|
||||
|
||||
DeepSeq => self.primop_deep_seq_force_top(reader, mc),
|
||||
DeepSeqPush => self.primop_deep_seq_push(reader, mc),
|
||||
DeepSeqLoop => self.primop_deep_seq_loop(reader, mc),
|
||||
Seq => self.primop_seq(reader, mc),
|
||||
|
||||
FilterForceList => self.primop_filter_force_list(reader, mc),
|
||||
FilterCallPred => self.primop_filter_call_pred(reader, mc),
|
||||
FilterCheck => self.primop_filter_check(reader, mc),
|
||||
|
||||
ForceResultShallow => self.primop_force_result_shallow(ctx, reader, mc),
|
||||
ForceResultShallowPush => self.primop_force_result_shallow_push(ctx, reader, mc),
|
||||
ForceResultShallowLoop => self.primop_force_result_shallow_loop(reader, mc),
|
||||
ForceResultDeepFinish => self.primop_force_result_deep_finish(ctx, reader, mc),
|
||||
|
||||
CallPattern => self.primop_call_pattern(ctx, reader, mc),
|
||||
|
||||
phase => todo!("primop phase {phase:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn return_from_primop(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
let Some(CallFrame {
|
||||
pc: ret_pc,
|
||||
stack_depth: _,
|
||||
thunk: _,
|
||||
env,
|
||||
with_env,
|
||||
}) = self.call_stack.pop()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
reader.set_pc(ret_pc);
|
||||
self.call_depth -= 1;
|
||||
self.env = env;
|
||||
self.with_env = with_env;
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_filter_force_list(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
self.force_slot(0, reader, mc)?;
|
||||
let list = match self.peek_forced(0).expect_gc::<List>() {
|
||||
Ok(list) => list,
|
||||
Err(got) => return self.finish_type_err(NixType::List, got),
|
||||
};
|
||||
if list.inner.borrow().is_empty() {
|
||||
return self.return_from_primop(reader);
|
||||
}
|
||||
// prepare stack layout: [ pred list idx acc ]
|
||||
self.push(Value::new_inline(0));
|
||||
self.push(Value::new_gc(List::new_gc(mc)));
|
||||
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_filter_call_pred(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
self.force_slot(3, reader, mc)?;
|
||||
let pred = self.peek_forced(3);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let elem = self.peek_forced(2).as_gc::<List>().unwrap().inner.borrow()[idx as usize];
|
||||
self.push(pred.relax());
|
||||
self.call(reader, mc, elem, PrimOpPhase::FilterCheck.ip() as usize)
|
||||
}
|
||||
|
||||
pub(crate) fn primop_filter_check(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let ret = self.try_force::<bool>(reader, mc)?;
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let list = self.peek_forced(2).as_gc::<List>().unwrap();
|
||||
let list = list.inner.borrow();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let acc = self.peek_forced(0).as_gc::<List>().unwrap();
|
||||
if ret {
|
||||
let mut acc = acc.unlock(mc).borrow_mut();
|
||||
acc.push(list[idx as usize]);
|
||||
}
|
||||
if idx as usize == list.len() - 1 {
|
||||
let acc = self.pop();
|
||||
let _ = self.pop(); // idx
|
||||
let _ = self.pop(); // list
|
||||
let _ = self.pop(); // pred
|
||||
self.push(acc);
|
||||
return self.return_from_primop(reader);
|
||||
}
|
||||
self.replace(1, Value::new_inline(idx + 1));
|
||||
reader.set_pc(PrimOpPhase::FilterCallPred.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_seq(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
self.force_slot(1, reader, mc)?;
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
self.return_from_primop(reader)
|
||||
}
|
||||
|
||||
pub(crate) fn primop_abort(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [msg] - force msg, then abort with it
|
||||
self.force_slot(0, reader, mc)?;
|
||||
let msg_val = self.peek_forced(0);
|
||||
let msg = ctx.get_string(msg_val).unwrap_or("<non-string-value>");
|
||||
self.finish_err(Error::eval_error(format!(
|
||||
"evaluation aborted with the following error message: '{msg}'"
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_force_top(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e1, e2] - force e1, return e2
|
||||
self.force_slot(1, reader, mc)?;
|
||||
|
||||
let e1 = self.peek_forced(1);
|
||||
|
||||
let children: SmallVec<_> = if let Some(attrs) = e1.as_gc::<AttrSet>() {
|
||||
let attrs = attrs.entries.borrow();
|
||||
if attrs.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
attrs.iter().map(|&(_, v)| v).collect()
|
||||
}
|
||||
} else if let Some(list) = e1.as_gc::<List<'gc>>() {
|
||||
let inner = list.inner.borrow();
|
||||
if inner.is_empty() {
|
||||
SmallVec::new()
|
||||
} else {
|
||||
inner.iter().copied().collect()
|
||||
}
|
||||
} else {
|
||||
SmallVec::new()
|
||||
};
|
||||
|
||||
if children.is_empty() {
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
return self.return_from_primop(reader);
|
||||
}
|
||||
|
||||
let count = children.len() as i32;
|
||||
let seen: Gc<'gc, List<'gc>> = Gc::new(mc, List::default());
|
||||
let worklist: Gc<'gc, List<'gc>> = List::new(mc, children);
|
||||
|
||||
let e2 = self.pop();
|
||||
let _ = self.pop();
|
||||
self.push(e2);
|
||||
self.push(Value::new_gc(seen));
|
||||
self.push(Value::new_gc(worklist));
|
||||
self.push(Value::new_inline(count));
|
||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_push(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack: [e2, seen, worklist, counter]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
||||
if counter == 0 {
|
||||
let _ = self.pop(); // counter
|
||||
let _ = self.pop(); // worklist
|
||||
let _ = self.pop(); // seen
|
||||
return self.return_from_primop(reader);
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let item = worklist.unlock(mc).borrow_mut().pop().unwrap();
|
||||
self.replace(0, Value::new_inline(counter - 1));
|
||||
self.push(item);
|
||||
|
||||
// force item at TOS, resume at DeepSeqLoop after force
|
||||
self.force_slot_to_pc(0, reader, mc, PrimOpPhase::DeepSeqLoop.ip() as usize)?;
|
||||
reader.set_pc(PrimOpPhase::DeepSeqLoop.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_deep_seq_loop(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
// stack after pop: [e2, seen, worklist, counter]
|
||||
let item = self.pop();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = self.peek(0).as_inline::<i32>().unwrap();
|
||||
|
||||
let mut added: usize = 0;
|
||||
if let Some(attrs) = item.as_gc::<AttrSet>() {
|
||||
let attrs = attrs.entries.borrow();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||
if !self.is_value_in_seen(seen, item) {
|
||||
self.add_value_to_seen(seen, mc, item);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
{
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &(_, v) in attrs.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = attrs.len();
|
||||
}
|
||||
}
|
||||
} else if let Some(list) = item.as_gc::<List<'gc>>() {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let seen = self.peek_forced(2).as_gc::<List<'gc>>().unwrap();
|
||||
if !self.is_value_in_seen(seen, item) {
|
||||
self.add_value_to_seen(seen, mc, item);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let worklist = self.peek_forced(1).as_gc::<List<'gc>>().unwrap();
|
||||
{
|
||||
let inner = list.inner.borrow();
|
||||
let mut wl = worklist.unlock(mc).borrow_mut();
|
||||
for &v in inner.iter() {
|
||||
wl.push(v);
|
||||
}
|
||||
added = inner.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.replace(0, Value::new_inline(counter + added as i32));
|
||||
reader.set_pc(PrimOpPhase::DeepSeqPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_force_result_shallow(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
self.force_slot(0, reader, mc)?;
|
||||
let val = self.peek_forced(0);
|
||||
|
||||
let (count, has_children) = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
let len = attrs.entries.borrow().len();
|
||||
(len, len > 0)
|
||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||
let len = list.inner.borrow().len();
|
||||
(len, len > 0)
|
||||
} else {
|
||||
(0, false)
|
||||
};
|
||||
|
||||
if !has_children {
|
||||
let val = self.pop();
|
||||
return self.finish_ok(ctx.convert_value(val));
|
||||
}
|
||||
|
||||
self.push(Value::new_inline(0i32));
|
||||
self.push(Value::new_inline(count as i32));
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_force_result_shallow_push(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let idx = self.peek(1).as_inline::<i32>().unwrap();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let len = self.peek(0).as_inline::<i32>().unwrap();
|
||||
|
||||
if idx == len {
|
||||
let _ = self.pop(); // len
|
||||
let _ = self.pop(); // idx
|
||||
let val = self.pop();
|
||||
return self.finish_ok(ctx.convert_value(val));
|
||||
}
|
||||
|
||||
let val = self.peek_forced(2);
|
||||
let child = if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
attrs.entries.borrow().get(idx as usize).map(|&(_, v)| v)
|
||||
} else if let Some(list) = val.as_gc::<List<'gc>>() {
|
||||
list.inner.borrow().get(idx as usize).copied()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(child) = child {
|
||||
self.replace(1, Value::new_inline(idx + 1));
|
||||
self.push(child);
|
||||
self.force_slot_to_pc(
|
||||
0,
|
||||
reader,
|
||||
mc,
|
||||
PrimOpPhase::ForceResultShallowLoop.ip() as usize,
|
||||
)?;
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowLoop.ip() as usize);
|
||||
}
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_force_result_shallow_loop(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
_mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let _ = self.pop(); // forced child
|
||||
reader.set_pc(PrimOpPhase::ForceResultShallowPush.ip() as usize);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
pub(crate) fn primop_force_result_deep_finish(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = self.try_force::<StrictValue>(reader, mc)?;
|
||||
self.finish_ok(ctx.convert_value(val.relax()))
|
||||
}
|
||||
|
||||
pub(crate) fn primop_call_pattern(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (func, attrset) = self.try_force::<(Gc<Closure>, Gc<AttrSet>)>(reader, mc)?;
|
||||
|
||||
let Closure {
|
||||
ip,
|
||||
n_locals,
|
||||
env,
|
||||
pattern,
|
||||
} = *func;
|
||||
let Some(pattern) = pattern else {
|
||||
unreachable!()
|
||||
};
|
||||
// TODO: get function name
|
||||
// TODO: param spans
|
||||
if !pattern.ellipsis {
|
||||
for key in pattern.required.iter().copied() {
|
||||
if attrset.lookup(key).is_none() {
|
||||
let name = ctx.resolve_string(key);
|
||||
return self.finish_err(Error::eval_error(format!(
|
||||
"function 'anonymous lambda' called without required argument '{name}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
for &(key, _) in attrset.entries.borrow().iter() {
|
||||
let is_expected =
|
||||
pattern.required.contains(&key) || pattern.optional.contains(&key);
|
||||
if !is_expected {
|
||||
let name = ctx.resolve_string(key);
|
||||
return self.finish_err(Error::eval_error(format!(
|
||||
"function 'anonymous lambda' called with unexpected argument '{name}'"
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let new_env = Gc::new(
|
||||
mc,
|
||||
RefLock::new(Env::with_arg(Value::new_gc(attrset), n_locals, env)),
|
||||
);
|
||||
reader.set_pc(ip as usize);
|
||||
self.env = new_env;
|
||||
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
fn is_value_in_seen(&self, seen: Gc<'gc, List<'gc>>, val: Value<'gc>) -> bool {
|
||||
if !is_container(val) {
|
||||
return false;
|
||||
}
|
||||
let target = val.to_bits();
|
||||
for &v in seen.inner.borrow().iter() {
|
||||
if v.to_bits() == target {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn add_value_to_seen(&self, seen: Gc<'gc, List<'gc>>, mc: &Mutation<'gc>, val: Value<'gc>) {
|
||||
if is_container(val) {
|
||||
seen.unlock(mc).borrow_mut().push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_container(val: Value<'_>) -> bool {
|
||||
val.is::<AttrSet>() || val.is::<List<'_>>()
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use fix_abstract_vm::{resolve_operand, *};
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Gc, Mutation, RefLock};
|
||||
|
||||
use crate::value::*;
|
||||
use crate::{
|
||||
BytecodeReader, CallFrame, Closure, Env, ForceMode, Step, ThunkState, VmRuntimeCtx,
|
||||
VmRuntimeCtxExt,
|
||||
@@ -17,7 +17,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
arg: Value<'gc>,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
let func = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let func = self.try_force::<StrictValue>(reader, mc)?;
|
||||
if self.call_depth > 10000 {
|
||||
return self.finish_err(Error::eval_error("stack overflow; max-call-depth exceeded"));
|
||||
}
|
||||
@@ -29,8 +29,10 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.push(arg);
|
||||
self.call_stack.push(CallFrame {
|
||||
pc: resume_pc,
|
||||
stack_depth: 0,
|
||||
thunk: None,
|
||||
env: self.env,
|
||||
with_env: self.with_env,
|
||||
});
|
||||
reader.set_pc(PrimOpPhase::CallPattern.ip() as usize);
|
||||
return Step::Continue(());
|
||||
@@ -42,8 +44,10 @@ impl<'gc> crate::Vm<'gc> {
|
||||
let new_env = Gc::new(mc, RefLock::new(Env::with_arg(arg, n_locals, env)));
|
||||
self.call_stack.push(CallFrame {
|
||||
pc: resume_pc,
|
||||
stack_depth: 0,
|
||||
thunk: None,
|
||||
env: self.env,
|
||||
with_env: self.with_env,
|
||||
});
|
||||
reader.set_pc(ip as usize);
|
||||
self.env = new_env;
|
||||
@@ -52,8 +56,10 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.push(arg);
|
||||
self.call_stack.push(CallFrame {
|
||||
pc: resume_pc,
|
||||
stack_depth: 0,
|
||||
thunk: None,
|
||||
env: self.env,
|
||||
with_env: self.with_env,
|
||||
});
|
||||
reader.set_pc(primop.dispatch_ip as usize)
|
||||
} else {
|
||||
@@ -72,43 +78,21 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.push(arg);
|
||||
self.call_stack.push(CallFrame {
|
||||
pc: resume_pc,
|
||||
stack_depth: 0,
|
||||
thunk: None,
|
||||
env: self.env,
|
||||
with_env: self.with_env,
|
||||
});
|
||||
reader.set_pc(app.primop.dispatch_ip as usize)
|
||||
} else {
|
||||
let position = (app.primop.arity - app.arity) as usize;
|
||||
let mut new_app = PrimOpApp {
|
||||
let new_app = PrimOpApp {
|
||||
arity: app.arity - 1,
|
||||
..*app
|
||||
};
|
||||
new_app.args[position] = arg;
|
||||
self.push(Value::new_gc(Gc::new(mc, new_app)))
|
||||
}
|
||||
} else if let Some(attrs) = func.as_gc::<AttrSet>()
|
||||
&& let Some(functor) = attrs.lookup(self.functor_sym)
|
||||
{
|
||||
// f arg => (f.__functor f) arg
|
||||
//
|
||||
// Stage the work for `CallFunctor1` so retries during force are
|
||||
// safe: the stack invariant `[..., orig_arg, self, functor]`
|
||||
// holds every time control re-enters phase 1.
|
||||
self.call_depth -= 1;
|
||||
self.call_stack.push(CallFrame {
|
||||
pc: resume_pc,
|
||||
thunk: None,
|
||||
env: self.env,
|
||||
});
|
||||
self.push(arg);
|
||||
self.push(func.relax());
|
||||
self.push(functor);
|
||||
reader.set_pc(PrimOpPhase::CallFunctor1.ip() as usize);
|
||||
return Step::Continue(());
|
||||
} else {
|
||||
return self.finish_err(Error::eval_error(format!(
|
||||
"attempt to call something which is not a function but {}",
|
||||
func.ty()
|
||||
)));
|
||||
todo!("call other types: {func:?}")
|
||||
}
|
||||
Step::Continue(())
|
||||
}
|
||||
@@ -120,7 +104,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let arg = resolve_operand(&reader.read_operand_data(ctx), mc, self);
|
||||
let arg = reader.read_operand_data(ctx).resolve(mc, self);
|
||||
let pc = reader.pc();
|
||||
self.call(reader, mc, arg, pc)
|
||||
}
|
||||
@@ -132,11 +116,13 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let val = self.try_force::<StrictValue>(reader, mc)?;
|
||||
let Some(CallFrame {
|
||||
pc: ret_pc,
|
||||
stack_depth,
|
||||
thunk,
|
||||
env,
|
||||
with_env,
|
||||
}) = self.call_stack.pop()
|
||||
else {
|
||||
match self.force_mode {
|
||||
@@ -151,8 +137,10 @@ impl<'gc> crate::Vm<'gc> {
|
||||
self.push(val.relax());
|
||||
self.call_stack.push(CallFrame {
|
||||
pc: PrimOpPhase::ForceResultDeepFinish.ip() as usize,
|
||||
stack_depth: 0,
|
||||
thunk: None,
|
||||
env: self.env,
|
||||
with_env: self.with_env,
|
||||
});
|
||||
self.call_depth += 1;
|
||||
reader.set_pc(PrimOpPhase::DeepSeq.ip() as usize);
|
||||
@@ -163,21 +151,13 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader.set_pc(ret_pc);
|
||||
if let Some(outer_thunk) = thunk {
|
||||
*outer_thunk.borrow_mut(mc) = ThunkState::Evaluated(val);
|
||||
self.replace(stack_depth, val.relax());
|
||||
} else {
|
||||
self.call_depth -= 1;
|
||||
self.push(val.relax())
|
||||
}
|
||||
self.env = env;
|
||||
self.with_env = with_env;
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_dispatch_primop(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
fix_primops::dispatch_primop(self, ctx, reader, mc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
RefLock::new(ThunkState::Pending {
|
||||
ip: entry_point as usize,
|
||||
env: self.env,
|
||||
with_env: self.with_env,
|
||||
}),
|
||||
);
|
||||
self.push(Value::new_gc(thunk));
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use fix_abstract_vm::{NixType, resolve_operand};
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use gc_arena::{Gc, RefLock};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::value::NixType;
|
||||
use crate::{
|
||||
AttrSet, BytecodeReader, List, Step, StrictValue, Value, VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
AttrKeyData, AttrSet, BytecodeReader, List, OperandData, Step, StrictValue, Value,
|
||||
VmRuntimeCtx, VmRuntimeCtxExt,
|
||||
};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
@@ -16,46 +17,25 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let static_count = reader.read_u32() as usize;
|
||||
let dynamic_count = reader.read_u32() as usize;
|
||||
|
||||
for i in 0..dynamic_count {
|
||||
let depth = dynamic_count - 1 - i;
|
||||
self.force_slot_to_pc(depth, reader, mc, reader.inst_start_pc())?;
|
||||
let count = reader.read_u32() as usize;
|
||||
let mut entries: SmallVec<[AttrEntry; 4]> = SmallVec::with_capacity(count);
|
||||
for _ in 0..count {
|
||||
let key = reader.read_attr_key_data();
|
||||
let val = reader.read_operand_data(ctx);
|
||||
let _span_id = reader.read_u32();
|
||||
entries.push(AttrEntry { key, val });
|
||||
}
|
||||
|
||||
let mut dyn_keys: SmallVec<[_; 2]> = SmallVec::with_capacity(dynamic_count);
|
||||
for i in 0..dynamic_count {
|
||||
let depth = dynamic_count - 1 - i;
|
||||
let key_val = self.peek_forced(depth);
|
||||
let key_sid = match ctx.get_string_id(key_val) {
|
||||
Ok(id) => Some(id),
|
||||
Err(NixType::Null) => None,
|
||||
Err(got) => return self.finish_type_err(NixType::String, got),
|
||||
let mut kv: SmallVec<[(crate::StringId, Value); 4]> = SmallVec::with_capacity(count);
|
||||
for entry in &entries {
|
||||
let key_sid = match &entry.key {
|
||||
AttrKeyData::Static(sid) => *sid,
|
||||
AttrKeyData::Dynamic => {
|
||||
todo!()
|
||||
}
|
||||
};
|
||||
dyn_keys.push(key_sid);
|
||||
let val = entry.val.resolve(mc, self);
|
||||
kv.push((key_sid, val));
|
||||
}
|
||||
|
||||
self.stack.truncate(self.stack.len() - dynamic_count);
|
||||
|
||||
let mut kv: SmallVec<[(crate::StringId, Value); 4]> =
|
||||
SmallVec::with_capacity(static_count + dynamic_count);
|
||||
|
||||
for _ in 0..static_count {
|
||||
let key = reader.read_string_id();
|
||||
let val = resolve_operand(&reader.read_operand_data(ctx), mc, self);
|
||||
let _span_id = reader.read_u32();
|
||||
kv.push((key, val));
|
||||
}
|
||||
|
||||
for key in dyn_keys {
|
||||
let val = resolve_operand(&reader.read_operand_data(ctx), mc, self);
|
||||
let _span_id = reader.read_u32();
|
||||
if let Some(key) = key {
|
||||
kv.push((key, val))
|
||||
}
|
||||
}
|
||||
|
||||
kv.sort_by_key(|(k, _)| *k);
|
||||
let attrs = Gc::new(mc, AttrSet::from_sorted_unchecked(kv));
|
||||
self.push(Value::new_gc(attrs));
|
||||
@@ -78,7 +58,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
let _span_id = reader.read_u32();
|
||||
let key = reader.read_string_id();
|
||||
|
||||
let attrset = self.force_and_retry::<Gc<AttrSet>>(reader, mc)?;
|
||||
let attrset = self.try_force::<Gc<AttrSet>>(reader, mc)?;
|
||||
|
||||
match attrset.lookup(key) {
|
||||
Some(v) => {
|
||||
@@ -98,7 +78,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
) -> Step {
|
||||
let _span_id = reader.read_u32();
|
||||
|
||||
let (attrset, key_val) = self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
||||
let (attrset, key_val) = self.try_force::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
||||
|
||||
let key_sid = match ctx.get_string_id(key_val) {
|
||||
Ok(id) => id,
|
||||
@@ -194,7 +174,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
let _span_id = reader.read_u32();
|
||||
let key = reader.read_string_id();
|
||||
|
||||
let current = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let current = self.try_force::<StrictValue>(reader, mc)?;
|
||||
|
||||
match current
|
||||
.as_gc::<AttrSet>()
|
||||
@@ -217,7 +197,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
) -> Step {
|
||||
let _span_id = reader.read_u32();
|
||||
|
||||
let (current, key_val) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
let (current, key_val) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
|
||||
let key_sid = match ctx.get_string_id(key_val) {
|
||||
Ok(id) => id,
|
||||
@@ -257,7 +237,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let key = reader.read_string_id();
|
||||
let current = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let current = self.try_force::<StrictValue>(reader, mc)?;
|
||||
|
||||
self.push(Value::new_inline(
|
||||
current
|
||||
@@ -278,7 +258,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let (current, dyn_key) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
let (current, dyn_key) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||
|
||||
let key_sid = match ctx.get_string_id(dyn_key) {
|
||||
Ok(id) => id,
|
||||
@@ -314,7 +294,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
let count = reader.read_u32() as usize;
|
||||
let mut items: SmallVec<[Value; 4]> = SmallVec::with_capacity(count);
|
||||
for _ in 0..count {
|
||||
items.push(resolve_operand(&reader.read_operand_data(ctx), mc, self));
|
||||
items.push(reader.read_operand_data(ctx).resolve(mc, self));
|
||||
}
|
||||
let list = Gc::new(
|
||||
mc,
|
||||
@@ -332,3 +312,8 @@ impl<'gc> crate::Vm<'gc> {
|
||||
Step::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AttrEntry {
|
||||
pub(crate) key: AttrKeyData,
|
||||
pub(crate) val: OperandData,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use fix_abstract_vm::*;
|
||||
use fix_error::Error;
|
||||
use gc_arena::Mutation;
|
||||
|
||||
use crate::{BytecodeReader, Step, VmRuntimeCtx};
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, Step};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
@@ -12,7 +9,7 @@ impl<'gc> crate::Vm<'gc> {
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let offset = reader.read_i32();
|
||||
let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let cond = self.try_force::<StrictValue>(reader, mc)?;
|
||||
if cond.as_inline::<bool>() == Some(false) {
|
||||
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||
}
|
||||
@@ -23,10 +20,10 @@ impl<'gc> crate::Vm<'gc> {
|
||||
pub(crate) fn op_jump_if_true(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let offset = reader.read_i32();
|
||||
let cond = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let cond = self.try_force::<StrictValue>(reader, mc)?;
|
||||
if cond.as_inline::<bool>() == Some(true) {
|
||||
reader.set_pc(((reader.pc() as isize) + (offset as isize)) as usize);
|
||||
}
|
||||
@@ -41,20 +38,9 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_assert(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
) -> Step {
|
||||
let raw_id = reader.read_string_id();
|
||||
let raw = ctx.resolve_string(raw_id);
|
||||
pub(crate) fn op_assert(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
let _raw_idx = reader.read_u32();
|
||||
let _span_id = reader.read_u32();
|
||||
let assertion = self.force_and_retry::<bool>(reader, mc)?;
|
||||
if !assertion {
|
||||
// FIXME: use catchable error
|
||||
return self.finish_err(Error::eval_error(format!("assertion '{raw}' failed")));
|
||||
}
|
||||
Step::Continue(())
|
||||
todo!("implement Assert (force TOS)");
|
||||
}
|
||||
}
|
||||
|
||||
+19
-138
@@ -1,14 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use fix_abstract_vm::{
|
||||
AttrSet, NixString, Path, StrictValue, StringContext, canon_path_str
|
||||
};
|
||||
use fix_builtins::BuiltinId;
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
@@ -29,6 +22,12 @@ impl<'gc> crate::Vm<'gc> {
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_mk_pos(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
let _span_id = reader.read_u32();
|
||||
todo!("MkPos");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_load_repl_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
let _name = reader.read_string_id();
|
||||
@@ -36,54 +35,9 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_load_scoped_binding(
|
||||
&mut self,
|
||||
ctx: &impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
_mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let slot_id = reader.read_u32();
|
||||
let name = reader.read_string_id();
|
||||
let scope = match self.scope_slots.get(slot_id as usize).copied() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return self.finish_err(Error::eval_error(format!(
|
||||
"internal: invalid scope slot {slot_id}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
let Some(attrs) = scope.as_gc::<AttrSet>() else {
|
||||
return self.finish_err(Error::eval_error("internal: scope slot is not an attrset"));
|
||||
};
|
||||
match attrs.lookup(name) {
|
||||
Some(val) => {
|
||||
self.push(val);
|
||||
Step::Continue(())
|
||||
}
|
||||
None => self.finish_err(Error::eval_error(format!(
|
||||
"scoped binding '{}' not found",
|
||||
ctx.resolve_string(name)
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_coerce_to_string(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
if val.is::<StringId>() || val.is::<NixString>() {
|
||||
self.push(val.relax());
|
||||
} else if let Some(p) = val.as_inline::<Path>() {
|
||||
// Coercing a path to a string yields the canonical path text.
|
||||
// FIXME: copy to store
|
||||
self.push(Value::new_inline(p.0));
|
||||
} else {
|
||||
todo!("coerce other types to string: {:?}", val.ty());
|
||||
}
|
||||
Step::Continue(())
|
||||
pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
let _name = reader.read_string_id();
|
||||
todo!("LoadScopedBinding");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -91,93 +45,20 @@ impl<'gc> crate::Vm<'gc> {
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
_mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let count = reader.read_u16() as usize;
|
||||
let _parts_count = reader.read_u16() as usize;
|
||||
let _force_string = reader.read_u8() != 0;
|
||||
|
||||
let mut total_len = 0;
|
||||
let mut has_any_context = false;
|
||||
for i in 0..count {
|
||||
let val = self.peek_forced(count - 1 - i);
|
||||
let s = ctx.get_string(val).expect("coerced");
|
||||
total_len += s.len();
|
||||
if !ctx.get_string_context(val).is_empty() {
|
||||
has_any_context = true;
|
||||
}
|
||||
let mut _operands: smallvec::SmallVec<[crate::OperandData; 4]> =
|
||||
smallvec::SmallVec::with_capacity(_parts_count);
|
||||
for _ in 0.._parts_count {
|
||||
_operands.push(reader.read_operand_data(ctx));
|
||||
}
|
||||
|
||||
let mut result = String::with_capacity(total_len);
|
||||
let mut merged = StringContext::new();
|
||||
for i in 0..count {
|
||||
let val = self.peek_forced(count - 1 - i);
|
||||
let s = ctx.get_string(val).expect("coerced");
|
||||
result.push_str(s);
|
||||
if has_any_context {
|
||||
let ctx = ctx.get_string_context(val);
|
||||
if !ctx.is_empty() {
|
||||
merged = merged.merge(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.stack.truncate(self.stack.len() - count);
|
||||
|
||||
if merged.is_empty() {
|
||||
let sid = ctx.intern_string(result);
|
||||
self.push(Value::new_inline(sid));
|
||||
} else {
|
||||
let ns = gc_arena::Gc::new(mc, NixString::with_context(result, merged));
|
||||
self.push(Value::new_gc(ns));
|
||||
}
|
||||
Step::Continue(())
|
||||
todo!("implement ConcatStrings (force parts, coerce to string, concatenate)");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_resolve_path(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||
let dir_id = reader.read_string_id();
|
||||
// Already a path: keep as-is. ResolvePath is idempotent on paths.
|
||||
if let Some(p) = path_val.as_inline::<Path>() {
|
||||
self.push(Value::new_inline(p));
|
||||
return Step::Continue(());
|
||||
}
|
||||
let path = match ctx.get_string(path_val) {
|
||||
Some(s) => s.to_owned(),
|
||||
None => {
|
||||
return self.finish_err(Error::eval_error(format!(
|
||||
"expected a string for path, got {}",
|
||||
path_val.ty()
|
||||
)));
|
||||
}
|
||||
};
|
||||
let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return self.finish_err(e),
|
||||
};
|
||||
let sid = ctx.intern_string(resolved);
|
||||
self.push(Value::new_inline(Path(sid)));
|
||||
Step::Continue(())
|
||||
pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl VmRuntimeCtx) -> Step {
|
||||
todo!("implement ResolvePath");
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>> {
|
||||
let raw = if path.starts_with('/') {
|
||||
return Ok(canon_path_str(path));
|
||||
} else if let Some(rest) = path.strip_prefix("~/") {
|
||||
let mut dir =
|
||||
std::env::home_dir().ok_or_else(|| Error::eval_error("home dir not defined"))?;
|
||||
dir.push(rest);
|
||||
dir
|
||||
} else {
|
||||
let mut dir = PathBuf::from(current_dir);
|
||||
dir.push(path);
|
||||
dir
|
||||
};
|
||||
Ok(canon_path_str(&raw))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub(crate) mod arithmetic;
|
||||
pub(crate) mod builtins;
|
||||
pub(crate) mod calls;
|
||||
pub(crate) mod closures;
|
||||
pub(crate) mod collections;
|
||||
|
||||
@@ -1,11 +1,51 @@
|
||||
use fix_abstract_vm::{resolve_operand, *};
|
||||
use fix_common::Symbol;
|
||||
use fix_error::Error;
|
||||
use smallvec::SmallVec;
|
||||
use gc_arena::Gc;
|
||||
|
||||
use crate::{Break, BytecodeReader, CallFrame, Step, VmRuntimeCtx};
|
||||
use crate::value::*;
|
||||
use crate::{BytecodeReader, CallFrame, Step, VmRuntimeCtx, WithEnv};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_push_with(
|
||||
&mut self,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
let env = reader.read_operand_data(ctx).resolve(mc, self);
|
||||
let scope = Gc::new(
|
||||
mc,
|
||||
WithEnv {
|
||||
env,
|
||||
prev: self.with_env,
|
||||
},
|
||||
);
|
||||
self.with_env = Some(scope);
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_pop_with(&mut self) -> Step {
|
||||
let Some(scope) = self.with_env else {
|
||||
unreachable!("no with_scope to pop");
|
||||
};
|
||||
self.with_env = scope.prev;
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_prepare_with(&mut self) -> Step {
|
||||
self.call_stack.push(CallFrame {
|
||||
pc: usize::MAX,
|
||||
stack_depth: 0,
|
||||
thunk: None,
|
||||
env: self.env,
|
||||
with_env: self.with_env,
|
||||
});
|
||||
Step::Continue(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_lookup_with(
|
||||
&mut self,
|
||||
@@ -13,66 +53,31 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let counter = self.peek_forced(0).as_inline::<i32>().unwrap();
|
||||
|
||||
let name = reader.read_string_id();
|
||||
let n = reader.read_u8();
|
||||
let mut namespaces = SmallVec::<[_; 2]>::new();
|
||||
for _ in 0..n {
|
||||
namespaces.push(resolve_operand(&reader.read_operand_data(ctx), mc, self));
|
||||
}
|
||||
|
||||
let resume_pc = reader.inst_start_pc();
|
||||
let namespace = match namespaces[counter as usize].restrict() {
|
||||
Ok(val) => val,
|
||||
Err(thunk) => {
|
||||
let mut state = thunk.borrow_mut(mc);
|
||||
match *state {
|
||||
ThunkState::Pending { ip, env } => {
|
||||
*state = ThunkState::Blackhole;
|
||||
self.call_stack.push(CallFrame {
|
||||
thunk: Some(thunk),
|
||||
pc: resume_pc,
|
||||
env: self.env,
|
||||
});
|
||||
self.env = env;
|
||||
reader.set_pc(ip);
|
||||
return Step::Break(Break::Force);
|
||||
}
|
||||
ThunkState::Evaluated(v) => v,
|
||||
ThunkState::Apply { func, arg } => {
|
||||
self.call_stack.push(CallFrame {
|
||||
thunk: Some(thunk),
|
||||
pc: resume_pc,
|
||||
env: self.env,
|
||||
});
|
||||
self.push(func);
|
||||
return self.call(reader, mc, arg, resume_pc);
|
||||
}
|
||||
ThunkState::Blackhole => {
|
||||
return self
|
||||
.finish_err(Error::eval_error("infinite recursion encountered"));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(val) = namespace
|
||||
.as_gc::<AttrSet>()
|
||||
.and_then(|attrs| attrs.lookup(name))
|
||||
{
|
||||
self.replace(0, val);
|
||||
} else if counter + 1 == n as i32 {
|
||||
let Some(&WithEnv { env, prev }) = self.with_env.as_deref() else {
|
||||
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
|
||||
unreachable!()
|
||||
};
|
||||
self.with_env = with_env;
|
||||
return self.finish_err(Error::eval_error(format!(
|
||||
"undefined variable '{}'",
|
||||
Symbol::from(ctx.resolve_string(name))
|
||||
)));
|
||||
} else {
|
||||
self.replace(0, Value::new_inline(counter + 1));
|
||||
reader.set_pc(resume_pc);
|
||||
}
|
||||
};
|
||||
self.push(env);
|
||||
let env = self.try_force::<Gc<AttrSet>>(reader, mc)?;
|
||||
let Some(val) = env.lookup(name) else {
|
||||
reader.set_pc(reader.inst_start_pc());
|
||||
self.with_env = prev;
|
||||
return Step::Continue(());
|
||||
};
|
||||
|
||||
self.push(val);
|
||||
let Some(CallFrame { with_env, .. }) = self.call_stack.pop() else {
|
||||
unreachable!()
|
||||
};
|
||||
self.with_env = with_env;
|
||||
Step::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
+277
-292
@@ -11,47 +11,248 @@ use fix_builtins::{BUILTINS, BuiltinId};
|
||||
use fix_codegen::InstructionPtr;
|
||||
use fix_common::StringId;
|
||||
use fix_error::{Error, Result, Source};
|
||||
use gc_arena::metrics::Pacing;
|
||||
use gc_arena::arena::CollectionPhase;
|
||||
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
||||
use hashbrown::HashMap;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
mod boxing;
|
||||
mod bytecode_reader;
|
||||
#[cfg(feature = "tailcall")]
|
||||
mod dispatch_tailcall;
|
||||
pub use fix_abstract_vm::*;
|
||||
mod instructions;
|
||||
mod forced;
|
||||
mod value;
|
||||
pub use value::StaticValue;
|
||||
use value::*;
|
||||
mod helpers;
|
||||
pub(crate) mod instructions;
|
||||
pub(crate) use bytecode_reader::BytecodeReader;
|
||||
pub(crate) use forced::Forced;
|
||||
use helpers::*;
|
||||
|
||||
type VmResult<T> = std::result::Result<T, VmError>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum VmError {
|
||||
Catchable(String),
|
||||
Uncatchable(Box<Error>),
|
||||
}
|
||||
|
||||
impl From<Box<Error>> for VmError {
|
||||
fn from(e: Box<Error>) -> Self {
|
||||
VmError::Uncatchable(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl VmError {
|
||||
fn into_error(self) -> Box<Error> {
|
||||
match self {
|
||||
VmError::Catchable(_) => todo!("Check for tryEval catch frames"),
|
||||
VmError::Uncatchable(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
#[collect(require_static)]
|
||||
pub enum ForceMode {
|
||||
#[default]
|
||||
AsIs,
|
||||
Shallow,
|
||||
Deep,
|
||||
}
|
||||
|
||||
pub trait VmContext {
|
||||
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx);
|
||||
}
|
||||
|
||||
pub trait VmRuntimeCtx {
|
||||
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId;
|
||||
fn resolve_string(&self, id: StringId) -> &str;
|
||||
fn get_const(&self, id: u32) -> StaticValue;
|
||||
fn add_const(&mut self, val: StaticValue) -> u32;
|
||||
}
|
||||
|
||||
pub trait VmCode {
|
||||
fn bytecode(&self) -> &[u8];
|
||||
fn compile(
|
||||
&mut self,
|
||||
source: Source,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
) -> fix_error::Result<InstructionPtr>;
|
||||
}
|
||||
|
||||
pub(crate) trait VmRuntimeCtxExt: VmRuntimeCtx {
|
||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
||||
fn get_string_id<'a, 'gc: 'a>(
|
||||
&'a mut self,
|
||||
val: StrictValue<'gc>,
|
||||
) -> std::result::Result<StringId, NixType>;
|
||||
fn convert_value(&self, val: Value) -> fix_common::Value;
|
||||
}
|
||||
|
||||
impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
||||
if let Some(sid) = val.as_inline::<StringId>() {
|
||||
Some(self.resolve_string(sid))
|
||||
} else {
|
||||
val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_string_id<'a, 'gc: 'a>(
|
||||
&'a mut self,
|
||||
val: StrictValue<'gc>,
|
||||
) -> std::result::Result<StringId, NixType> {
|
||||
if let Some(sid) = val.as_inline::<StringId>() {
|
||||
Ok(sid)
|
||||
} else if let Some(s) = val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str()) {
|
||||
Ok(self.intern_string(s))
|
||||
} else {
|
||||
Err(val.ty())
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_value(&self, val: Value) -> fix_common::Value {
|
||||
self.convert_value_with_seen(val, &mut HashSet::new())
|
||||
}
|
||||
}
|
||||
|
||||
trait ConvertValueWithSeen: VmRuntimeCtx {
|
||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value;
|
||||
}
|
||||
|
||||
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
||||
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value {
|
||||
use fix_common::Value;
|
||||
if let Some(i) = val.as_inline::<i32>() {
|
||||
Value::Int(i as i64)
|
||||
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
||||
Value::Int(*gc_i)
|
||||
} else if let Some(f) = val.as_float() {
|
||||
Value::Float(f)
|
||||
} else if let Some(b) = val.as_inline::<bool>() {
|
||||
Value::Bool(b)
|
||||
} else if val.is::<Null>() {
|
||||
Value::Null
|
||||
} else if let Some(sid) = val.as_inline::<StringId>() {
|
||||
let s = self.resolve_string(sid).to_owned();
|
||||
Value::String(s)
|
||||
} else if let Some(ns) = val.as_gc::<NixString>() {
|
||||
Value::String(ns.as_str().to_owned())
|
||||
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
||||
let bits = val.to_bits();
|
||||
if attrs.entries.borrow().is_empty() {
|
||||
return Value::AttrSet(Default::default());
|
||||
}
|
||||
if !seen.insert(bits) {
|
||||
return Value::Repeated;
|
||||
}
|
||||
let mut map = std::collections::BTreeMap::new();
|
||||
for &(key, val) in attrs.entries.borrow().iter() {
|
||||
let key = self.resolve_string(key).to_owned();
|
||||
let converted = self.convert_value_with_seen(val, seen);
|
||||
map.insert(fix_common::Symbol::from(key), converted);
|
||||
}
|
||||
Value::AttrSet(fix_common::AttrSet::new(map))
|
||||
} else if let Some(list) = val.as_gc::<List>() {
|
||||
let bits = val.to_bits();
|
||||
if list.inner.borrow().is_empty() {
|
||||
return Value::List(Default::default());
|
||||
}
|
||||
if !seen.insert(bits) {
|
||||
return Value::Repeated;
|
||||
}
|
||||
let items: Vec<_> = list
|
||||
.inner
|
||||
.borrow()
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|v| self.convert_value_with_seen(v, seen))
|
||||
.collect();
|
||||
Value::List(fix_common::List::new(items))
|
||||
} else if val.is::<Closure>() {
|
||||
Value::Func
|
||||
} else if let Some(thunk) = val.as_gc::<Thunk>() {
|
||||
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
||||
self.convert_value_with_seen(v.relax(), seen)
|
||||
} else {
|
||||
Value::Thunk
|
||||
}
|
||||
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
||||
let name = fix_builtins::BUILTINS[primop.id as usize].0;
|
||||
Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
|
||||
} else if let Some(app) = val.as_gc::<PrimOpApp>() {
|
||||
let name = fix_builtins::BUILTINS[app.primop.id as usize].0;
|
||||
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
|
||||
} else {
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub(crate) enum Break {
|
||||
Force,
|
||||
Done,
|
||||
}
|
||||
|
||||
pub(crate) type Step = std::ops::ControlFlow<Break>;
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct Vm<'gc> {
|
||||
stack: Vec<Value<'gc>>,
|
||||
call_stack: Vec<CallFrame<'gc>>,
|
||||
call_depth: usize,
|
||||
pub(crate) stack: Vec<Value<'gc>>,
|
||||
pub(crate) call_stack: Vec<CallFrame<'gc>>,
|
||||
pub(crate) call_depth: usize,
|
||||
#[allow(dead_code)]
|
||||
#[collect(require_static)]
|
||||
error_context: Vec<ErrorFrame>,
|
||||
pub(crate) error_context: Vec<ErrorFrame>,
|
||||
|
||||
env: GcEnv<'gc>,
|
||||
pub(crate) env: GcEnv<'gc>,
|
||||
pub(crate) with_env: Option<GcWithEnv<'gc>>,
|
||||
|
||||
import_cache: HashMap<PathBuf, Value<'gc>>,
|
||||
scope_slots: Vec<Value<'gc>>,
|
||||
pub(crate) import_cache: HashMap<PathBuf, Value<'gc>>,
|
||||
|
||||
builtins: Value<'gc>,
|
||||
empty_list: Value<'gc>,
|
||||
empty_attrs: Value<'gc>,
|
||||
pub(crate) builtins: Value<'gc>,
|
||||
pub(crate) empty_list: Value<'gc>,
|
||||
pub(crate) empty_attrs: Value<'gc>,
|
||||
|
||||
force_mode: ForceMode,
|
||||
pub(crate) force_mode: ForceMode,
|
||||
|
||||
#[collect(require_static)]
|
||||
result: Option<Result<fix_common::Value>>,
|
||||
pub(crate) result: Option<Result<fix_common::Value>>,
|
||||
}
|
||||
|
||||
#[collect(require_static)]
|
||||
pending_load: Option<PendingLoad>,
|
||||
pub(crate) enum OperandData {
|
||||
Const(StaticValue),
|
||||
Local { layer: u8, idx: u32 },
|
||||
Builtins,
|
||||
BigInt(i64),
|
||||
}
|
||||
|
||||
functor_sym: StringId,
|
||||
impl OperandData {
|
||||
pub(crate) fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &Vm<'gc>) -> Value<'gc> {
|
||||
match *self {
|
||||
OperandData::Const(sv) => sv.into(),
|
||||
OperandData::Local { layer, idx } => {
|
||||
let mut cur = root.env;
|
||||
for _ in 0..layer {
|
||||
let prev = cur.borrow().prev.expect("env chain too short");
|
||||
cur = prev;
|
||||
}
|
||||
cur.borrow().locals[idx as usize]
|
||||
}
|
||||
OperandData::Builtins => root.builtins,
|
||||
OperandData::BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum AttrKeyData {
|
||||
Static(StringId),
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
fn init_builtins<'gc>(mc: &Mutation<'gc>, ctx: &mut impl VmRuntimeCtx) -> Value<'gc> {
|
||||
@@ -121,9 +322,9 @@ impl<'gc> Vm<'gc> {
|
||||
error_context: Vec::with_capacity(1024),
|
||||
|
||||
env: Gc::new(mc, RefLock::new(Env::empty())),
|
||||
with_env: None,
|
||||
|
||||
import_cache: HashMap::new(),
|
||||
scope_slots: Vec::new(),
|
||||
|
||||
builtins,
|
||||
empty_list: Value::new_gc(Gc::new(mc, List::default())),
|
||||
@@ -132,26 +333,23 @@ impl<'gc> Vm<'gc> {
|
||||
force_mode,
|
||||
|
||||
result: None,
|
||||
pending_load: None,
|
||||
|
||||
functor_sym: ctx.intern_string("__functor"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn finish_ok(&mut self, val: fix_common::Value) -> Step {
|
||||
pub(crate) 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 {
|
||||
pub(crate) 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 {
|
||||
pub(crate) fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
|
||||
self.result = Some(Err(Error::eval_error(format!(
|
||||
"expected {expected}, got {got}"
|
||||
))));
|
||||
@@ -159,24 +357,24 @@ impl<'gc> Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn finish_vm_err(&mut self, err: VmError) -> Step {
|
||||
pub(crate) fn finish_vm_err(&mut self, err: VmError) -> Step {
|
||||
self.finish_err(err.into_error())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn push(&mut self, val: Value<'gc>) {
|
||||
pub(crate) fn push(&mut self, val: Value<'gc>) {
|
||||
self.stack.push(val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn pop(&mut self) -> Value<'gc> {
|
||||
pub(crate) fn pop(&mut self) -> Value<'gc> {
|
||||
self.stack.pop().expect("stack underflow")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn peek(&self, depth: usize) -> Value<'gc> {
|
||||
pub(crate) fn peek(&mut self, depth: usize) -> Value<'gc> {
|
||||
*self
|
||||
.stack
|
||||
.get(self.stack.len() - depth - 1)
|
||||
@@ -185,7 +383,7 @@ impl<'gc> Vm<'gc> {
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
|
||||
pub(crate) fn peek_forced(&mut self, depth: usize) -> StrictValue<'gc> {
|
||||
self.stack
|
||||
.get(self.stack.len() - depth - 1)
|
||||
.expect("stack underflow")
|
||||
@@ -194,7 +392,7 @@ impl<'gc> Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn replace(&mut self, depth: usize, val: Value<'gc>) {
|
||||
pub(crate) fn replace(&mut self, depth: usize, val: Value<'gc>) {
|
||||
let len = self.stack.len();
|
||||
*self
|
||||
.stack
|
||||
@@ -204,7 +402,7 @@ impl<'gc> Vm<'gc> {
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(debug_assertions, track_caller)]
|
||||
fn pop_forced(&mut self) -> StrictValue<'gc> {
|
||||
pub(crate) fn pop_forced(&mut self) -> StrictValue<'gc> {
|
||||
self.stack
|
||||
.pop()
|
||||
.expect("stack underflow")
|
||||
@@ -212,45 +410,17 @@ impl<'gc> Vm<'gc> {
|
||||
.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>>(
|
||||
pub(crate) fn try_force<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())
|
||||
self.try_force_to_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>>(
|
||||
pub(crate) fn try_force_to_pc<T: Forced<'gc>>(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
@@ -262,7 +432,7 @@ impl<'gc> Vm<'gc> {
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(unused)]
|
||||
fn force_slot(
|
||||
pub(crate) fn force_slot(
|
||||
&mut self,
|
||||
depth: usize,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
@@ -272,7 +442,7 @@ impl<'gc> Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn force_slot_to_pc(
|
||||
pub(crate) fn force_slot_to_pc(
|
||||
&mut self,
|
||||
depth: usize,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
@@ -284,219 +454,59 @@ impl<'gc> Vm<'gc> {
|
||||
};
|
||||
let mut state = thunk.borrow_mut(mc);
|
||||
match *state {
|
||||
ThunkState::Pending { ip, env } => {
|
||||
ThunkState::Pending { ip, env, with_env } => {
|
||||
*state = ThunkState::Blackhole;
|
||||
drop(state);
|
||||
self.call_stack.push(CallFrame {
|
||||
thunk: Some(thunk),
|
||||
stack_depth: depth,
|
||||
pc: resume_pc,
|
||||
env: self.env,
|
||||
with_env: self.with_env,
|
||||
});
|
||||
self.env = env;
|
||||
self.with_env = with_env;
|
||||
reader.set_pc(ip);
|
||||
Step::Break(Break::Force)
|
||||
}
|
||||
ThunkState::Evaluated(v) => {
|
||||
drop(state);
|
||||
self.replace(depth, v.relax());
|
||||
Step::Continue(())
|
||||
}
|
||||
ThunkState::Apply { func, arg } => {
|
||||
self.call_stack.push(CallFrame {
|
||||
thunk: Some(thunk),
|
||||
pc: resume_pc,
|
||||
env: self.env,
|
||||
});
|
||||
self.push(func);
|
||||
self.call(reader, mc, arg, resume_pc)
|
||||
}
|
||||
ThunkState::Apply { .. } => todo!("force apply"),
|
||||
ThunkState::Blackhole => {
|
||||
drop(state);
|
||||
self.finish_err(Error::eval_error("infinite recursion encountered"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Machine<'gc> for Vm<'gc> {
|
||||
#[inline(always)]
|
||||
fn push(&mut self, val: Value<'gc>) {
|
||||
self.push(val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop(&mut self) -> Value<'gc> {
|
||||
self.pop()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek(&self, depth: usize) -> Value<'gc> {
|
||||
Vm::peek(self, depth)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn peek_forced(&self, depth: usize) -> StrictValue<'gc> {
|
||||
Vm::peek_forced(self, depth)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_forced(&mut self) -> StrictValue<'gc> {
|
||||
self.pop_forced()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn replace(&mut self, depth: usize, val: Value<'gc>) {
|
||||
self.replace(depth, val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn stack_len(&self) -> usize {
|
||||
self.stack.len()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn force_slot_to_pc(
|
||||
&mut self,
|
||||
depth: usize,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
self.force_slot_to_pc(depth, reader, mc, resume_pc)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn call(
|
||||
&mut self,
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
mc: &Mutation<'gc>,
|
||||
arg: Value<'gc>,
|
||||
resume_pc: usize,
|
||||
) -> Step {
|
||||
self.call(reader, mc, arg, resume_pc)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn push_call_frame(&mut self, frame: CallFrame<'gc>) {
|
||||
self.call_stack.push(frame);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pop_call_frame(&mut self) -> Option<CallFrame<'gc>> {
|
||||
self.call_stack.pop()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn call_depth(&self) -> usize {
|
||||
self.call_depth
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn inc_call_depth(&mut self) {
|
||||
self.call_depth += 1;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn dec_call_depth(&mut self) {
|
||||
self.call_depth -= 1;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn env(&self) -> GcEnv<'gc> {
|
||||
self.env
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_env(&mut self, env: GcEnv<'gc>) {
|
||||
self.env = env;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn finish_ok(&mut self, val: fix_common::Value) -> Step {
|
||||
self.finish_ok(val)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn finish_err(&mut self, err: Box<Error>) -> Step {
|
||||
self.finish_err(err)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn finish_type_err(&mut self, expected: NixType, got: NixType) -> Step {
|
||||
self.finish_type_err(expected, got)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn builtins(&self) -> Value<'gc> {
|
||||
self.builtins
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn functor_sym(&self) -> StringId {
|
||||
self.functor_sym
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn empty_list(&self) -> Value<'gc> {
|
||||
self.empty_list
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn empty_attrs(&self) -> Value<'gc> {
|
||||
self.empty_attrs
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn force_mode(&self) -> ForceMode {
|
||||
self.force_mode
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn import_cache_get(&self, path: &std::path::Path) -> Option<Value<'gc>> {
|
||||
self.import_cache.get(path).copied()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn import_cache_insert(&mut self, path: PathBuf, val: Value<'gc>) {
|
||||
self.import_cache.insert(path, val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn scope_slot(&self, idx: u32) -> Value<'gc> {
|
||||
*self
|
||||
.scope_slots
|
||||
.get(idx as usize)
|
||||
.expect("invalid scope slot")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn scope_slots_push(&mut self, val: Value<'gc>) -> u32 {
|
||||
let idx = self.scope_slots.len() as u32;
|
||||
self.scope_slots.push(val);
|
||||
idx
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_pending_load(&mut self, load: PendingLoad) {
|
||||
self.pending_load = Some(load);
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
struct ErrorFrame {
|
||||
span_id: u32,
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
enum Action {
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct CallFrame<'gc> {
|
||||
pub(crate) pc: usize,
|
||||
pub(crate) stack_depth: usize,
|
||||
pub(crate) thunk: Option<Gc<'gc, Thunk<'gc>>>,
|
||||
pub(crate) env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
pub(crate) with_env: Option<Gc<'gc, WithEnv<'gc>>>,
|
||||
}
|
||||
|
||||
pub(crate) enum Action {
|
||||
Continue { pc: usize },
|
||||
Done(Result<fix_common::Value>),
|
||||
LoadFile(PendingLoad),
|
||||
}
|
||||
|
||||
/// Compute initial heap size mirroring CppNix's strategy: 25% of physical RAM,
|
||||
/// clamped to [32 MiB, 384 MiB]. Used as `Pacing::min_sleep` so the collector
|
||||
/// defers the first cycle until the heap reaches this size.
|
||||
fn initial_heap_size() -> usize {
|
||||
const MIN_SIZE: usize = 32 * 1024 * 1024;
|
||||
const MAX_SIZE: usize = 384 * 1024 * 1024;
|
||||
|
||||
let mut sys = sysinfo::System::new();
|
||||
sys.refresh_memory();
|
||||
let total = sys.total_memory() as usize;
|
||||
let quarter = total / 4;
|
||||
quarter.clamp(MIN_SIZE, MAX_SIZE)
|
||||
pub(crate) enum NixNum {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
impl Vm<'_> {
|
||||
@@ -507,10 +517,8 @@ impl Vm<'_> {
|
||||
) -> Result<fix_common::Value> {
|
||||
let (code, runtime) = ctx.split();
|
||||
let mut arena: Arena<Rootable![Vm<'_>]> = Arena::new(|mc| Vm::new(force_mode, mc, runtime));
|
||||
arena.metrics().set_pacing(Pacing {
|
||||
min_sleep: initial_heap_size(),
|
||||
..Pacing::STOP_THE_WORLD
|
||||
});
|
||||
|
||||
const COLLECTOR_GRANULARITY: f64 = 1024.0;
|
||||
|
||||
let mut pc = ip.0;
|
||||
loop {
|
||||
@@ -518,28 +526,14 @@ impl Vm<'_> {
|
||||
match arena.mutate_root(|mc, root| root.dispatch_batch(bytecode, runtime, pc, mc)) {
|
||||
Action::Continue { pc: new_pc } => {
|
||||
pc = new_pc;
|
||||
if arena.metrics().allocation_debt() > 0.0 {
|
||||
arena.finish_cycle();
|
||||
if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
|
||||
if arena.collection_phase() == CollectionPhase::Sweeping {
|
||||
arena.collect_debt();
|
||||
} else if let Some(marked) = arena.mark_debt() {
|
||||
marked.start_sweeping();
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::LoadFile(load) => {
|
||||
let source = match Source::new_file(load.path) {
|
||||
Ok(src) => src,
|
||||
Err(err) => break Err(Error::eval_error(format!("import failed: {err}"))),
|
||||
};
|
||||
let extra_scope = load.scope.map(|s| ExtraScope::ScopedImport {
|
||||
keys: s.keys,
|
||||
slot_id: s.slot_id,
|
||||
});
|
||||
let new_ip = match code.compile_with_scope(source, extra_scope, runtime) {
|
||||
Ok(ip) => ip,
|
||||
Err(err) => break Err(err),
|
||||
};
|
||||
pc = new_ip.0;
|
||||
arena.mutate_root(|mc, root| {
|
||||
root.env = Gc::new(mc, RefLock::new(Env::empty()));
|
||||
});
|
||||
}
|
||||
Action::Done(done) => break done,
|
||||
}
|
||||
}
|
||||
@@ -571,11 +565,6 @@ impl<'gc> Vm<'gc> {
|
||||
TailResult::Done => {
|
||||
Action::Done(self.result.take().expect("TailResult::Done without result"))
|
||||
}
|
||||
TailResult::LoadFile => Action::LoadFile(
|
||||
self.pending_load
|
||||
.take()
|
||||
.expect("TailResult::LoadFile without pending_load"),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -660,18 +649,21 @@ impl<'gc> Vm<'gc> {
|
||||
Jump => self.op_jump(&mut reader),
|
||||
|
||||
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc),
|
||||
CoerceToString => self.op_coerce_to_string(&mut reader, mc),
|
||||
ResolvePath => self.op_resolve_path(ctx, &mut reader, mc),
|
||||
ResolvePath => self.op_resolve_path(ctx),
|
||||
|
||||
Assert => self.op_assert(ctx, &mut reader, mc),
|
||||
Assert => self.op_assert(&mut reader),
|
||||
|
||||
PushWith => self.op_push_with(ctx, &mut reader, mc),
|
||||
PopWith => self.op_pop_with(),
|
||||
LookupWith => self.op_lookup_with(ctx, &mut reader, mc),
|
||||
PrepareWith => self.op_prepare_with(),
|
||||
|
||||
LoadBuiltins => self.op_load_builtins(),
|
||||
LoadBuiltin => self.op_load_builtin(&mut reader),
|
||||
|
||||
MkPos => self.op_mk_pos(&mut reader),
|
||||
LoadReplBinding => self.op_load_repl_binding(&mut reader),
|
||||
LoadScopedBinding => self.op_load_scoped_binding(ctx, &mut reader, mc),
|
||||
LoadScopedBinding => self.op_load_scoped_binding(&mut reader),
|
||||
|
||||
Illegal => unreachable!(),
|
||||
};
|
||||
@@ -681,13 +673,6 @@ impl<'gc> Vm<'gc> {
|
||||
Step::Break(Break::Done) => {
|
||||
return Action::Done(self.result.take().expect("Break::Done without result"));
|
||||
}
|
||||
Step::Break(Break::LoadFile) => {
|
||||
return Action::LoadFile(
|
||||
self.pending_load
|
||||
.take()
|
||||
.expect("Break::LoadFile without pending_load"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ use smallvec::SmallVec;
|
||||
use string_interner::Symbol;
|
||||
use string_interner::symbol::SymbolU32;
|
||||
|
||||
use crate::NixNum;
|
||||
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||
use crate::string_context::StringContext;
|
||||
|
||||
mod private {
|
||||
pub trait Cealed {}
|
||||
@@ -117,7 +117,6 @@ define_value_types! {
|
||||
Null => RawTag::P3, "Null";
|
||||
StringId => RawTag::P4, "SmallString";
|
||||
PrimOp => RawTag::P5, "PrimOp";
|
||||
Path => RawTag::N6, "Path";
|
||||
}
|
||||
gc {
|
||||
i64 => RawTag::P6, "BigInt";
|
||||
@@ -250,7 +249,7 @@ impl<'gc> Value<'gc> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_bits(self) -> u64 {
|
||||
pub(crate) fn to_bits(self) -> u64 {
|
||||
self.raw.to_bits()
|
||||
}
|
||||
|
||||
@@ -290,8 +289,6 @@ impl<'gc> Value<'gc> {
|
||||
NixType::PrimOp
|
||||
} else if self.is::<NixString>() {
|
||||
NixType::String
|
||||
} else if self.is::<Path>() {
|
||||
NixType::Path
|
||||
} else if self.is::<AttrSet>() {
|
||||
NixType::AttrSet
|
||||
} else if self.is::<List>() {
|
||||
@@ -308,27 +305,27 @@ impl<'gc> Value<'gc> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn expect_inline<T: InlineStorable>(self) -> Result<T, NixType> {
|
||||
pub(crate) fn expect_inline<T: InlineStorable>(self) -> Result<T, NixType> {
|
||||
self.as_inline::<T>().ok_or_else(|| self.ty())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn expect_gc<T: GcStorable>(self) -> Result<Gc<'gc, T>, NixType> {
|
||||
pub(crate) fn expect_gc<T: GcStorable>(self) -> Result<Gc<'gc, T>, NixType> {
|
||||
self.as_gc::<T>().ok_or_else(|| self.ty())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn expect_num(self) -> Result<NixNum, NixType> {
|
||||
pub(crate) fn expect_num(self) -> Result<NixNum, NixType> {
|
||||
self.as_num().ok_or_else(|| self.ty())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn expect_bool(self) -> Result<bool, NixType> {
|
||||
pub(crate) 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> {
|
||||
pub(crate) fn expect_float(self) -> Result<f64, NixType> {
|
||||
self.as_float().ok_or_else(|| self.ty())
|
||||
}
|
||||
}
|
||||
@@ -385,7 +382,7 @@ impl StaticValue {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Null;
|
||||
pub(crate) struct Null;
|
||||
impl RawStore for Null {
|
||||
fn to_val(self, value: &mut RawValue) {
|
||||
value.set_data([0; 6]);
|
||||
@@ -407,55 +404,26 @@ impl RawStore for StringId {
|
||||
}
|
||||
}
|
||||
|
||||
/// A canonicalized absolute path. Inline value carrying an interned
|
||||
/// `StringId` whose contents are the path's absolute, dot-resolved form.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Path(pub StringId);
|
||||
|
||||
impl RawStore for Path {
|
||||
fn to_val(self, value: &mut RawValue) {
|
||||
self.0.to_val(value);
|
||||
}
|
||||
fn from_val(value: &RawValue) -> Self {
|
||||
Self(StringId::from_val(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Heap-allocated Nix string.
|
||||
///
|
||||
/// Stored on the GC heap via `Gc<'gc, NixString>`. The string data itself
|
||||
/// lives in a standard `Box<str>` owned by this struct; the GC only manages
|
||||
/// the outer allocation.
|
||||
#[derive(Collect)]
|
||||
#[collect(require_static)]
|
||||
pub struct NixString {
|
||||
pub(crate) struct NixString {
|
||||
data: Box<str>,
|
||||
context: StringContext,
|
||||
// TODO: string context for derivation dependency tracking
|
||||
}
|
||||
|
||||
impl NixString {
|
||||
pub fn new(s: impl Into<Box<str>>) -> Self {
|
||||
Self {
|
||||
data: s.into(),
|
||||
context: StringContext::new(),
|
||||
}
|
||||
pub(crate) fn new(s: impl Into<Box<str>>) -> Self {
|
||||
Self { data: s.into() }
|
||||
}
|
||||
|
||||
/// Construct a `NixString` whose `context` is already sorted+deduped.
|
||||
/// The caller is responsible for invariant maintenance.
|
||||
pub fn with_context(s: impl Into<Box<str>>, context: StringContext) -> Self {
|
||||
Self {
|
||||
data: s.into(),
|
||||
context,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &StringContext {
|
||||
&self.context
|
||||
}
|
||||
|
||||
pub fn has_context(&self) -> bool {
|
||||
!self.context.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NixString {
|
||||
@@ -466,71 +434,91 @@ impl fmt::Debug for NixString {
|
||||
|
||||
#[derive(Collect, Debug, Default)]
|
||||
#[collect(no_drop)]
|
||||
pub struct AttrSet<'gc> {
|
||||
pub entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
||||
pub(crate) struct AttrSet<'gc> {
|
||||
pub(crate) entries: RefLock<SmallVec<[(StringId, Value<'gc>); 4]>>,
|
||||
}
|
||||
|
||||
impl<'gc> Unlock for AttrSet<'gc> {
|
||||
type Unlocked = RefCell<SmallVec<[(StringId, Value<'gc>); 4]>>;
|
||||
unsafe fn unlock_unchecked(&self) -> &Self::Unlocked {
|
||||
unsafe { self.entries.unlock_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> AttrSet<'gc> {
|
||||
pub fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
||||
pub(crate) fn from_sorted_unchecked(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||
Self { entries }
|
||||
Self {
|
||||
entries: RefLock::new(entries),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||
self.entries
|
||||
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||
let entries = self.entries.borrow();
|
||||
entries
|
||||
.binary_search_by_key(&key, |(k, _)| *k)
|
||||
.ok()
|
||||
.map(|i| self.entries[i].1)
|
||||
.map(|i| entries[i].1)
|
||||
}
|
||||
|
||||
pub fn has(&self, key: StringId) -> bool {
|
||||
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
|
||||
pub(crate) fn has(&self, key: StringId) -> bool {
|
||||
self.entries
|
||||
.borrow()
|
||||
.binary_search_by_key(&key, |(k, _)| *k)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||
use std::cmp::Ordering::*;
|
||||
|
||||
debug_assert!(self.entries.is_sorted_by_key(|(key, _)| *key));
|
||||
debug_assert!(other.entries.is_sorted_by_key(|(key, _)| *key));
|
||||
let self_entries = self.entries.borrow();
|
||||
let other_entries = other.entries.borrow();
|
||||
debug_assert!(self_entries.is_sorted_by_key(|(key, _)| *key));
|
||||
debug_assert!(other_entries.is_sorted_by_key(|(key, _)| *key));
|
||||
|
||||
let mut entries = SmallVec::new();
|
||||
let mut i = 0;
|
||||
let mut j = 0;
|
||||
while i < self.entries.len() && j < other.entries.len() {
|
||||
match self.entries[i].0.cmp(&other.entries[j].0) {
|
||||
while i < self_entries.len() && j < other_entries.len() {
|
||||
match self_entries[i].0.cmp(&other_entries[j].0) {
|
||||
Less => {
|
||||
entries.push(self.entries[i]);
|
||||
entries.push(self_entries[i]);
|
||||
i += 1;
|
||||
}
|
||||
Greater => {
|
||||
entries.push(other.entries[j]);
|
||||
entries.push(other_entries[j]);
|
||||
j += 1;
|
||||
}
|
||||
Equal => {
|
||||
entries.push(other.entries[j]);
|
||||
entries.push(other_entries[j]);
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
entries.extend(other.entries[j..].iter().cloned());
|
||||
entries.extend(self.entries[i..].iter().cloned());
|
||||
entries.extend(other_entries[j..].iter().cloned());
|
||||
entries.extend(self_entries[i..].iter().cloned());
|
||||
|
||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||
|
||||
Gc::new(mc, AttrSet { entries })
|
||||
Gc::new(
|
||||
mc,
|
||||
AttrSet {
|
||||
entries: RefLock::new(entries),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug, Default)]
|
||||
#[repr(transparent)]
|
||||
#[collect(no_drop)]
|
||||
pub struct List<'gc> {
|
||||
pub inner: RefLock<SmallVec<[Value<'gc>; 4]>>,
|
||||
pub(crate) struct List<'gc> {
|
||||
pub(crate) inner: RefLock<SmallVec<[Value<'gc>; 4]>>,
|
||||
}
|
||||
|
||||
impl<'gc> List<'gc> {
|
||||
pub fn new(mc: &Mutation<'gc>, data: SmallVec<[Value<'gc>; 4]>) -> Gc<'gc, Self> {
|
||||
pub(crate) fn new(mc: &Mutation<'gc>, data: SmallVec<[Value<'gc>; 4]>) -> Gc<'gc, Self> {
|
||||
Gc::new(
|
||||
mc,
|
||||
Self {
|
||||
@@ -539,7 +527,7 @@ impl<'gc> List<'gc> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||
pub(crate) fn new_gc(mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||
Gc::new(mc, Self::default())
|
||||
}
|
||||
}
|
||||
@@ -551,42 +539,53 @@ impl<'gc> Unlock for List<'gc> {
|
||||
}
|
||||
}
|
||||
|
||||
pub type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
||||
pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub enum ThunkState<'gc> {
|
||||
Pending { ip: usize, env: GcEnv<'gc> },
|
||||
Apply { func: Value<'gc>, arg: Value<'gc> },
|
||||
pub(crate) enum ThunkState<'gc> {
|
||||
Pending {
|
||||
ip: usize,
|
||||
env: GcEnv<'gc>,
|
||||
with_env: Option<GcWithEnv<'gc>>,
|
||||
},
|
||||
Apply {
|
||||
func: Value<'gc>,
|
||||
arg: Value<'gc>,
|
||||
},
|
||||
Blackhole,
|
||||
Evaluated(StrictValue<'gc>),
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub struct Env<'gc> {
|
||||
pub locals: SmallVec<[Value<'gc>; 4]>,
|
||||
pub prev: Option<GcEnv<'gc>>,
|
||||
pub(crate) struct Env<'gc> {
|
||||
pub(crate) locals: SmallVec<[Value<'gc>; 4]>,
|
||||
pub(crate) prev: Option<GcEnv<'gc>>,
|
||||
}
|
||||
pub type GcEnv<'gc> = GcRefLock<'gc, Env<'gc>>;
|
||||
pub(crate) type GcEnv<'gc> = GcRefLock<'gc, Env<'gc>>;
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub struct WithEnv<'gc> {
|
||||
pub env: Value<'gc>,
|
||||
pub prev: Option<GcWithEnv<'gc>>,
|
||||
pub(crate) struct WithEnv<'gc> {
|
||||
pub(crate) env: Value<'gc>,
|
||||
pub(crate) prev: Option<GcWithEnv<'gc>>,
|
||||
}
|
||||
pub type GcWithEnv<'gc> = Gc<'gc, WithEnv<'gc>>;
|
||||
pub(crate) type GcWithEnv<'gc> = Gc<'gc, WithEnv<'gc>>;
|
||||
|
||||
impl<'gc> Env<'gc> {
|
||||
pub fn empty() -> Self {
|
||||
pub(crate) fn empty() -> Self {
|
||||
Env {
|
||||
locals: SmallVec::new(),
|
||||
prev: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_arg(arg: Value<'gc>, n_locals: u32, prev: Gc<'gc, RefLock<Env<'gc>>>) -> Self {
|
||||
pub(crate) fn with_arg(
|
||||
arg: Value<'gc>,
|
||||
n_locals: u32,
|
||||
prev: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
) -> Self {
|
||||
let mut locals = smallvec::smallvec![Value::default(); 1 + n_locals as usize];
|
||||
locals[0] = arg;
|
||||
Env {
|
||||
@@ -598,29 +597,29 @@ impl<'gc> Env<'gc> {
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub struct Closure<'gc> {
|
||||
pub ip: u32,
|
||||
pub n_locals: u32,
|
||||
pub env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
pub pattern: Option<Gc<'gc, PatternInfo>>,
|
||||
pub(crate) struct Closure<'gc> {
|
||||
pub(crate) ip: u32,
|
||||
pub(crate) n_locals: u32,
|
||||
pub(crate) env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
pub(crate) pattern: Option<Gc<'gc, PatternInfo>>,
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(require_static)]
|
||||
pub struct PatternInfo {
|
||||
pub required: SmallVec<[StringId; 4]>,
|
||||
pub optional: SmallVec<[StringId; 4]>,
|
||||
pub ellipsis: bool,
|
||||
pub param_spans: Box<[(StringId, u32)]>,
|
||||
pub(crate) struct PatternInfo {
|
||||
pub(crate) required: SmallVec<[StringId; 4]>,
|
||||
pub(crate) optional: SmallVec<[StringId; 4]>,
|
||||
pub(crate) ellipsis: bool,
|
||||
pub(crate) param_spans: Box<[(StringId, u32)]>,
|
||||
}
|
||||
|
||||
#[repr(packed, Rust)]
|
||||
#[derive(Clone, Copy, Debug, Collect)]
|
||||
#[collect(require_static)]
|
||||
pub struct PrimOp {
|
||||
pub id: BuiltinId,
|
||||
pub arity: u8,
|
||||
pub dispatch_ip: u32,
|
||||
pub(crate) struct PrimOp {
|
||||
pub(crate) id: BuiltinId,
|
||||
pub(crate) arity: u8,
|
||||
pub(crate) dispatch_ip: u32,
|
||||
}
|
||||
|
||||
impl RawStore for PrimOp {
|
||||
@@ -647,20 +646,20 @@ impl RawStore for PrimOp {
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub struct PrimOpApp<'gc> {
|
||||
pub primop: PrimOp,
|
||||
pub arity: u8,
|
||||
pub args: [Value<'gc>; 3],
|
||||
pub(crate) struct PrimOpApp<'gc> {
|
||||
pub(crate) primop: PrimOp,
|
||||
pub(crate) arity: u8,
|
||||
pub(crate) args: [Value<'gc>; 3],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Collect)]
|
||||
#[repr(transparent)]
|
||||
#[collect(no_drop)]
|
||||
pub struct StrictValue<'gc>(Value<'gc>);
|
||||
pub(crate) struct StrictValue<'gc>(Value<'gc>);
|
||||
|
||||
impl<'gc> StrictValue<'gc> {
|
||||
#[inline]
|
||||
pub fn relax(self) -> Value<'gc> {
|
||||
pub(crate) fn relax(self) -> Value<'gc> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -681,13 +680,12 @@ impl fmt::Debug for StrictValue<'_> {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Collect)]
|
||||
#[collect(require_static)]
|
||||
pub enum NixType {
|
||||
pub(crate) enum NixType {
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
Null,
|
||||
String,
|
||||
Path,
|
||||
AttrSet,
|
||||
List,
|
||||
Thunk,
|
||||
@@ -705,7 +703,6 @@ impl NixType {
|
||||
Bool => "a boolean",
|
||||
Null => "null",
|
||||
String => "a string",
|
||||
Path => "a path",
|
||||
AttrSet => "a set",
|
||||
List => "a list",
|
||||
Thunk => "a thunk",
|
||||
@@ -721,8 +718,3 @@ impl std::fmt::Display for NixType {
|
||||
write!(f, "{}", self.display())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum NixNum {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
}
|
||||
@@ -33,7 +33,6 @@ rnix = { workspace = true }
|
||||
ere = { workspace = true }
|
||||
ghost-cell = { workspace = true }
|
||||
|
||||
fix-abstract-vm = { path = "../fix-abstract-vm" }
|
||||
fix-builtins = { path = "../fix-builtins" }
|
||||
fix-common = { path = "../fix-common" }
|
||||
fix-codegen = { path = "../fix-codegen" }
|
||||
|
||||
+97
-207
@@ -2,18 +2,14 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use bumpalo::Bump;
|
||||
use fix_abstract_vm::{ForceMode, StaticValue, VmCode, VmContext, VmRuntimeCtx};
|
||||
use fix_builtins::PrimOpPhase;
|
||||
use fix_codegen::disassembler::{Disassembler, DisassemblerContext};
|
||||
use fix_codegen::{BytecodeContext, InstructionPtr, Op};
|
||||
use fix_common::{StringId, Symbol};
|
||||
use fix_error::{Error, Result, Source};
|
||||
use fix_ir::downgrade::{Downgrade as _, DowngradeContext};
|
||||
use fix_ir::{
|
||||
GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef,
|
||||
ThunkId,
|
||||
};
|
||||
use fix_vm::Vm;
|
||||
use fix_ir::{Ir, IrRef, MaybeThunk, RawIrRef, ThunkId};
|
||||
use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx};
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use string_interner::{DefaultStringInterner, Symbol as _};
|
||||
@@ -34,8 +30,7 @@ pub struct CodeState {
|
||||
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 global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
}
|
||||
|
||||
pub struct Evaluator {
|
||||
@@ -69,7 +64,6 @@ impl Evaluator {
|
||||
thunk_count: 0,
|
||||
bytecode,
|
||||
global_env,
|
||||
current_scope_slot: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -91,13 +85,13 @@ impl Evaluator {
|
||||
source: Source,
|
||||
scope: &HashSet<StringId>,
|
||||
) -> Result<fix_common::Value> {
|
||||
self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow)
|
||||
self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow)
|
||||
}
|
||||
|
||||
fn do_eval<'ctx>(
|
||||
&'ctx mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<ExtraScope<'ctx>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
force_mode: ForceMode,
|
||||
) -> Result<fix_common::Value> {
|
||||
let ip = {
|
||||
@@ -116,7 +110,7 @@ impl Evaluator {
|
||||
_expr: &str,
|
||||
_scope: &mut HashSet<StringId>,
|
||||
) -> Result<fix_common::Value> {
|
||||
todo!("add_binding")
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
|
||||
@@ -153,22 +147,16 @@ impl VmCode for CodeState {
|
||||
fn bytecode(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
fn compile_with_scope(
|
||||
fn compile(
|
||||
&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)
|
||||
compiler.compile_bytecode(source, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,23 +175,14 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
||||
fn compile_bytecode(
|
||||
&mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<ExtraScope>,
|
||||
extra_scope: Option<Scope>,
|
||||
) -> 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
|
||||
let root = self.downgrade(source, extra_scope)?;
|
||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||
Ok(ip)
|
||||
}
|
||||
|
||||
fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
|
||||
fn downgrade(&mut self, source: Source, extra_scope: Option<Scope>) -> Result<OwnedIr> {
|
||||
tracing::debug!("Parsing Nix expression");
|
||||
|
||||
self.code.sources.push(source.clone());
|
||||
@@ -223,7 +202,7 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
||||
token,
|
||||
self.runtime,
|
||||
&self.code.global_env,
|
||||
extra_scope.map(Into::into),
|
||||
extra_scope,
|
||||
&mut self.code.thunk_count,
|
||||
source,
|
||||
);
|
||||
@@ -266,7 +245,6 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
||||
Float(x) => StaticValue::new_float(x),
|
||||
Bool(x) => StaticValue::new_inline(x),
|
||||
String(x) => StaticValue::new_inline(x),
|
||||
Path(_) => todo!("path value type"),
|
||||
PrimOp {
|
||||
id,
|
||||
arity,
|
||||
@@ -276,22 +254,6 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
||||
};
|
||||
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)]
|
||||
@@ -348,20 +310,34 @@ struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
||||
token: GhostToken<'id>,
|
||||
runtime: &'ctx mut R,
|
||||
source: Source,
|
||||
scopes: Vec<Scope<'ctx, 'id, 'ir>>,
|
||||
with_stack: Vec<GhostRoMaybeThunkRef<'id, 'ir>>,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
with_scope_count: u32,
|
||||
arg_count: u32,
|
||||
thunk_count: &'ctx mut usize,
|
||||
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
||||
}
|
||||
|
||||
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
|
||||
!matches!(
|
||||
ir.borrow(token),
|
||||
Ir::Builtin(_)
|
||||
| Ir::Builtins
|
||||
| Ir::Int(_)
|
||||
| Ir::Float(_)
|
||||
| Ir::Bool(_)
|
||||
| Ir::Null
|
||||
| Ir::Str(_)
|
||||
| Ir::Thunk(_)
|
||||
)
|
||||
}
|
||||
|
||||
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>>,
|
||||
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
thunk_count: &'ctx mut usize,
|
||||
source: Source,
|
||||
) -> Self {
|
||||
@@ -375,7 +351,7 @@ impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
.collect(),
|
||||
thunk_count,
|
||||
arg_count: 0,
|
||||
with_stack: Vec::new(),
|
||||
with_scope_count: 0,
|
||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||
}
|
||||
}
|
||||
@@ -384,37 +360,31 @@ impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
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 new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||
}
|
||||
|
||||
fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir> {
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk {
|
||||
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;
|
||||
match *ir.borrow(&self.token) {
|
||||
Ir::Builtin(x) => return Builtin(x),
|
||||
Ir::Int(x) => return Int(x),
|
||||
Ir::Float(x) => return Float(x),
|
||||
Ir::Bool(x) => return Bool(x),
|
||||
Ir::Str(x) => return Str(x),
|
||||
Ir::Thunk(x) => return Thunk(x),
|
||||
Ir::Arg { layer } => return Arg { layer },
|
||||
Ir::Builtins => return Builtins,
|
||||
Ir::Null => return Null,
|
||||
_ => (),
|
||||
}
|
||||
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())
|
||||
.add_binding(id, ir, &self.token);
|
||||
Thunk(id)
|
||||
}
|
||||
|
||||
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
|
||||
@@ -425,35 +395,35 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
self.runtime.resolve_string(id).into()
|
||||
}
|
||||
|
||||
fn lookup(
|
||||
&mut self,
|
||||
sym: StringId,
|
||||
span: rnix::TextRange,
|
||||
) -> Result<GhostRoMaybeThunkRef<'id, 'ir>> {
|
||||
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<MaybeThunk> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
match scope {
|
||||
&Scope::Global(global_scope) => {
|
||||
use MaybeThunk::*;
|
||||
if let Some(expr) = global_scope.get(&sym) {
|
||||
return Ok(expr.into());
|
||||
let val = match expr {
|
||||
Ir::Builtins => Builtins,
|
||||
Ir::Builtin(s) => Builtin(*s),
|
||||
Ir::Bool(b) => Bool(*b),
|
||||
Ir::Null => Null,
|
||||
_ => unreachable!("globals should only contain leaf IR nodes"),
|
||||
};
|
||||
return Ok(val);
|
||||
}
|
||||
}
|
||||
&Scope::Repl(repl_bindings) => {
|
||||
if repl_bindings.contains(&sym) {
|
||||
return Ok(self
|
||||
.bump
|
||||
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into()));
|
||||
return Ok(MaybeThunk::ReplBinding(sym));
|
||||
}
|
||||
}
|
||||
Scope::ScopedImport { keys, .. } => {
|
||||
if keys.contains(&sym) {
|
||||
return Ok(self
|
||||
.bump
|
||||
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)).into()));
|
||||
Scope::ScopedImport(scoped_bindings) => {
|
||||
if scoped_bindings.contains(&sym) {
|
||||
return Ok(MaybeThunk::ScopedImportBinding(sym));
|
||||
}
|
||||
}
|
||||
Scope::Let(let_scope) => {
|
||||
if let Some(&expr) = let_scope.get(&sym) {
|
||||
return Ok(expr.into());
|
||||
return Ok(MaybeThunk::Thunk(expr));
|
||||
}
|
||||
}
|
||||
&Scope::Param {
|
||||
@@ -461,33 +431,16 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
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()));
|
||||
return Ok(MaybeThunk::Arg {
|
||||
layer: self.thunk_scopes.len() - abs_layer,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()))
|
||||
if self.with_scope_count > 0 {
|
||||
Ok(MaybeThunk::WithLookup(sym))
|
||||
} else {
|
||||
Err(Error::downgrade_error(
|
||||
format!("'{}' not found", self.resolve_sym(sym)),
|
||||
@@ -503,40 +456,25 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
|
||||
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,
|
||||
)>,
|
||||
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'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();
|
||||
let iter = keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(offset, &key)| (key, ThunkId(base + offset)));
|
||||
self.scopes.push(Scope::Let(iter.collect()));
|
||||
let (vals, ret) = {
|
||||
let mut guard = ScopeGuard { ctx: self };
|
||||
f(guard.as_ctx())?
|
||||
};
|
||||
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);
|
||||
}
|
||||
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
@@ -546,19 +484,19 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
{
|
||||
self.scopes.push(Scope::Param {
|
||||
sym,
|
||||
abs_layer: self.thunk_scopes.len().try_into().expect("scope too deep!"),
|
||||
abs_layer: self.thunk_scopes.len(),
|
||||
});
|
||||
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
|
||||
fn with_with_scope<F, Ret>(&mut self, f: F) -> Ret
|
||||
where
|
||||
F: FnOnce(&mut Self) -> Ret,
|
||||
{
|
||||
self.with_stack.push(namespace);
|
||||
self.with_scope_count += 1;
|
||||
let ret = f(self);
|
||||
self.with_stack.pop();
|
||||
self.with_scope_count -= 1;
|
||||
ret
|
||||
}
|
||||
|
||||
@@ -567,14 +505,11 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
f: F,
|
||||
) -> (
|
||||
Ret,
|
||||
bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
||||
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'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);
|
||||
(
|
||||
@@ -599,15 +534,13 @@ impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings;
|
||||
Ok(Ir::freeze(
|
||||
self.new_expr(Ir::TopLevel { body, thunks }),
|
||||
self.token,
|
||||
))
|
||||
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
||||
Ok(ir.freeze(self.token))
|
||||
}
|
||||
}
|
||||
|
||||
struct ThunkScope<'id, 'ir> {
|
||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||
}
|
||||
|
||||
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||
@@ -617,48 +550,21 @@ impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, id: ThunkId, ir: GhostRoIrRef<'id, 'ir>) {
|
||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) {
|
||||
self.bindings.push((id, ir));
|
||||
}
|
||||
|
||||
fn extend_bindings(
|
||||
&mut self,
|
||||
iter: impl IntoIterator<Item = (ThunkId, GhostRoIrRef<'id, 'ir>)>,
|
||||
) {
|
||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
||||
self.bindings.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
enum Scope<'ctx, 'id, 'ir> {
|
||||
Global(&'ctx HashMap<StringId, MaybeThunk>),
|
||||
enum Scope<'ctx> {
|
||||
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
||||
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),
|
||||
}
|
||||
}
|
||||
ScopedImport(HashSet<StringId>),
|
||||
Let(HashMap<StringId, ThunkId>),
|
||||
Param { sym: StringId, abs_layer: usize },
|
||||
}
|
||||
|
||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
||||
@@ -683,31 +589,15 @@ struct OwnedIr {
|
||||
}
|
||||
|
||||
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) }
|
||||
fn as_ref(&self) -> RawIrRef<'_> {
|
||||
self.ir
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
|
||||
pub enum StringContextElem {
|
||||
Opaque { path: String },
|
||||
DrvDeep { drv_path: String },
|
||||
Built { drv_path: String, output: String },
|
||||
}
|
||||
|
||||
impl StringContextElem {
|
||||
pub fn decode(encoded: &str) -> Self {
|
||||
if let Some(drv_path) = encoded.strip_prefix('=') {
|
||||
StringContextElem::DrvDeep {
|
||||
drv_path: drv_path.to_string(),
|
||||
}
|
||||
} else if let Some(rest) = encoded.strip_prefix('!') {
|
||||
if let Some(second_bang) = rest.find('!') {
|
||||
let output = rest[..second_bang].to_string();
|
||||
let drv_path = rest[second_bang + 1..].to_string();
|
||||
StringContextElem::Built { drv_path, output }
|
||||
} else {
|
||||
StringContextElem::Opaque {
|
||||
path: encoded.to_string(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
StringContextElem::Opaque {
|
||||
path: encoded.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type InputDrvs = BTreeMap<String, BTreeSet<String>>;
|
||||
pub type Srcs = BTreeSet<String>;
|
||||
pub fn extract_input_drvs_and_srcs(context: &[String]) -> Result<(InputDrvs, Srcs), String> {
|
||||
let mut input_drvs: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
let mut input_srcs: BTreeSet<String> = BTreeSet::new();
|
||||
|
||||
for encoded in context {
|
||||
match StringContextElem::decode(encoded) {
|
||||
StringContextElem::Opaque { path } => {
|
||||
input_srcs.insert(path);
|
||||
}
|
||||
StringContextElem::DrvDeep { drv_path } => {
|
||||
compute_fs_closure(&drv_path, &mut input_drvs, &mut input_srcs)?;
|
||||
}
|
||||
StringContextElem::Built { drv_path, output } => {
|
||||
input_drvs.entry(drv_path).or_default().insert(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((input_drvs, input_srcs))
|
||||
}
|
||||
|
||||
fn compute_fs_closure(
|
||||
drv_path: &str,
|
||||
input_drvs: &mut BTreeMap<String, BTreeSet<String>>,
|
||||
input_srcs: &mut BTreeSet<String>,
|
||||
) -> Result<(), String> {
|
||||
let mut queue: VecDeque<String> = VecDeque::new();
|
||||
let mut visited: BTreeSet<String> = BTreeSet::new();
|
||||
|
||||
queue.push_back(drv_path.to_string());
|
||||
|
||||
while let Some(current_path) = queue.pop_front() {
|
||||
if visited.contains(¤t_path) {
|
||||
continue;
|
||||
}
|
||||
visited.insert(current_path.clone());
|
||||
input_srcs.insert(current_path.clone());
|
||||
|
||||
if !current_path.ends_with(".drv") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = std::fs::read_to_string(¤t_path)
|
||||
.map_err(|e| format!("failed to read derivation {}: {}", current_path, e))?;
|
||||
|
||||
let inputs = parse_derivation_inputs(&content)
|
||||
.ok_or_else(|| format!("failed to parse derivation {}", current_path))?;
|
||||
|
||||
for src in inputs.input_srcs {
|
||||
input_srcs.insert(src.clone());
|
||||
if !visited.contains(&src) {
|
||||
queue.push_back(src);
|
||||
}
|
||||
}
|
||||
|
||||
for (dep_drv, outputs) in inputs.input_drvs {
|
||||
input_srcs.insert(dep_drv.clone());
|
||||
let entry = input_drvs.entry(dep_drv.clone()).or_default();
|
||||
for output in outputs {
|
||||
entry.insert(output);
|
||||
}
|
||||
if !visited.contains(&dep_drv) {
|
||||
queue.push_back(dep_drv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct DerivationInputs {
|
||||
input_drvs: Vec<(String, Vec<String>)>,
|
||||
input_srcs: Vec<String>,
|
||||
}
|
||||
|
||||
fn parse_derivation_inputs(aterm: &str) -> Option<DerivationInputs> {
|
||||
let aterm = aterm.strip_prefix("Derive([")?;
|
||||
|
||||
let mut bracket_count: i32 = 1;
|
||||
let mut pos = 0;
|
||||
let bytes = aterm.as_bytes();
|
||||
while pos < bytes.len() && bracket_count > 0 {
|
||||
match bytes[pos] {
|
||||
b'[' => bracket_count += 1,
|
||||
b']' => bracket_count -= 1,
|
||||
_ => {}
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
if bracket_count != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rest = &aterm[pos..];
|
||||
let rest = rest.strip_prefix(",[")?;
|
||||
|
||||
let mut input_drvs = Vec::new();
|
||||
let mut bracket_count: i32 = 1;
|
||||
let mut start = 0;
|
||||
pos = 0;
|
||||
let bytes = rest.as_bytes();
|
||||
|
||||
while pos < bytes.len() && bracket_count > 0 {
|
||||
match bytes[pos] {
|
||||
b'[' => bracket_count += 1,
|
||||
b']' => bracket_count -= 1,
|
||||
b'(' if bracket_count == 1 => {
|
||||
start = pos;
|
||||
}
|
||||
b')' if bracket_count == 1 => {
|
||||
let entry = &rest[start + 1..pos];
|
||||
if let Some((drv_path, outputs)) = parse_input_drv_entry(entry) {
|
||||
input_drvs.push((drv_path, outputs));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
let rest = &rest[pos..];
|
||||
let rest = rest.strip_prefix(",[")?;
|
||||
|
||||
let mut input_srcs = Vec::new();
|
||||
bracket_count = 1;
|
||||
pos = 0;
|
||||
let bytes = rest.as_bytes();
|
||||
|
||||
while pos < bytes.len() && bracket_count > 0 {
|
||||
match bytes[pos] {
|
||||
b'[' => bracket_count += 1,
|
||||
b']' => bracket_count -= 1,
|
||||
b'"' if bracket_count == 1 => {
|
||||
pos += 1;
|
||||
let src_start = pos;
|
||||
while pos < bytes.len() && bytes[pos] != b'"' {
|
||||
if bytes[pos] == b'\\' && pos + 1 < bytes.len() {
|
||||
pos += 2;
|
||||
} else {
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
let src = std::str::from_utf8(&bytes[src_start..pos]).ok()?;
|
||||
input_srcs.push(src.to_string());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
Some(DerivationInputs {
|
||||
input_drvs,
|
||||
input_srcs,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_input_drv_entry(entry: &str) -> Option<(String, Vec<String>)> {
|
||||
let entry = entry.strip_prefix('"')?;
|
||||
let quote_end = entry.find('"')?;
|
||||
let drv_path = entry[..quote_end].to_string();
|
||||
|
||||
let rest = &entry[quote_end + 1..];
|
||||
let rest = rest.strip_prefix(",[")?;
|
||||
let rest = rest.strip_suffix(']')?;
|
||||
|
||||
let mut outputs = Vec::new();
|
||||
for part in rest.split(',') {
|
||||
let part = part.trim();
|
||||
if let Some(name) = part.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
|
||||
outputs.push(name.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Some((drv_path, outputs))
|
||||
}
|
||||
Generated
+38
-22
@@ -31,6 +31,7 @@
|
||||
"llm-agents",
|
||||
"flake-parts"
|
||||
],
|
||||
"import-tree": "import-tree",
|
||||
"nixpkgs": [
|
||||
"llm-agents",
|
||||
"nixpkgs"
|
||||
@@ -45,11 +46,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778445566,
|
||||
"narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=",
|
||||
"lastModified": 1776192490,
|
||||
"narHash": "sha256-5gYQNEs0/vDkHhg63aHS5g0IwG/8HNvU1Vr00cElofk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "bun2nix",
|
||||
"rev": "2499dedd70744dba1815875b854818a3019e9e4c",
|
||||
"rev": "6ef9f144616eedea90b364bb408ef2e1de7b310a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -67,11 +68,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778919578,
|
||||
"narHash": "sha256-+z+jgTly48gsAiX8rOe/vs8C/2G4vdCpcEtqMJUpFqw=",
|
||||
"lastModified": 1777018861,
|
||||
"narHash": "sha256-l+dfxHtTq1jQM53xgYudV8ciECFmJ72PcRAqRS4ys04=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "ecd6d4ff22cfdb1339b2915455a2ff4dc85bf52e",
|
||||
"rev": "7b33c6466f781cd699fe250c5b69dc4193da67a7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -83,11 +84,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1777699697,
|
||||
"narHash": "sha256-Eg9b/rq/ECYwNwEXs5i9wHyhxNI0JrYx2srdI2uZMaQ=",
|
||||
"rev": "382052b74656a369c5408822af3f2501e9b1af81",
|
||||
"lastModified": 1751685974,
|
||||
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
|
||||
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/382052b74656a369c5408822af3f2501e9b1af81.tar.gz"
|
||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
@@ -102,11 +103,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"lastModified": 1775087534,
|
||||
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -115,6 +116,21 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"import-tree": {
|
||||
"locked": {
|
||||
"lastModified": 1763762820,
|
||||
"narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
|
||||
"owner": "vic",
|
||||
"repo": "import-tree",
|
||||
"rev": "3c23749d8013ec6daa1d7255057590e9ca726646",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "vic",
|
||||
"repo": "import-tree",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"llm-agents": {
|
||||
"inputs": {
|
||||
"blueprint": "blueprint",
|
||||
@@ -127,11 +143,11 @@
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778929997,
|
||||
"narHash": "sha256-iAfbBUHBbR0N4DFqFWr4Jtmpc1YcOK7kpVM4f0MK1V8=",
|
||||
"lastModified": 1777019177,
|
||||
"narHash": "sha256-YjPvucTsKmGO9QVNz07x7sSsK11PB0jtMniRkTolbq4=",
|
||||
"owner": "numtide",
|
||||
"repo": "llm-agents.nix",
|
||||
"rev": "0da8f0313c9f68c155e0932f880fc1913e7be846",
|
||||
"rev": "8ff0f2a7fcd176b4547da6879ad549de2bbded41",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -142,11 +158,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1778443072,
|
||||
"narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=",
|
||||
"lastModified": 1776548001,
|
||||
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32",
|
||||
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -167,11 +183,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1778854817,
|
||||
"narHash": "sha256-iG+VuMy8W585geVVCUd7pR025WsY3ZkgSv5Yt5bxDmQ=",
|
||||
"lastModified": 1776800521,
|
||||
"narHash": "sha256-f8YJfwAOsLFpIoqZuX3yF69UvMLrkx7iVzMH1pJU7cM=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "1a68212c5683555ad80f0eab71db9715c6d52145",
|
||||
"rev": "8954b66d43225e62c92e8bbcc8500191b5cceb1e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
Reference in New Issue
Block a user