Compare commits
3 Commits
29fab93cd1
...
4aa694aa3a
| Author | SHA1 | Date | |
|---|---|---|---|
|
4aa694aa3a
|
|||
|
d98e389606
|
|||
|
9a17990d5e
|
Generated
+147
@@ -576,6 +576,7 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
"sptr",
|
"sptr",
|
||||||
"string-interner",
|
"string-interner",
|
||||||
|
"sysinfo",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -877,6 +878,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
@@ -917,6 +927,25 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.37.3"
|
version = "0.37.3"
|
||||||
@@ -1322,6 +1351,20 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.27.0"
|
||||||
@@ -1715,12 +1758,107 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -1730,6 +1868,15 @@ dependencies = [
|
|||||||
"windows-link",
|
"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]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ bumpalo = { version = "3.20", features = [
|
|||||||
ghost-cell = "0.2"
|
ghost-cell = "0.2"
|
||||||
hashbrown = "0.16"
|
hashbrown = "0.16"
|
||||||
num_enum = "0.7.5"
|
num_enum = "0.7.5"
|
||||||
smallvec = "1.15"
|
smallvec = { version = "1.15", features = ["const_new", "const_generics"] }
|
||||||
ere = "0.2"
|
ere = "0.2"
|
||||||
string-interner = "0.19"
|
string-interner = "0.19"
|
||||||
rnix = "0.14"
|
rnix = "0.14"
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ use fix_error::Source;
|
|||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp,
|
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp, StaticValue, StrictValue, StringContext, Thunk, ThunkState, Value
|
||||||
StaticValue, StrictValue, Thunk, ThunkState, Value,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait VmContext {
|
pub trait VmContext {
|
||||||
@@ -36,6 +35,9 @@ pub trait VmRuntimeCtxExt: VmRuntimeCtx {
|
|||||||
&'a mut self,
|
&'a mut self,
|
||||||
val: StrictValue<'gc>,
|
val: StrictValue<'gc>,
|
||||||
) -> std::result::Result<StringId, NixType>;
|
) -> 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;
|
fn convert_value(&self, val: Value) -> fix_common::Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +75,14 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext {
|
||||||
|
if let Some(ns) = val.as_gc::<NixString>() {
|
||||||
|
ns.as_ref().context()
|
||||||
|
} else {
|
||||||
|
StringContext::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn convert_value(&self, val: Value) -> fix_common::Value {
|
fn convert_value(&self, val: Value) -> fix_common::Value {
|
||||||
self.convert_value_with_seen(val, &mut HashSet::new())
|
self.convert_value_with_seen(val, &mut HashSet::new())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ mod machine;
|
|||||||
mod path_util;
|
mod path_util;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
mod state;
|
mod state;
|
||||||
|
mod string_context;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
pub use bytecode_reader::*;
|
pub use bytecode_reader::*;
|
||||||
@@ -15,4 +16,5 @@ pub use machine::*;
|
|||||||
pub use path_util::*;
|
pub use path_util::*;
|
||||||
pub use resolve::*;
|
pub use resolve::*;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
|
pub use string_context::*;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ use string_interner::Symbol;
|
|||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::symbol::SymbolU32;
|
||||||
|
|
||||||
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||||
|
use crate::string_context::StringContext;
|
||||||
|
|
||||||
mod private {
|
mod private {
|
||||||
pub trait Cealed {}
|
pub trait Cealed {}
|
||||||
@@ -420,26 +421,41 @@ impl RawStore for Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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)]
|
#[derive(Collect)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub struct NixString {
|
pub struct NixString {
|
||||||
data: Box<str>,
|
data: Box<str>,
|
||||||
// TODO: string context for derivation dependency tracking
|
context: StringContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NixString {
|
impl NixString {
|
||||||
pub fn new(s: impl Into<Box<str>>) -> Self {
|
pub fn new(s: impl Into<Box<str>>) -> Self {
|
||||||
Self { data: s.into() }
|
Self {
|
||||||
|
data: s.into(),
|
||||||
|
context: StringContext::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 fn as_str(&self) -> &str {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn context(&self) -> &StringContext {
|
||||||
|
&self.context
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_context(&self) -> bool {
|
||||||
|
!self.context.is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for NixString {
|
impl fmt::Debug for NixString {
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ define_builtins! {
|
|||||||
("__tryEval", TryEval, 1),
|
("__tryEval", TryEval, 1),
|
||||||
("__typeOf", TypeOf, 1),
|
("__typeOf", TypeOf, 1),
|
||||||
("__unsafeDiscardStringContext", UnsafeDiscardStringContext, 1),
|
("__unsafeDiscardStringContext", UnsafeDiscardStringContext, 1),
|
||||||
|
("__unsafeDiscardOutputDependency", UnsafeDiscardOutputDependency, 1),
|
||||||
("__unsafeGetAttrPos", UnsafeGetAttrPos, 2),
|
("__unsafeGetAttrPos", UnsafeGetAttrPos, 2),
|
||||||
("__warn", Warn, 2),
|
("__warn", Warn, 2),
|
||||||
("__zipAttrsWith", ZipAttrsWith, 2),
|
("__zipAttrsWith", ZipAttrsWith, 2),
|
||||||
@@ -246,6 +247,9 @@ pub enum PrimOpPhase {
|
|||||||
ForceResultShallowLoop,
|
ForceResultShallowLoop,
|
||||||
ForceResultDeepFinish,
|
ForceResultDeepFinish,
|
||||||
|
|
||||||
|
EqStep,
|
||||||
|
EqForce,
|
||||||
|
|
||||||
// TODO: split into separate enums
|
// TODO: split into separate enums
|
||||||
CallPattern,
|
CallPattern,
|
||||||
CallFunctor1,
|
CallFunctor1,
|
||||||
@@ -254,6 +258,14 @@ pub enum PrimOpPhase {
|
|||||||
ImportFinalize,
|
ImportFinalize,
|
||||||
ScopedImportFinalize,
|
ScopedImportFinalize,
|
||||||
|
|
||||||
|
AppendContextLoop,
|
||||||
|
AppendContextEntryForced,
|
||||||
|
AppendContextOutputsForced,
|
||||||
|
AppendContextOutputElementLoop,
|
||||||
|
AppendContextOutputElementForced,
|
||||||
|
|
||||||
|
UnsafeDiscardOutputDependency,
|
||||||
|
|
||||||
Illegal,
|
Illegal,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,6 +384,7 @@ impl BuiltinId {
|
|||||||
TryEval => PrimOpPhase::TryEval,
|
TryEval => PrimOpPhase::TryEval,
|
||||||
TypeOf => PrimOpPhase::TypeOf,
|
TypeOf => PrimOpPhase::TypeOf,
|
||||||
UnsafeDiscardStringContext => PrimOpPhase::UnsafeDiscardStringContext,
|
UnsafeDiscardStringContext => PrimOpPhase::UnsafeDiscardStringContext,
|
||||||
|
UnsafeDiscardOutputDependency => PrimOpPhase::UnsafeDiscardOutputDependency,
|
||||||
UnsafeGetAttrPos => PrimOpPhase::UnsafeGetAttrPos,
|
UnsafeGetAttrPos => PrimOpPhase::UnsafeGetAttrPos,
|
||||||
Warn => PrimOpPhase::Warn,
|
Warn => PrimOpPhase::Warn,
|
||||||
ZipAttrsWith => PrimOpPhase::ZipAttrsWith,
|
ZipAttrsWith => PrimOpPhase::ZipAttrsWith,
|
||||||
|
|||||||
@@ -0,0 +1,447 @@
|
|||||||
|
//! `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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
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,11 +1,15 @@
|
|||||||
|
mod context;
|
||||||
mod control;
|
mod control;
|
||||||
mod conv;
|
mod conv;
|
||||||
|
mod eq;
|
||||||
mod io;
|
mod io;
|
||||||
mod list;
|
mod list;
|
||||||
mod path;
|
mod path;
|
||||||
|
|
||||||
|
pub use context::*;
|
||||||
pub use control::*;
|
pub use control::*;
|
||||||
pub use conv::*;
|
pub use conv::*;
|
||||||
|
pub use eq::*;
|
||||||
use fix_abstract_vm::{BytecodeReader, Machine, Step, VmRuntimeCtx};
|
use fix_abstract_vm::{BytecodeReader, Machine, Step, VmRuntimeCtx};
|
||||||
use fix_builtins::PrimOpPhase;
|
use fix_builtins::PrimOpPhase;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
@@ -49,6 +53,9 @@ pub fn dispatch_primop<'gc, M: Machine<'gc>>(
|
|||||||
ForceResultShallowLoop => force_result_shallow_loop(m, reader, mc),
|
ForceResultShallowLoop => force_result_shallow_loop(m, reader, mc),
|
||||||
ForceResultDeepFinish => force_result_deep_finish(m, ctx, 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),
|
CallPattern => call_pattern(m, ctx, reader, mc),
|
||||||
CallFunctor1 => call_functor_1(m, reader, mc),
|
CallFunctor1 => call_functor_1(m, reader, mc),
|
||||||
CallFunctor2 => call_functor_2(m, reader, mc),
|
CallFunctor2 => call_functor_2(m, reader, mc),
|
||||||
@@ -64,6 +71,17 @@ pub fn dispatch_primop<'gc, M: Machine<'gc>>(
|
|||||||
ToString => to_string(m, ctx, reader, mc),
|
ToString => to_string(m, ctx, reader, mc),
|
||||||
TypeOf => type_of(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:?}"),
|
phase => todo!("primop phase {phase:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ smallvec = { workspace = true }
|
|||||||
string-interner = { workspace = true }
|
string-interner = { workspace = true }
|
||||||
likely_stable = { workspace = true }
|
likely_stable = { workspace = true }
|
||||||
sptr = "0.3"
|
sptr = "0.3"
|
||||||
|
sysinfo = { version = "0.38", default-features = false, features = ["system"] }
|
||||||
|
|
||||||
fix-builtins = { path = "../fix-builtins" }
|
fix-builtins = { path = "../fix-builtins" }
|
||||||
fix-codegen = { path = "../fix-codegen" }
|
fix-codegen = { path = "../fix-codegen" }
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
|
if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string_or_path(rhs)) {
|
||||||
let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}")));
|
let merged = ctx
|
||||||
|
.get_string_context(lhs)
|
||||||
|
.merge(ctx.get_string_context(rhs));
|
||||||
|
let ns = Gc::new(
|
||||||
|
mc,
|
||||||
|
crate::NixString::with_context(format!("{ls}{rs}"), merged),
|
||||||
|
);
|
||||||
self.push(Value::new_gc(ns));
|
self.push(Value::new_gc(ns));
|
||||||
return Step::Continue(());
|
return Step::Continue(());
|
||||||
}
|
}
|
||||||
@@ -105,12 +111,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
let eq = match self.values_equal(ctx, lhs, rhs) {
|
fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, false)
|
||||||
Ok(eq) => eq,
|
|
||||||
Err(e) => return self.finish_vm_err(e),
|
|
||||||
};
|
|
||||||
self.push(Value::new_inline(eq));
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -121,12 +122,7 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
mc: &Mutation<'gc>,
|
mc: &Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
let (lhs, rhs) = self.force_and_retry::<(StrictValue, StrictValue)>(reader, mc)?;
|
||||||
let eq = match self.values_equal(ctx, lhs, rhs) {
|
fix_primops::start_eq(self, ctx, reader, mc, lhs, rhs, true)
|
||||||
Ok(eq) => eq,
|
|
||||||
Err(e) => return self.finish_vm_err(e),
|
|
||||||
};
|
|
||||||
self.push(Value::new_inline(!eq));
|
|
||||||
Step::Continue(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -230,67 +226,6 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
Step::Continue(())
|
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);
|
|
||||||
}
|
|
||||||
// Paths only equal paths (not strings, even if their text matches).
|
|
||||||
if let (Some(a), Some(b)) = (lhs.as_inline::<Path>(), rhs.as_inline::<Path>()) {
|
|
||||||
return Ok(a.0 == b.0);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
let b = &b.entries;
|
|
||||||
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(
|
fn compare_values_inner(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &impl VmRuntimeCtx,
|
ctx: &impl VmRuntimeCtx,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use fix_abstract_vm::{AttrSet, NixString, Path, StrictValue, canon_path_str};
|
use fix_abstract_vm::{
|
||||||
|
AttrSet, NixString, Path, StrictValue, StringContext, canon_path_str
|
||||||
|
};
|
||||||
use fix_builtins::BuiltinId;
|
use fix_builtins::BuiltinId;
|
||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
use fix_error::Error;
|
use fix_error::Error;
|
||||||
@@ -89,29 +91,45 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
_mc: &gc_arena::Mutation<'gc>,
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
let count = reader.read_u16() as usize;
|
let count = reader.read_u16() as usize;
|
||||||
let _force_string = reader.read_u8() != 0;
|
let _force_string = reader.read_u8() != 0;
|
||||||
|
|
||||||
let mut total_len = 0;
|
let mut total_len = 0;
|
||||||
|
let mut has_any_context = false;
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let val = self.peek_forced(count - 1 - i);
|
let val = self.peek_forced(count - 1 - i);
|
||||||
let s = ctx.get_string(val).expect("coerced");
|
let s = ctx.get_string(val).expect("coerced");
|
||||||
total_len += s.len();
|
total_len += s.len();
|
||||||
|
if !ctx.get_string_context(val).is_empty() {
|
||||||
|
has_any_context = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = String::with_capacity(total_len);
|
let mut result = String::with_capacity(total_len);
|
||||||
|
let mut merged = StringContext::new();
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let val = self.peek_forced(count - 1 - i);
|
let val = self.peek_forced(count - 1 - i);
|
||||||
let s = ctx.get_string(val).expect("coerced");
|
let s = ctx.get_string(val).expect("coerced");
|
||||||
result.push_str(s);
|
result.push_str(s);
|
||||||
|
if has_any_context {
|
||||||
|
let ctx = ctx.get_string_context(val);
|
||||||
|
if !ctx.is_empty() {
|
||||||
|
merged = merged.merge(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stack.truncate(self.stack.len() - count);
|
self.stack.truncate(self.stack.len() - count);
|
||||||
|
|
||||||
|
if merged.is_empty() {
|
||||||
let sid = ctx.intern_string(result);
|
let sid = ctx.intern_string(result);
|
||||||
self.push(Value::new_inline(sid));
|
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(())
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-9
@@ -11,7 +11,7 @@ use fix_builtins::{BUILTINS, BuiltinId};
|
|||||||
use fix_codegen::InstructionPtr;
|
use fix_codegen::InstructionPtr;
|
||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
use fix_error::{Error, Result, Source};
|
use fix_error::{Error, Result, Source};
|
||||||
use gc_arena::arena::CollectionPhase;
|
use gc_arena::metrics::Pacing;
|
||||||
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
use gc_arena::{Arena, Collect, Gc, Mutation, RefLock, Rootable};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
@@ -485,6 +485,20 @@ enum Action {
|
|||||||
LoadFile(PendingLoad),
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
impl Vm<'_> {
|
impl Vm<'_> {
|
||||||
pub fn run<C: VmContext>(
|
pub fn run<C: VmContext>(
|
||||||
ctx: &mut C,
|
ctx: &mut C,
|
||||||
@@ -493,8 +507,10 @@ impl Vm<'_> {
|
|||||||
) -> Result<fix_common::Value> {
|
) -> Result<fix_common::Value> {
|
||||||
let (code, runtime) = ctx.split();
|
let (code, runtime) = ctx.split();
|
||||||
let mut arena: Arena<Rootable![Vm<'_>]> = Arena::new(|mc| Vm::new(force_mode, mc, runtime));
|
let mut arena: Arena<Rootable![Vm<'_>]> = Arena::new(|mc| Vm::new(force_mode, mc, runtime));
|
||||||
|
arena.metrics().set_pacing(Pacing {
|
||||||
const COLLECTOR_GRANULARITY: f64 = 1024.0;
|
min_sleep: initial_heap_size(),
|
||||||
|
..Pacing::STOP_THE_WORLD
|
||||||
|
});
|
||||||
|
|
||||||
let mut pc = ip.0;
|
let mut pc = ip.0;
|
||||||
loop {
|
loop {
|
||||||
@@ -502,12 +518,8 @@ impl Vm<'_> {
|
|||||||
match arena.mutate_root(|mc, root| root.dispatch_batch(bytecode, runtime, pc, mc)) {
|
match arena.mutate_root(|mc, root| root.dispatch_batch(bytecode, runtime, pc, mc)) {
|
||||||
Action::Continue { pc: new_pc } => {
|
Action::Continue { pc: new_pc } => {
|
||||||
pc = new_pc;
|
pc = new_pc;
|
||||||
if arena.metrics().allocation_debt() > COLLECTOR_GRANULARITY {
|
if arena.metrics().allocation_debt() > 0.0 {
|
||||||
if arena.collection_phase() == CollectionPhase::Sweeping {
|
arena.finish_cycle();
|
||||||
arena.collect_debt();
|
|
||||||
} else if let Some(marked) = arena.mark_debt() {
|
|
||||||
marked.start_sweeping();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::LoadFile(load) => {
|
Action::LoadFile(load) => {
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
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
+18
-18
@@ -45,11 +45,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777369708,
|
"lastModified": 1778445566,
|
||||||
"narHash": "sha256-1xW7cRZNsFNPQD+cE0fwnLVStnDth0HSoASEIFeT7uI=",
|
"narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "bun2nix",
|
"repo": "bun2nix",
|
||||||
"rev": "e659e1cc4b8e1b21d0aa85f1c481f9db61ecfa98",
|
"rev": "2499dedd70744dba1815875b854818a3019e9e4c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -67,11 +67,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777796307,
|
"lastModified": 1778919578,
|
||||||
"narHash": "sha256-L7xLjorTwVf2aLu5b0ZZY2D0RFXwD/a/a/fFFDikB2w=",
|
"narHash": "sha256-+z+jgTly48gsAiX8rOe/vs8C/2G4vdCpcEtqMJUpFqw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "0f9881f2344c0b1c100bd9e774555759b7da6fd5",
|
"rev": "ecd6d4ff22cfdb1339b2915455a2ff4dc85bf52e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -102,11 +102,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777678872,
|
"lastModified": 1778716662,
|
||||||
"narHash": "sha256-EPIFsulyon7Z1vLQq5Fk64GR8L7cQsT+IPhcsukVbgk=",
|
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "5250617bffd85403b14dbf43c3870e7f255d2c16",
|
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -127,11 +127,11 @@
|
|||||||
"treefmt-nix": "treefmt-nix"
|
"treefmt-nix": "treefmt-nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777786380,
|
"lastModified": 1778929997,
|
||||||
"narHash": "sha256-GGKC1WrEoTafJwIXn+fim6cZ/w1ZWVc+DUYdk2lvPvA=",
|
"narHash": "sha256-iAfbBUHBbR0N4DFqFWr4Jtmpc1YcOK7kpVM4f0MK1V8=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "llm-agents.nix",
|
"repo": "llm-agents.nix",
|
||||||
"rev": "961b1096bc0b2ecc7096e360646cd2f29671c55e",
|
"rev": "0da8f0313c9f68c155e0932f880fc1913e7be846",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -142,11 +142,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777578337,
|
"lastModified": 1778443072,
|
||||||
"narHash": "sha256-Ad49moKWeXtKBJNy2ebiTQUEgdLyvGmTeykAQ9xM+Z4=",
|
"narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "15f4ee454b1dce334612fa6843b3e05cf546efab",
|
"rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -167,11 +167,11 @@
|
|||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777768857,
|
"lastModified": 1778854817,
|
||||||
"narHash": "sha256-zfekJcaVctfAps1KDHwZpwkvAQn7GObRHh3Gl3xocGI=",
|
"narHash": "sha256-iG+VuMy8W585geVVCUd7pR025WsY3ZkgSv5Yt5bxDmQ=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "1102c0b633599564919e36076d4362d7e68dbddc",
|
"rev": "1a68212c5683555ad80f0eab71db9715c6d52145",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
Reference in New Issue
Block a user