feat: init Rust VM
This commit is contained in:
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -41,12 +41,6 @@ version = "0.2.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "allocator-api2"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anes"
|
name = "anes"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@@ -196,9 +190,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "boxing"
|
name = "boxing"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a817f12ef805b34fe1565bea00630d84f8f08bf26200b05c41456c77cdada88"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sptr",
|
"sptr",
|
||||||
]
|
]
|
||||||
@@ -220,7 +212,7 @@ version = "3.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2 0.2.21",
|
"allocator-api2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -804,7 +796,6 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
|||||||
name = "fix"
|
name = "fix"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2 0.4.0",
|
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
"boxing",
|
"boxing",
|
||||||
@@ -835,10 +826,13 @@ dependencies = [
|
|||||||
"rusqlite",
|
"rusqlite",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
|
"sealed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha1",
|
"sha1",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"small-map",
|
||||||
|
"smallvec",
|
||||||
"string-interner",
|
"string-interner",
|
||||||
"tap",
|
"tap",
|
||||||
"tar",
|
"tar",
|
||||||
@@ -980,20 +974,18 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gc-arena"
|
name = "gc-arena"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/kyren/gc-arena?rev=75671ae03f53718357b741ed4027560f14e90836#75671ae03f53718357b741ed4027560f14e90836"
|
||||||
checksum = "3cd70cf88a32937834aae9614ff2569b5d9467fa0c42c5d7762fd94a8de88266"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2 0.2.21",
|
"allocator-api2",
|
||||||
"gc-arena-derive",
|
"gc-arena-derive",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.16.1",
|
||||||
"sptr",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gc-arena-derive"
|
name = "gc-arena-derive"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/kyren/gc-arena?rev=75671ae03f53718357b741ed4027560f14e90836#75671ae03f53718357b741ed4027560f14e90836"
|
||||||
checksum = "c612a69f5557a11046b77a7408d2836fe77077f842171cd211c5ef504bd3cddd"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1079,9 +1071,6 @@ name = "hashbrown"
|
|||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
dependencies = [
|
|
||||||
"allocator-api2 0.2.21",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
@@ -1098,7 +1087,7 @@ version = "0.16.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2 0.2.21",
|
"allocator-api2",
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"foldhash 0.2.0",
|
"foldhash 0.2.0",
|
||||||
]
|
]
|
||||||
@@ -2024,6 +2013,15 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rapidhash"
|
||||||
|
version = "4.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -2385,6 +2383,17 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sealed"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.6.0"
|
version = "3.6.0"
|
||||||
@@ -2524,6 +2533,17 @@ version = "0.4.12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "small-map"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64c0b79fa0e86d80062670059d27f93911776cdeba2555bf428b455d8738c32f"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.16.1",
|
||||||
|
"rapidhash",
|
||||||
|
"rustc-hash 2.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = [
|
members = [
|
||||||
"fix"
|
"fix",
|
||||||
|
"boxing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.profiling]
|
[profile.profiling]
|
||||||
|
|||||||
8
boxing/Cargo.toml
Normal file
8
boxing/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "boxing"
|
||||||
|
version = "0.1.3"
|
||||||
|
edition = "2021"
|
||||||
|
description = "NaN-boxing primitives (local fork with bool fix)"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sptr = "0.3"
|
||||||
2
boxing/src/lib.rs
Normal file
2
boxing/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod nan;
|
||||||
|
mod utils;
|
||||||
7
boxing/src/nan.rs
Normal file
7
boxing/src/nan.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod raw;
|
||||||
|
|
||||||
|
pub use raw::RawBox;
|
||||||
|
|
||||||
|
const SIGN_MASK: u64 = 0x7FFF_FFFF_FFFF_FFFF;
|
||||||
|
const QUIET_NAN: u64 = 0x7FF8_0000_0000_0000;
|
||||||
|
const NEG_QUIET_NAN: u64 = 0xFFF8_0000_0000_0000;
|
||||||
471
boxing/src/nan/raw.rs
Normal file
471
boxing/src/nan/raw.rs
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
use super::{NEG_QUIET_NAN, QUIET_NAN, SIGN_MASK};
|
||||||
|
use crate::utils::ArrayExt;
|
||||||
|
use sptr::Strict;
|
||||||
|
use std::fmt;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::num::NonZeroU8;
|
||||||
|
|
||||||
|
pub trait RawStore: Sized {
|
||||||
|
fn to_val(self, value: &mut Value);
|
||||||
|
fn from_val(value: &Value) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawStore for [u8; 6] {
|
||||||
|
#[inline]
|
||||||
|
fn to_val(self, value: &mut Value) {
|
||||||
|
value.set_data(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_val(value: &Value) -> Self {
|
||||||
|
*value.data()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawStore for bool {
|
||||||
|
#[inline]
|
||||||
|
fn to_val(self, value: &mut Value) {
|
||||||
|
value.set_data([u8::from(self)].truncate_to());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_val(value: &Value) -> Self {
|
||||||
|
value.data()[0] == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! int_store {
|
||||||
|
($ty:ty) => {
|
||||||
|
impl RawStore for $ty {
|
||||||
|
#[inline]
|
||||||
|
fn to_val(self, value: &mut Value) {
|
||||||
|
let bytes = self.to_ne_bytes();
|
||||||
|
value.set_data(bytes.truncate_to());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_val(value: &Value) -> Self {
|
||||||
|
<$ty>::from_ne_bytes(value.data().truncate_to())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int_store!(u8);
|
||||||
|
int_store!(u16);
|
||||||
|
int_store!(u32);
|
||||||
|
|
||||||
|
int_store!(i8);
|
||||||
|
int_store!(i16);
|
||||||
|
int_store!(i32);
|
||||||
|
|
||||||
|
fn store_ptr<P: Strict + Copy>(value: &mut Value, ptr: P) {
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
{
|
||||||
|
assert!(
|
||||||
|
ptr.addr() <= 0x0000_FFFF_FFFF_FFFF,
|
||||||
|
"Pointer too large to store in NaN box"
|
||||||
|
);
|
||||||
|
|
||||||
|
let val = (unsafe { value.whole_mut() } as *mut [u8; 8]).cast::<P>();
|
||||||
|
|
||||||
|
let ptr = Strict::map_addr(ptr, |addr| {
|
||||||
|
addr | (usize::from(value.header().into_raw()) << 48)
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { val.write(ptr) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
{
|
||||||
|
let _ = (value, ptr);
|
||||||
|
unimplemented!("32-bit pointer storage not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_ptr<P: Strict>(value: &Value) -> P {
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
{
|
||||||
|
let val = (unsafe { value.whole() } as *const [u8; 8]).cast::<P>();
|
||||||
|
let ptr = unsafe { val.read() };
|
||||||
|
Strict::map_addr(ptr, |addr| addr & 0x0000_FFFF_FFFF_FFFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
{
|
||||||
|
let _ = value;
|
||||||
|
unimplemented!("32-bit pointer storage not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RawStore for *const T {
|
||||||
|
fn to_val(self, value: &mut Value) {
|
||||||
|
store_ptr::<*const T>(value, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_val(value: &Value) -> Self {
|
||||||
|
load_ptr::<*const T>(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RawStore for *mut T {
|
||||||
|
fn to_val(self, value: &mut Value) {
|
||||||
|
store_ptr::<*mut T>(value, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_val(value: &Value) -> Self {
|
||||||
|
load_ptr::<*mut T>(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum TagVal {
|
||||||
|
_P1,
|
||||||
|
_P2,
|
||||||
|
_P3,
|
||||||
|
_P4,
|
||||||
|
_P5,
|
||||||
|
_P6,
|
||||||
|
_P7,
|
||||||
|
|
||||||
|
_N1,
|
||||||
|
_N2,
|
||||||
|
_N3,
|
||||||
|
_N4,
|
||||||
|
_N5,
|
||||||
|
_N6,
|
||||||
|
_N7,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RawTag(TagVal);
|
||||||
|
|
||||||
|
impl RawTag {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(neg: bool, val: NonZeroU8) -> RawTag {
|
||||||
|
unsafe { Self::new_unchecked(neg, val.get() & 0x07) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new_checked(neg: bool, val: u8) -> Option<RawTag> {
|
||||||
|
Some(RawTag(match (neg, val) {
|
||||||
|
(false, 1) => TagVal::_P1,
|
||||||
|
(false, 2) => TagVal::_P2,
|
||||||
|
(false, 3) => TagVal::_P3,
|
||||||
|
(false, 4) => TagVal::_P4,
|
||||||
|
(false, 5) => TagVal::_P5,
|
||||||
|
(false, 6) => TagVal::_P6,
|
||||||
|
(false, 7) => TagVal::_P7,
|
||||||
|
|
||||||
|
(true, 1) => TagVal::_N1,
|
||||||
|
(true, 2) => TagVal::_N2,
|
||||||
|
(true, 3) => TagVal::_N3,
|
||||||
|
(true, 4) => TagVal::_N4,
|
||||||
|
(true, 5) => TagVal::_N5,
|
||||||
|
(true, 6) => TagVal::_N6,
|
||||||
|
(true, 7) => TagVal::_N7,
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// `val` must be in the range `1..8`
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub unsafe fn new_unchecked(neg: bool, val: u8) -> RawTag {
|
||||||
|
RawTag(match (neg, val) {
|
||||||
|
(false, 1) => TagVal::_P1,
|
||||||
|
(false, 2) => TagVal::_P2,
|
||||||
|
(false, 3) => TagVal::_P3,
|
||||||
|
(false, 4) => TagVal::_P4,
|
||||||
|
(false, 5) => TagVal::_P5,
|
||||||
|
(false, 6) => TagVal::_P6,
|
||||||
|
(false, 7) => TagVal::_P7,
|
||||||
|
|
||||||
|
(true, 1) => TagVal::_N1,
|
||||||
|
(true, 2) => TagVal::_N2,
|
||||||
|
(true, 3) => TagVal::_N3,
|
||||||
|
(true, 4) => TagVal::_N4,
|
||||||
|
(true, 5) => TagVal::_N5,
|
||||||
|
(true, 6) => TagVal::_N6,
|
||||||
|
(true, 7) => TagVal::_N7,
|
||||||
|
|
||||||
|
_ => unsafe { core::hint::unreachable_unchecked() },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_neg(self) -> bool {
|
||||||
|
matches!(self.0, |TagVal::_N1| TagVal::_N2
|
||||||
|
| TagVal::_N3
|
||||||
|
| TagVal::_N4
|
||||||
|
| TagVal::_N5
|
||||||
|
| TagVal::_N6
|
||||||
|
| TagVal::_N7)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn val(self) -> NonZeroU8 {
|
||||||
|
match self.0 {
|
||||||
|
TagVal::_P1 | TagVal::_N1 => NonZeroU8::MIN,
|
||||||
|
TagVal::_P2 | TagVal::_N2 => NonZeroU8::MIN.saturating_add(1),
|
||||||
|
TagVal::_P3 | TagVal::_N3 => NonZeroU8::MIN.saturating_add(2),
|
||||||
|
TagVal::_P4 | TagVal::_N4 => NonZeroU8::MIN.saturating_add(3),
|
||||||
|
TagVal::_P5 | TagVal::_N5 => NonZeroU8::MIN.saturating_add(4),
|
||||||
|
TagVal::_P6 | TagVal::_N6 => NonZeroU8::MIN.saturating_add(5),
|
||||||
|
TagVal::_P7 | TagVal::_N7 => NonZeroU8::MIN.saturating_add(6),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn neg_val(self) -> (bool, u8) {
|
||||||
|
match self.0 {
|
||||||
|
TagVal::_P1 => (false, 1),
|
||||||
|
TagVal::_P2 => (false, 2),
|
||||||
|
TagVal::_P3 => (false, 3),
|
||||||
|
TagVal::_P4 => (false, 4),
|
||||||
|
TagVal::_P5 => (false, 5),
|
||||||
|
TagVal::_P6 => (false, 6),
|
||||||
|
TagVal::_P7 => (false, 7),
|
||||||
|
TagVal::_N1 => (true, 1),
|
||||||
|
TagVal::_N2 => (true, 2),
|
||||||
|
TagVal::_N3 => (true, 3),
|
||||||
|
TagVal::_N4 => (true, 4),
|
||||||
|
TagVal::_N5 => (true, 5),
|
||||||
|
TagVal::_N6 => (true, 6),
|
||||||
|
TagVal::_N7 => (true, 7),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for RawTag {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("RawTag")
|
||||||
|
.field("neg", &self.is_neg())
|
||||||
|
.field("val", &self.val())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
struct Header(u16);
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
#[inline]
|
||||||
|
fn new(tag: RawTag) -> Header {
|
||||||
|
let (neg, val) = tag.neg_val();
|
||||||
|
Header(0x7FF8 | (u16::from(neg) << 15) | u16::from(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn tag(self) -> RawTag {
|
||||||
|
unsafe { RawTag::new_unchecked(self.get_sign(), self.get_tag()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_sign(self) -> bool {
|
||||||
|
self.0 & 0x8000 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_tag(self) -> u8 {
|
||||||
|
(self.0 & 0x0007) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_raw(self) -> u16 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[repr(C, align(8))]
|
||||||
|
pub struct Value {
|
||||||
|
#[cfg(target_endian = "big")]
|
||||||
|
header: Header,
|
||||||
|
data: [u8; 6],
|
||||||
|
#[cfg(target_endian = "little")]
|
||||||
|
header: Header,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(tag: RawTag, data: [u8; 6]) -> Value {
|
||||||
|
Value {
|
||||||
|
header: Header::new(tag),
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn empty(tag: RawTag) -> Value {
|
||||||
|
Value::new(tag, [0; 6])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store<T: RawStore>(tag: RawTag, val: T) -> Value {
|
||||||
|
let mut v = Value::new(tag, [0; 6]);
|
||||||
|
T::to_val(val, &mut v);
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load<T: RawStore>(self) -> T {
|
||||||
|
T::from_val(&self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn tag(&self) -> RawTag {
|
||||||
|
self.header.tag()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn header(&self) -> &Header {
|
||||||
|
&self.header
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_data(&mut self, val: [u8; 6]) {
|
||||||
|
self.data = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn data(&self) -> &[u8; 6] {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn data_mut(&mut self) -> &mut [u8; 6] {
|
||||||
|
&mut self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub unsafe fn whole(&self) -> &[u8; 8] {
|
||||||
|
let ptr = (self as *const Value).cast::<[u8; 8]>();
|
||||||
|
unsafe { &*ptr }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub unsafe fn whole_mut(&mut self) -> &mut [u8; 8] {
|
||||||
|
let ptr = (self as *mut Value).cast::<[u8; 8]>();
|
||||||
|
unsafe { &mut *ptr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub union RawBox {
|
||||||
|
float: f64,
|
||||||
|
value: ManuallyDrop<Value>,
|
||||||
|
bits: u64,
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
ptr: *const (),
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
ptr: (u32, *const ()),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawBox {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_float(val: f64) -> RawBox {
|
||||||
|
match (val.is_nan(), val.is_sign_positive()) {
|
||||||
|
(true, true) => RawBox {
|
||||||
|
float: f64::from_bits(QUIET_NAN),
|
||||||
|
},
|
||||||
|
(true, false) => RawBox {
|
||||||
|
float: f64::from_bits(NEG_QUIET_NAN),
|
||||||
|
},
|
||||||
|
(false, _) => RawBox { float: val },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_value(value: Value) -> RawBox {
|
||||||
|
RawBox {
|
||||||
|
value: ManuallyDrop::new(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn tag(&self) -> Option<RawTag> {
|
||||||
|
if self.is_value() {
|
||||||
|
Some(unsafe { self.value.tag() })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_float(&self) -> bool {
|
||||||
|
(unsafe { !self.float.is_nan() } || unsafe { self.bits & SIGN_MASK == QUIET_NAN })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_value(&self) -> bool {
|
||||||
|
(unsafe { self.float.is_nan() } && unsafe { self.bits & SIGN_MASK != QUIET_NAN })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn float(&self) -> Option<&f64> {
|
||||||
|
if self.is_float() {
|
||||||
|
Some(unsafe { &self.float })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn value(&self) -> Option<&Value> {
|
||||||
|
if self.is_value() {
|
||||||
|
Some(unsafe { &self.value })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn into_float_unchecked(self) -> f64 {
|
||||||
|
unsafe { self.float }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for RawBox {
|
||||||
|
#[inline]
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
RawBox {
|
||||||
|
ptr: unsafe { self.ptr },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for RawBox {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self.float() {
|
||||||
|
Some(val) => f.debug_tuple("RawBox::Float").field(val).finish(),
|
||||||
|
None => {
|
||||||
|
let val = self.value().expect("RawBox is neither float nor value");
|
||||||
|
|
||||||
|
f.debug_struct("RawBox::Data")
|
||||||
|
.field("tag", &val.tag())
|
||||||
|
.field("data", val.data())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
boxing/src/utils.rs
Normal file
16
boxing/src/utils.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
pub trait ArrayExt<const LEN: usize> {
|
||||||
|
type Elem;
|
||||||
|
|
||||||
|
fn truncate_to<const M: usize>(self) -> [Self::Elem; M];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default + Copy, const N: usize> ArrayExt<N> for [T; N] {
|
||||||
|
type Elem = T;
|
||||||
|
|
||||||
|
fn truncate_to<const M: usize>(self) -> [Self::Elem; M] {
|
||||||
|
let copy_len = usize::min(N, M);
|
||||||
|
let mut out = [T::default(); M];
|
||||||
|
out[0..copy_len].copy_from_slice(&self[0..copy_len]);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,16 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|
||||||
tokio = { version = "1.41", features = ["rt-multi-thread", "sync", "net", "io-util"] }
|
tokio = { version = "1.41", features = [
|
||||||
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = ["wire", "async"] }
|
"rt-multi-thread",
|
||||||
|
"sync",
|
||||||
|
"net",
|
||||||
|
"io-util",
|
||||||
|
] }
|
||||||
|
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = [
|
||||||
|
"wire",
|
||||||
|
"async",
|
||||||
|
] }
|
||||||
|
|
||||||
# REPL
|
# REPL
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
@@ -26,9 +34,13 @@ miette = { version = "7.4", features = ["fancy"] }
|
|||||||
|
|
||||||
hashbrown = "0.16"
|
hashbrown = "0.16"
|
||||||
string-interner = "0.19"
|
string-interner = "0.19"
|
||||||
bumpalo = { version = "3.20", features = ["allocator-api2", "boxed", "collections"] }
|
bumpalo = { version = "3.20", features = [
|
||||||
|
"allocator-api2",
|
||||||
|
"boxed",
|
||||||
|
"collections",
|
||||||
|
] }
|
||||||
|
|
||||||
rust-embed="8.11"
|
rust-embed = "8.11"
|
||||||
|
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
|
|
||||||
@@ -42,7 +54,10 @@ hex = "0.4"
|
|||||||
|
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
|
||||||
reqwest = { version = "0.13", features = ["blocking", "rustls"], default-features = false }
|
reqwest = { version = "0.13", features = [
|
||||||
|
"blocking",
|
||||||
|
"rustls",
|
||||||
|
], default-features = false }
|
||||||
tar = "0.4"
|
tar = "0.4"
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
xz2 = "0.1"
|
xz2 = "0.1"
|
||||||
@@ -64,9 +79,15 @@ tap = "1.0.1"
|
|||||||
|
|
||||||
ghost-cell = "0.2"
|
ghost-cell = "0.2"
|
||||||
colored = "3.1"
|
colored = "3.1"
|
||||||
boxing = "0.1"
|
boxing = { path = "../boxing" }
|
||||||
gc-arena = { version = "0.5.3", features = ["allocator-api2"] }
|
sealed = "0.6"
|
||||||
allocator-api2 = "0.4.0"
|
small-map = "0.1"
|
||||||
|
smallvec = "1.15"
|
||||||
|
|
||||||
|
[dependencies.gc-arena]
|
||||||
|
git = "https://github.com/kyren/gc-arena"
|
||||||
|
rev = "75671ae03f53718357b741ed4027560f14e90836"
|
||||||
|
features = ["allocator-api2", "hashbrown", "smallvec"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.8", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use fix::context::Context;
|
|
||||||
use fix::error::{Result, Source};
|
use fix::error::{Result, Source};
|
||||||
|
use fix::runtime::Runtime;
|
||||||
use fix::value::Value;
|
use fix::value::Value;
|
||||||
|
|
||||||
pub fn eval(expr: &str) -> Value {
|
pub fn eval(expr: &str) -> Value {
|
||||||
Context::new()
|
Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.eval(Source::new_eval(expr.into()).unwrap())
|
.eval(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_result(expr: &str) -> Result<Value> {
|
pub fn eval_result(expr: &str) -> Result<Value> {
|
||||||
Context::new()
|
Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.eval(Source::new_eval(expr.into()).unwrap())
|
.eval(Source::new_eval(expr.into()).unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
|
use gc_arena::Collect;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
|
use string_interner::Symbol as _;
|
||||||
|
|
||||||
use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, SymId, ThunkId, UnOpKind};
|
use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind};
|
||||||
|
|
||||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
pub(crate) struct InstructionPtr(pub usize);
|
||||||
pub(crate) enum Constant {
|
|
||||||
Int(i64),
|
|
||||||
Float(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#[derive(Collect)]
|
||||||
|
#[collect(require_static)]
|
||||||
pub struct Bytecode {
|
pub struct Bytecode {
|
||||||
pub code: Box<[u8]>,
|
pub code: Box<[u8]>,
|
||||||
pub current_dir: String,
|
pub current_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait BytecodeContext {
|
pub(crate) trait BytecodeContext {
|
||||||
fn intern_string(&mut self, s: &str) -> u32;
|
fn intern_string(&mut self, s: &str) -> StringId;
|
||||||
fn intern_constant(&mut self, c: Constant) -> u32;
|
fn register_span(&mut self, range: TextRange) -> u32;
|
||||||
fn register_span(&self, range: TextRange) -> u32;
|
fn get_code(&self) -> &[u8];
|
||||||
fn get_sym(&self, id: SymId) -> &str;
|
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
||||||
fn get_current_dir(&self) -> &Path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy, TryFromPrimitive)]
|
#[derive(Clone, Copy, TryFromPrimitive)]
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum Op {
|
pub enum Op {
|
||||||
PushConst,
|
PushSmi,
|
||||||
|
PushBigInt,
|
||||||
|
PushFloat,
|
||||||
PushString,
|
PushString,
|
||||||
PushNull,
|
PushNull,
|
||||||
PushTrue,
|
PushTrue,
|
||||||
@@ -106,58 +106,78 @@ struct ScopeInfo {
|
|||||||
|
|
||||||
struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
||||||
ctx: &'a mut Ctx,
|
ctx: &'a mut Ctx,
|
||||||
code: Vec<u8>,
|
|
||||||
scope_stack: Vec<ScopeInfo>,
|
scope_stack: Vec<ScopeInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> Bytecode {
|
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
||||||
let current_dir = ctx.get_current_dir().to_string_lossy().to_string();
|
let ip = ctx.get_code().len();
|
||||||
let mut emitter = BytecodeEmitter::new(ctx);
|
let mut emitter = BytecodeEmitter::new(ctx);
|
||||||
emitter.emit_toplevel(ir);
|
emitter.emit_toplevel(ir);
|
||||||
Bytecode {
|
InstructionPtr(ip)
|
||||||
code: emitter.code.into_boxed_slice(),
|
|
||||||
current_dir,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||||
fn new(ctx: &'a mut Ctx) -> Self {
|
fn new(ctx: &'a mut Ctx) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ctx,
|
ctx,
|
||||||
code: Vec::with_capacity(4096),
|
|
||||||
scope_stack: Vec::with_capacity(32),
|
scope_stack: Vec::with_capacity(32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn emit_op(&mut self, op: Op) {
|
fn emit_op(&mut self, op: Op) {
|
||||||
self.code.push(op as u8);
|
self.ctx.get_code_mut().push(op as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn emit_u8(&mut self, val: u8) {
|
fn emit_u8(&mut self, val: u8) {
|
||||||
self.code.push(val);
|
self.ctx.get_code_mut().push(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn emit_u16(&mut self, val: u16) {
|
fn emit_u16(&mut self, val: u16) {
|
||||||
self.code.extend_from_slice(&val.to_le_bytes());
|
self.ctx
|
||||||
|
.get_code_mut()
|
||||||
|
.extend_from_slice(&val.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn emit_u32(&mut self, val: u32) {
|
fn emit_u32(&mut self, val: u32) {
|
||||||
self.code.extend_from_slice(&val.to_le_bytes());
|
self.ctx
|
||||||
|
.get_code_mut()
|
||||||
|
.extend_from_slice(&val.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_i32(&mut self, val: i32) {
|
||||||
|
self.ctx
|
||||||
|
.get_code_mut()
|
||||||
|
.extend_from_slice(&val.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_i64(&mut self, val: i64) {
|
||||||
|
self.ctx
|
||||||
|
.get_code_mut()
|
||||||
|
.extend_from_slice(&val.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_f64(&mut self, val: f64) {
|
||||||
|
self.ctx
|
||||||
|
.get_code_mut()
|
||||||
|
.extend_from_slice(&val.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn emit_i32_placeholder(&mut self) -> usize {
|
fn emit_i32_placeholder(&mut self) -> usize {
|
||||||
let offset = self.code.len();
|
let offset = self.ctx.get_code_mut().len();
|
||||||
self.code.extend_from_slice(&[0u8; 4]);
|
self.ctx.get_code_mut().extend_from_slice(&[0u8; 4]);
|
||||||
offset
|
offset
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
fn patch_i32(&mut self, offset: usize, val: i32) {
|
fn patch_i32(&mut self, offset: usize, val: i32) {
|
||||||
self.code[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
|
self.ctx.get_code_mut()[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -168,11 +188,18 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn patch_jump_target(&mut self, placeholder_offset: usize) {
|
fn patch_jump_target(&mut self, placeholder_offset: usize) {
|
||||||
let current_pos = self.code.len();
|
let current_pos = self.ctx.get_code_mut().len();
|
||||||
let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4;
|
let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4;
|
||||||
self.patch_i32(placeholder_offset, relative_offset);
|
self.patch_i32(placeholder_offset, relative_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_str_id(&mut self, id: StringId) {
|
||||||
|
self.ctx
|
||||||
|
.get_code_mut()
|
||||||
|
.extend_from_slice(&(id.0.to_usize() as u32).to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
fn current_depth(&self) -> u16 {
|
fn current_depth(&self) -> u16 {
|
||||||
self.scope_stack.last().map_or(0, |s| s.depth)
|
self.scope_stack.last().map_or(0, |s| s.depth)
|
||||||
}
|
}
|
||||||
@@ -390,13 +417,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
let label_idx = self.ctx.intern_string(&label);
|
let label_idx = self.ctx.intern_string(&label);
|
||||||
|
|
||||||
let skip_patch = self.emit_jump_placeholder();
|
let skip_patch = self.emit_jump_placeholder();
|
||||||
let entry_point = self.code.len() as u32;
|
let entry_point = self.ctx.get_code_mut().len() as u32;
|
||||||
self.emit_expr(inner);
|
self.emit_expr(inner);
|
||||||
self.emit_op(Op::Return);
|
self.emit_op(Op::Return);
|
||||||
self.patch_jump_target(skip_patch);
|
self.patch_jump_target(skip_patch);
|
||||||
self.emit_op(Op::MakeThunk);
|
self.emit_op(Op::MakeThunk);
|
||||||
self.emit_u32(entry_point);
|
self.emit_u32(entry_point);
|
||||||
self.emit_u32(label_idx);
|
self.emit_str_id(label_idx);
|
||||||
let (_, local_idx) = self.resolve_thunk(id);
|
let (_, local_idx) = self.resolve_thunk(id);
|
||||||
self.emit_op(Op::StoreLocal);
|
self.emit_op(Op::StoreLocal);
|
||||||
self.emit_u32(local_idx);
|
self.emit_u32(local_idx);
|
||||||
@@ -406,14 +433,17 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
||||||
match ir.deref() {
|
match ir.deref() {
|
||||||
&Ir::Int(x) => {
|
&Ir::Int(x) => {
|
||||||
let idx = self.ctx.intern_constant(Constant::Int(x));
|
if x <= i32::MAX as i64 {
|
||||||
self.emit_op(Op::PushConst);
|
self.emit_op(Op::PushSmi);
|
||||||
self.emit_u32(idx);
|
self.emit_i32(x as i32);
|
||||||
|
} else {
|
||||||
|
self.emit_op(Op::PushBigInt);
|
||||||
|
self.emit_i64(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&Ir::Float(x) => {
|
&Ir::Float(x) => {
|
||||||
let idx = self.ctx.intern_constant(Constant::Float(x.to_bits()));
|
self.emit_op(Op::PushFloat);
|
||||||
self.emit_op(Op::PushConst);
|
self.emit_f64(x);
|
||||||
self.emit_u32(idx);
|
|
||||||
}
|
}
|
||||||
&Ir::Bool(true) => self.emit_op(Op::PushTrue),
|
&Ir::Bool(true) => self.emit_op(Op::PushTrue),
|
||||||
&Ir::Bool(false) => self.emit_op(Op::PushFalse),
|
&Ir::Bool(false) => self.emit_op(Op::PushFalse),
|
||||||
@@ -421,7 +451,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
Ir::Str(s) => {
|
Ir::Str(s) => {
|
||||||
let idx = self.ctx.intern_string(s.deref());
|
let idx = self.ctx.intern_string(s.deref());
|
||||||
self.emit_op(Op::PushString);
|
self.emit_op(Op::PushString);
|
||||||
self.emit_u32(idx);
|
self.emit_str_id(idx);
|
||||||
}
|
}
|
||||||
&Ir::Path(p) => {
|
&Ir::Path(p) => {
|
||||||
self.emit_expr(p);
|
self.emit_expr(p);
|
||||||
@@ -433,20 +463,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
|
|
||||||
self.emit_op(Op::JumpIfFalse);
|
self.emit_op(Op::JumpIfFalse);
|
||||||
let else_placeholder = self.emit_i32_placeholder();
|
let else_placeholder = self.emit_i32_placeholder();
|
||||||
let after_jif = self.code.len();
|
let after_jif = self.ctx.get_code_mut().len();
|
||||||
|
|
||||||
self.emit_expr(consq);
|
self.emit_expr(consq);
|
||||||
|
|
||||||
self.emit_op(Op::Jump);
|
self.emit_op(Op::Jump);
|
||||||
let end_placeholder = self.emit_i32_placeholder();
|
let end_placeholder = self.emit_i32_placeholder();
|
||||||
let after_jump = self.code.len();
|
let after_jump = self.ctx.get_code_mut().len();
|
||||||
|
|
||||||
let else_offset = (after_jump as i32) - (after_jif as i32);
|
let else_offset = (after_jump as i32) - (after_jif as i32);
|
||||||
self.patch_i32(else_placeholder, else_offset);
|
self.patch_i32(else_placeholder, else_offset);
|
||||||
|
|
||||||
self.emit_expr(alter);
|
self.emit_expr(alter);
|
||||||
|
|
||||||
let end_offset = (self.code.len() as i32) - (after_jump as i32);
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||||
self.patch_i32(end_placeholder, end_offset);
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
}
|
}
|
||||||
&Ir::BinOp { lhs, rhs, kind } => {
|
&Ir::BinOp { lhs, rhs, kind } => {
|
||||||
@@ -510,10 +540,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_op(Op::LoadBuiltins);
|
self.emit_op(Op::LoadBuiltins);
|
||||||
}
|
}
|
||||||
&Ir::Builtin(name) => {
|
&Ir::Builtin(name) => {
|
||||||
let sym = self.ctx.get_sym(name).to_string();
|
|
||||||
let idx = self.ctx.intern_string(&sym);
|
|
||||||
self.emit_op(Op::LoadBuiltin);
|
self.emit_op(Op::LoadBuiltin);
|
||||||
self.emit_u32(idx);
|
self.emit_u32(name.0.to_usize() as u32);
|
||||||
}
|
}
|
||||||
&Ir::ConcatStrings {
|
&Ir::ConcatStrings {
|
||||||
ref parts,
|
ref parts,
|
||||||
@@ -540,7 +568,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_expr(*assertion);
|
self.emit_expr(*assertion);
|
||||||
self.emit_expr(*expr);
|
self.emit_expr(*expr);
|
||||||
self.emit_op(Op::Assert);
|
self.emit_op(Op::Assert);
|
||||||
self.emit_u32(raw_idx);
|
self.emit_str_id(raw_idx);
|
||||||
self.emit_u32(span_id);
|
self.emit_u32(span_id);
|
||||||
}
|
}
|
||||||
&Ir::CurPos(span) => {
|
&Ir::CurPos(span) => {
|
||||||
@@ -549,16 +577,12 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_u32(span_id);
|
self.emit_u32(span_id);
|
||||||
}
|
}
|
||||||
&Ir::ReplBinding(name) => {
|
&Ir::ReplBinding(name) => {
|
||||||
let sym = self.ctx.get_sym(name).to_string();
|
|
||||||
let idx = self.ctx.intern_string(&sym);
|
|
||||||
self.emit_op(Op::LoadReplBinding);
|
self.emit_op(Op::LoadReplBinding);
|
||||||
self.emit_u32(idx);
|
self.emit_str_id(name);
|
||||||
}
|
}
|
||||||
&Ir::ScopedImportBinding(name) => {
|
&Ir::ScopedImportBinding(name) => {
|
||||||
let sym = self.ctx.get_sym(name).to_string();
|
|
||||||
let idx = self.ctx.intern_string(&sym);
|
|
||||||
self.emit_op(Op::LoadScopedBinding);
|
self.emit_op(Op::LoadScopedBinding);
|
||||||
self.emit_u32(idx);
|
self.emit_str_id(name);
|
||||||
}
|
}
|
||||||
&Ir::With {
|
&Ir::With {
|
||||||
namespace,
|
namespace,
|
||||||
@@ -568,10 +592,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_with(namespace, body, thunks);
|
self.emit_with(namespace, body, thunks);
|
||||||
}
|
}
|
||||||
&Ir::WithLookup(name) => {
|
&Ir::WithLookup(name) => {
|
||||||
let sym = self.ctx.get_sym(name).to_string();
|
|
||||||
let idx = self.ctx.intern_string(&sym);
|
|
||||||
self.emit_op(Op::WithLookup);
|
self.emit_op(Op::WithLookup);
|
||||||
self.emit_u32(idx);
|
self.emit_str_id(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -584,20 +606,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_op(Op::ForceBool);
|
self.emit_op(Op::ForceBool);
|
||||||
self.emit_op(Op::JumpIfFalse);
|
self.emit_op(Op::JumpIfFalse);
|
||||||
let skip_placeholder = self.emit_i32_placeholder();
|
let skip_placeholder = self.emit_i32_placeholder();
|
||||||
let after_jif = self.code.len();
|
let after_jif = self.ctx.get_code_mut().len();
|
||||||
|
|
||||||
self.emit_expr(rhs);
|
self.emit_expr(rhs);
|
||||||
self.emit_op(Op::ForceBool);
|
self.emit_op(Op::ForceBool);
|
||||||
self.emit_op(Op::Jump);
|
self.emit_op(Op::Jump);
|
||||||
let end_placeholder = self.emit_i32_placeholder();
|
let end_placeholder = self.emit_i32_placeholder();
|
||||||
let after_jump = self.code.len();
|
let after_jump = self.ctx.get_code_mut().len();
|
||||||
|
|
||||||
let false_offset = (after_jump as i32) - (after_jif as i32);
|
let false_offset = (after_jump as i32) - (after_jif as i32);
|
||||||
self.patch_i32(skip_placeholder, false_offset);
|
self.patch_i32(skip_placeholder, false_offset);
|
||||||
|
|
||||||
self.emit_op(Op::PushFalse);
|
self.emit_op(Op::PushFalse);
|
||||||
|
|
||||||
let end_offset = (self.code.len() as i32) - (after_jump as i32);
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||||
self.patch_i32(end_placeholder, end_offset);
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
}
|
}
|
||||||
Or => {
|
Or => {
|
||||||
@@ -605,20 +627,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_op(Op::ForceBool);
|
self.emit_op(Op::ForceBool);
|
||||||
self.emit_op(Op::JumpIfTrue);
|
self.emit_op(Op::JumpIfTrue);
|
||||||
let skip_placeholder = self.emit_i32_placeholder();
|
let skip_placeholder = self.emit_i32_placeholder();
|
||||||
let after_jit = self.code.len();
|
let after_jit = self.ctx.get_code_mut().len();
|
||||||
|
|
||||||
self.emit_expr(rhs);
|
self.emit_expr(rhs);
|
||||||
self.emit_op(Op::ForceBool);
|
self.emit_op(Op::ForceBool);
|
||||||
self.emit_op(Op::Jump);
|
self.emit_op(Op::Jump);
|
||||||
let end_placeholder = self.emit_i32_placeholder();
|
let end_placeholder = self.emit_i32_placeholder();
|
||||||
let after_jump = self.code.len();
|
let after_jump = self.ctx.get_code_mut().len();
|
||||||
|
|
||||||
let true_offset = (after_jump as i32) - (after_jit as i32);
|
let true_offset = (after_jump as i32) - (after_jit as i32);
|
||||||
self.patch_i32(skip_placeholder, true_offset);
|
self.patch_i32(skip_placeholder, true_offset);
|
||||||
|
|
||||||
self.emit_op(Op::PushTrue);
|
self.emit_op(Op::PushTrue);
|
||||||
|
|
||||||
let end_offset = (self.code.len() as i32) - (after_jump as i32);
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||||
self.patch_i32(end_placeholder, end_offset);
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
}
|
}
|
||||||
Impl => {
|
Impl => {
|
||||||
@@ -626,20 +648,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_op(Op::ForceBool);
|
self.emit_op(Op::ForceBool);
|
||||||
self.emit_op(Op::JumpIfFalse);
|
self.emit_op(Op::JumpIfFalse);
|
||||||
let skip_placeholder = self.emit_i32_placeholder();
|
let skip_placeholder = self.emit_i32_placeholder();
|
||||||
let after_jif = self.code.len();
|
let after_jif = self.ctx.get_code_mut().len();
|
||||||
|
|
||||||
self.emit_expr(rhs);
|
self.emit_expr(rhs);
|
||||||
self.emit_op(Op::ForceBool);
|
self.emit_op(Op::ForceBool);
|
||||||
self.emit_op(Op::Jump);
|
self.emit_op(Op::Jump);
|
||||||
let end_placeholder = self.emit_i32_placeholder();
|
let end_placeholder = self.emit_i32_placeholder();
|
||||||
let after_jump = self.code.len();
|
let after_jump = self.ctx.get_code_mut().len();
|
||||||
|
|
||||||
let true_offset = (after_jump as i32) - (after_jif as i32);
|
let true_offset = (after_jump as i32) - (after_jif as i32);
|
||||||
self.patch_i32(skip_placeholder, true_offset);
|
self.patch_i32(skip_placeholder, true_offset);
|
||||||
|
|
||||||
self.emit_op(Op::PushTrue);
|
self.emit_op(Op::PushTrue);
|
||||||
|
|
||||||
let end_offset = (self.code.len() as i32) - (after_jump as i32);
|
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||||
self.patch_i32(end_placeholder, end_offset);
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
}
|
}
|
||||||
PipeL => {
|
PipeL => {
|
||||||
@@ -688,7 +710,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||||
|
|
||||||
let skip_patch = self.emit_jump_placeholder();
|
let skip_patch = self.emit_jump_placeholder();
|
||||||
let entry_point = self.code.len() as u32;
|
let entry_point = self.ctx.get_code().len() as u32;
|
||||||
self.push_scope(true, Some(arg), &thunk_ids);
|
self.push_scope(true, Some(arg), &thunk_ids);
|
||||||
self.emit_scope_thunks(thunks);
|
self.emit_scope_thunks(thunks);
|
||||||
self.emit_expr(body);
|
self.emit_expr(body);
|
||||||
@@ -710,20 +732,14 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_u8(if *ellipsis { 1 } else { 0 });
|
self.emit_u8(if *ellipsis { 1 } else { 0 });
|
||||||
|
|
||||||
for &(sym, _) in required.iter() {
|
for &(sym, _) in required.iter() {
|
||||||
let name = self.ctx.get_sym(sym).to_string();
|
self.emit_str_id(sym);
|
||||||
let idx = self.ctx.intern_string(&name);
|
|
||||||
self.emit_u32(idx);
|
|
||||||
}
|
}
|
||||||
for &(sym, _) in optional.iter() {
|
for &(sym, _) in optional.iter() {
|
||||||
let name = self.ctx.get_sym(sym).to_string();
|
self.emit_str_id(sym);
|
||||||
let idx = self.ctx.intern_string(&name);
|
|
||||||
self.emit_u32(idx);
|
|
||||||
}
|
}
|
||||||
for &(sym, span) in required.iter().chain(optional.iter()) {
|
for &(sym, span) in required.iter().chain(optional.iter()) {
|
||||||
let name = self.ctx.get_sym(sym).to_string();
|
|
||||||
let name_idx = self.ctx.intern_string(&name);
|
|
||||||
let span_id = self.ctx.register_span(span);
|
let span_id = self.ctx.register_span(span);
|
||||||
self.emit_u32(name_idx);
|
self.emit_str_id(sym);
|
||||||
self.emit_u32(span_id);
|
self.emit_u32(span_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -735,7 +751,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
|
|
||||||
fn emit_attrset(
|
fn emit_attrset(
|
||||||
&mut self,
|
&mut self,
|
||||||
stcs: &crate::ir::HashMap<'_, SymId, (RawIrRef<'_>, TextRange)>,
|
stcs: &crate::ir::HashMap<'_, StringId, (RawIrRef<'_>, TextRange)>,
|
||||||
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
|
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
|
||||||
) {
|
) {
|
||||||
if stcs.is_empty() && dyns.is_empty() {
|
if stcs.is_empty() && dyns.is_empty() {
|
||||||
@@ -745,42 +761,35 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
|
|
||||||
if !dyns.is_empty() {
|
if !dyns.is_empty() {
|
||||||
for (&sym, &(val, _)) in stcs.iter() {
|
for (&sym, &(val, _)) in stcs.iter() {
|
||||||
let key = self.ctx.get_sym(sym).to_string();
|
|
||||||
let idx = self.ctx.intern_string(&key);
|
|
||||||
self.emit_op(Op::PushString);
|
self.emit_op(Op::PushString);
|
||||||
self.emit_u32(idx);
|
self.emit_str_id(sym);
|
||||||
self.emit_expr(val);
|
self.emit_expr(val);
|
||||||
}
|
}
|
||||||
for (_, &(_, span)) in stcs.iter() {
|
for (_, &(_, span)) in stcs.iter() {
|
||||||
let span_id = self.ctx.register_span(span);
|
let span_id = self.ctx.register_span(span);
|
||||||
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
self.emit_op(Op::PushSmi);
|
||||||
self.emit_op(Op::PushConst);
|
self.emit_u32(span_id);
|
||||||
self.emit_u32(idx);
|
|
||||||
}
|
}
|
||||||
for &(key, val, span) in dyns.iter() {
|
for &(key, val, span) in dyns.iter() {
|
||||||
self.emit_expr(key);
|
self.emit_expr(key);
|
||||||
self.emit_expr(val);
|
self.emit_expr(val);
|
||||||
let span_id = self.ctx.register_span(span);
|
let span_id = self.ctx.register_span(span);
|
||||||
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
self.emit_op(Op::PushSmi);
|
||||||
self.emit_op(Op::PushConst);
|
self.emit_u32(span_id);
|
||||||
self.emit_u32(idx);
|
|
||||||
}
|
}
|
||||||
self.emit_op(Op::MakeAttrsDyn);
|
self.emit_op(Op::MakeAttrsDyn);
|
||||||
self.emit_u32(stcs.len() as u32);
|
self.emit_u32(stcs.len() as u32);
|
||||||
self.emit_u32(dyns.len() as u32);
|
self.emit_u32(dyns.len() as u32);
|
||||||
} else {
|
} else {
|
||||||
for (&sym, &(val, _)) in stcs.iter() {
|
for (&sym, &(val, _)) in stcs.iter() {
|
||||||
let key = self.ctx.get_sym(sym).to_string();
|
|
||||||
let idx = self.ctx.intern_string(&key);
|
|
||||||
self.emit_op(Op::PushString);
|
self.emit_op(Op::PushString);
|
||||||
self.emit_u32(idx);
|
self.emit_str_id(sym);
|
||||||
self.emit_expr(val);
|
self.emit_expr(val);
|
||||||
}
|
}
|
||||||
for (_, &(_, span)) in stcs.iter() {
|
for (_, &(_, span)) in stcs.iter() {
|
||||||
let span_id = self.ctx.register_span(span);
|
let span_id = self.ctx.register_span(span);
|
||||||
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
self.emit_op(Op::PushSmi);
|
||||||
self.emit_op(Op::PushConst);
|
self.emit_u32(span_id);
|
||||||
self.emit_u32(idx);
|
|
||||||
}
|
}
|
||||||
self.emit_op(Op::MakeAttrs);
|
self.emit_op(Op::MakeAttrs);
|
||||||
self.emit_u32(stcs.len() as u32);
|
self.emit_u32(stcs.len() as u32);
|
||||||
@@ -796,15 +805,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
) {
|
) {
|
||||||
self.emit_expr(expr);
|
self.emit_expr(expr);
|
||||||
for attr in attrpath.iter() {
|
for attr in attrpath.iter() {
|
||||||
match attr {
|
match *attr {
|
||||||
Attr::Str(sym, _) => {
|
Attr::Str(sym, _) => {
|
||||||
let key = self.ctx.get_sym(*sym).to_string();
|
|
||||||
let idx = self.ctx.intern_string(&key);
|
|
||||||
self.emit_op(Op::PushString);
|
self.emit_op(Op::PushString);
|
||||||
self.emit_u32(idx);
|
self.emit_str_id(sym);
|
||||||
}
|
}
|
||||||
Attr::Dynamic(expr, _) => {
|
Attr::Dynamic(expr, _) => {
|
||||||
self.emit_expr(*expr);
|
self.emit_expr(expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -826,15 +833,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
||||||
self.emit_expr(lhs);
|
self.emit_expr(lhs);
|
||||||
for attr in rhs.iter() {
|
for attr in rhs.iter() {
|
||||||
match attr {
|
match *attr {
|
||||||
Attr::Str(sym, _) => {
|
Attr::Str(sym, _) => {
|
||||||
let key = self.ctx.get_sym(*sym).to_string();
|
|
||||||
let idx = self.ctx.intern_string(&key);
|
|
||||||
self.emit_op(Op::PushString);
|
self.emit_op(Op::PushString);
|
||||||
self.emit_u32(idx);
|
self.emit_str_id(sym);
|
||||||
}
|
}
|
||||||
Attr::Dynamic(expr, _) => {
|
Attr::Dynamic(expr, _) => {
|
||||||
self.emit_expr(*expr);
|
self.emit_expr(expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,592 +0,0 @@
|
|||||||
use std::cell::UnsafeCell;
|
|
||||||
use std::hash::BuildHasher;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use ghost_cell::{GhostCell, GhostToken};
|
|
||||||
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
|
|
||||||
use rnix::TextRange;
|
|
||||||
use string_interner::DefaultStringInterner;
|
|
||||||
|
|
||||||
use crate::bytecode::{self, Bytecode, BytecodeContext, Constant};
|
|
||||||
use crate::disassembler::{Disassembler, DisassemblerContext};
|
|
||||||
use crate::downgrade::*;
|
|
||||||
use crate::error::{Error, Result, Source};
|
|
||||||
use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, SymId, ThunkId, ir_content_eq};
|
|
||||||
use crate::store::{DaemonStore, Store, StoreConfig};
|
|
||||||
use crate::value::{Symbol, Value};
|
|
||||||
|
|
||||||
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
|
||||||
use rnix::ParseError::*;
|
|
||||||
match error {
|
|
||||||
Unexpected(range)
|
|
||||||
| UnexpectedExtra(range)
|
|
||||||
| UnexpectedWanted(_, range, _)
|
|
||||||
| UnexpectedDoubleBind(range)
|
|
||||||
| DuplicatedArgs(range, _) => Some(*range),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_parse_error<'a>(
|
|
||||||
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
|
||||||
source: Source,
|
|
||||||
) -> Option<Box<Error>> {
|
|
||||||
for err in errors {
|
|
||||||
if let Some(span) = parse_error_span(err) {
|
|
||||||
return Some(
|
|
||||||
Error::parse_error(err.to_string())
|
|
||||||
.with_source(source)
|
|
||||||
.with_span(span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
|
||||||
pub fn eval(&mut self, _source: Source) -> Result<Value> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
pub fn eval_shallow(&mut self, _source: Source) -> Result<Value> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
pub fn eval_deep(&mut self, _source: Source) -> Result<Value> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval_repl<'a>(&'a mut self, _source: Source, _scope: &'a HashSet<SymId>) -> Result<Value> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassemble(&self, bytecode: &Bytecode) -> String {
|
|
||||||
Disassembler::new(bytecode, self).disassemble()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disassemble_colored(&self, bytecode: &Bytecode) -> String {
|
|
||||||
Disassembler::new(bytecode, self).disassemble_colored()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_binding<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
name: &str,
|
|
||||||
expr: &str,
|
|
||||||
scope: &'a mut HashSet<SymId>,
|
|
||||||
) -> Result<Value> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Context {
|
|
||||||
symbols: DefaultStringInterner,
|
|
||||||
global: HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
|
|
||||||
sources: Vec<Source>,
|
|
||||||
store: DaemonStore,
|
|
||||||
spans: UnsafeCell<Vec<(usize, TextRange)>>,
|
|
||||||
thunk_count: usize,
|
|
||||||
global_strings: Vec<String>,
|
|
||||||
global_string_map: HashMap<String, u32>,
|
|
||||||
global_constants: Vec<Constant>,
|
|
||||||
global_constant_map: HashMap<Constant, u32>,
|
|
||||||
synced_strings: usize,
|
|
||||||
synced_constants: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Owns the bump allocator and a read-only reference into it.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// The `ir` field points into `_bump`'s storage. We use `'static` as a sentinel
|
|
||||||
/// lifetime because the struct owns the backing memory. The `as_ref` method
|
|
||||||
/// re-binds the lifetime to `&self`, preventing use-after-free.
|
|
||||||
struct OwnedIr {
|
|
||||||
_bump: Bump,
|
|
||||||
ir: RawIrRef<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OwnedIr {
|
|
||||||
fn as_ref(&self) -> RawIrRef<'_> {
|
|
||||||
self.ir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context {
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
let mut symbols = DefaultStringInterner::new();
|
|
||||||
let mut global = HashMap::new();
|
|
||||||
let builtins_sym = symbols.get_or_intern("builtins");
|
|
||||||
global.insert(builtins_sym, Ir::Builtins);
|
|
||||||
|
|
||||||
let free_globals = [
|
|
||||||
"abort",
|
|
||||||
"baseNameOf",
|
|
||||||
"break",
|
|
||||||
"dirOf",
|
|
||||||
"derivation",
|
|
||||||
"derivationStrict",
|
|
||||||
"fetchGit",
|
|
||||||
"fetchMercurial",
|
|
||||||
"fetchTarball",
|
|
||||||
"fetchTree",
|
|
||||||
"fromTOML",
|
|
||||||
"import",
|
|
||||||
"isNull",
|
|
||||||
"map",
|
|
||||||
"placeholder",
|
|
||||||
"removeAttrs",
|
|
||||||
"scopedImport",
|
|
||||||
"throw",
|
|
||||||
"toString",
|
|
||||||
];
|
|
||||||
let consts = [
|
|
||||||
("true", Ir::Bool(true)),
|
|
||||||
("false", Ir::Bool(false)),
|
|
||||||
("null", Ir::Null),
|
|
||||||
];
|
|
||||||
|
|
||||||
for name in free_globals {
|
|
||||||
let name = symbols.get_or_intern(name);
|
|
||||||
let value = Ir::Builtin(name);
|
|
||||||
global.insert(name, value);
|
|
||||||
}
|
|
||||||
for (name, value) in consts {
|
|
||||||
let name = symbols.get_or_intern(name);
|
|
||||||
global.insert(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = StoreConfig::from_env();
|
|
||||||
let store = DaemonStore::connect(&config.daemon_socket)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
symbols,
|
|
||||||
global,
|
|
||||||
sources: Vec::new(),
|
|
||||||
store,
|
|
||||||
spans: UnsafeCell::new(Vec::new()),
|
|
||||||
thunk_count: 0,
|
|
||||||
global_strings: Vec::new(),
|
|
||||||
global_string_map: HashMap::new(),
|
|
||||||
global_constants: Vec::new(),
|
|
||||||
global_constant_map: HashMap::new(),
|
|
||||||
synced_strings: 0,
|
|
||||||
synced_constants: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade_ctx<'ctx, 'id, 'ir>(
|
|
||||||
&'ctx mut self,
|
|
||||||
bump: &'ir Bump,
|
|
||||||
token: GhostToken<'id>,
|
|
||||||
extra_scope: Option<Scope<'ctx>>,
|
|
||||||
) -> DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
let source = self.get_current_source();
|
|
||||||
DowngradeCtx::new(
|
|
||||||
bump,
|
|
||||||
token,
|
|
||||||
&mut self.symbols,
|
|
||||||
&self.global,
|
|
||||||
extra_scope,
|
|
||||||
&mut self.thunk_count,
|
|
||||||
source,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_dir(&self) -> &Path {
|
|
||||||
self.sources
|
|
||||||
.last()
|
|
||||||
.as_ref()
|
|
||||||
.expect("current_source is not set")
|
|
||||||
.get_dir()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_source(&self) -> Source {
|
|
||||||
self.sources
|
|
||||||
.last()
|
|
||||||
.expect("current_source is not set")
|
|
||||||
.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn downgrade<'ctx>(
|
|
||||||
&'ctx mut self,
|
|
||||||
source: Source,
|
|
||||||
extra_scope: Option<Scope<'ctx>>,
|
|
||||||
) -> Result<OwnedIr> {
|
|
||||||
tracing::debug!("Parsing Nix expression");
|
|
||||||
|
|
||||||
self.sources.push(source.clone());
|
|
||||||
|
|
||||||
let root = rnix::Root::parse(&source.src);
|
|
||||||
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
|
||||||
|
|
||||||
tracing::debug!("Downgrading Nix expression");
|
|
||||||
let expr = root
|
|
||||||
.tree()
|
|
||||||
.expr()
|
|
||||||
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
|
||||||
let bump = Bump::new();
|
|
||||||
GhostToken::new(|token| {
|
|
||||||
let ir = self
|
|
||||||
.downgrade_ctx(&bump, token, extra_scope)
|
|
||||||
.downgrade_toplevel(expr)?;
|
|
||||||
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
|
||||||
Ok(OwnedIr { _bump: bump, ir })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compile_bytecode(&mut self, source: Source) -> Result<Bytecode> {
|
|
||||||
let root = self.downgrade(source, None)?;
|
|
||||||
tracing::debug!("Generating bytecode");
|
|
||||||
let bytecode = bytecode::compile_bytecode(root.as_ref(), self);
|
|
||||||
tracing::debug!("Compiled bytecode: {:#04X?}", bytecode.code);
|
|
||||||
Ok(bytecode)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_store_dir(&self) -> &str {
|
|
||||||
self.store.get_store_dir()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BytecodeContext for Context {
|
|
||||||
fn intern_string(&mut self, s: &str) -> u32 {
|
|
||||||
if let Some(&idx) = self.global_string_map.get(s) {
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
let idx = self.global_strings.len() as u32;
|
|
||||||
self.global_strings.push(s.to_string());
|
|
||||||
self.global_string_map.insert(s.to_string(), idx);
|
|
||||||
idx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn intern_constant(&mut self, c: Constant) -> u32 {
|
|
||||||
if let Some(&idx) = self.global_constant_map.get(&c) {
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
let idx = self.global_constants.len() as u32;
|
|
||||||
self.global_constants.push(c.clone());
|
|
||||||
self.global_constant_map.insert(c, idx);
|
|
||||||
idx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_span(&self, range: TextRange) -> u32 {
|
|
||||||
// FIXME: SAFETY
|
|
||||||
let spans = unsafe { &mut *self.spans.get() };
|
|
||||||
let id = spans.len();
|
|
||||||
let source_id = self
|
|
||||||
.sources
|
|
||||||
.len()
|
|
||||||
.checked_sub(1)
|
|
||||||
.expect("current_source not set");
|
|
||||||
spans.push((source_id, range));
|
|
||||||
id as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_sym(&self, id: SymId) -> &str {
|
|
||||||
self.symbols.resolve(id).expect("SymId out of bounds")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_dir(&self) -> &Path {
|
|
||||||
Context::get_current_dir(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DisassemblerContext for Context {
|
|
||||||
fn lookup_string(&self, id: u32) -> &str {
|
|
||||||
self.global_strings
|
|
||||||
.get(id as usize)
|
|
||||||
.expect("string not found")
|
|
||||||
}
|
|
||||||
fn lookup_constant(&self, id: u32) -> &Constant {
|
|
||||||
self.global_constants
|
|
||||||
.get(id as usize)
|
|
||||||
.expect("constant not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Scope<'ctx> {
|
|
||||||
Global(&'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>),
|
|
||||||
Repl(&'ctx HashSet<SymId>),
|
|
||||||
ScopedImport(HashSet<SymId>),
|
|
||||||
Let(HashMap<SymId, ThunkId>),
|
|
||||||
Param(SymId, ArgId),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
|
|
||||||
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ScopeGuard<'_, '_, '_, '_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.ctx.scopes.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
|
|
||||||
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
self.ctx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ThunkScope<'id, 'ir> {
|
|
||||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
|
||||||
cache: HashTable<(IrRef<'id, 'ir>, ThunkId)>,
|
|
||||||
hasher: DefaultHashBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
|
||||||
fn new_in(bump: &'ir Bump) -> Self {
|
|
||||||
Self {
|
|
||||||
bindings: bumpalo::collections::Vec::new_in(bump),
|
|
||||||
cache: HashTable::new(),
|
|
||||||
hasher: DefaultHashBuilder::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup_cache(&self, key: IrRef<'id, 'ir>, token: &GhostToken<'id>) -> Option<ThunkId> {
|
|
||||||
let hash = self.hasher.hash_one(IrKey(key, token));
|
|
||||||
self.cache
|
|
||||||
.find(hash, |&(ir, _)| ir_content_eq(key, ir, token))
|
|
||||||
.map(|&(_, id)| id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, token: &GhostToken<'id>) {
|
|
||||||
self.bindings.push((id, ir));
|
|
||||||
let hash = self.hasher.hash_one(IrKey(ir, token));
|
|
||||||
self.cache.insert_unique(hash, (ir, id), |&(ir, _)| {
|
|
||||||
self.hasher.hash_one(IrKey(ir, token))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
|
||||||
self.bindings.extend(iter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
bump: &'ir Bump,
|
|
||||||
token: GhostToken<'id>,
|
|
||||||
symbols: &'ctx mut DefaultStringInterner,
|
|
||||||
source: Source,
|
|
||||||
scopes: Vec<Scope<'ctx>>,
|
|
||||||
with_scope_count: usize,
|
|
||||||
arg_count: usize,
|
|
||||||
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> DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
fn new(
|
|
||||||
bump: &'ir Bump,
|
|
||||||
token: GhostToken<'id>,
|
|
||||||
symbols: &'ctx mut DefaultStringInterner,
|
|
||||||
global: &'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
|
|
||||||
extra_scope: Option<Scope<'ctx>>,
|
|
||||||
thunk_count: &'ctx mut usize,
|
|
||||||
source: Source,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
bump,
|
|
||||||
token,
|
|
||||||
symbols,
|
|
||||||
source,
|
|
||||||
scopes: std::iter::once(Scope::Global(global))
|
|
||||||
.chain(extra_scope)
|
|
||||||
.collect(),
|
|
||||||
thunk_count,
|
|
||||||
arg_count: 0,
|
|
||||||
with_scope_count: 0,
|
|
||||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
|
||||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_arg(&mut self) -> ArgId {
|
|
||||||
self.arg_count += 1;
|
|
||||||
ArgId(self.arg_count - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
|
|
||||||
if !should_thunk(ir, &self.token) {
|
|
||||||
return ir;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cached = self
|
|
||||||
.thunk_scopes
|
|
||||||
.last()
|
|
||||||
.expect("no active cache scope")
|
|
||||||
.lookup_cache(ir, &self.token);
|
|
||||||
|
|
||||||
if let Some(id) = cached {
|
|
||||||
return IrRef::alloc(self.bump, Ir::Thunk(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
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.token);
|
|
||||||
IrRef::alloc(self.bump, Ir::Thunk(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_sym(&mut self, sym: String) -> SymId {
|
|
||||||
self.symbols.get_or_intern(sym)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_sym(&self, id: SymId) -> Symbol<'_> {
|
|
||||||
self.symbols.resolve(id).expect("no symbol found").into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'id, 'ir>> {
|
|
||||||
for scope in self.scopes.iter().rev() {
|
|
||||||
match scope {
|
|
||||||
&Scope::Global(global_scope) => {
|
|
||||||
if let Some(expr) = global_scope.get(&sym) {
|
|
||||||
let ir = match expr {
|
|
||||||
Ir::Builtins => Ir::Builtins,
|
|
||||||
Ir::Builtin(s) => Ir::Builtin(*s),
|
|
||||||
Ir::Bool(b) => Ir::Bool(*b),
|
|
||||||
Ir::Null => Ir::Null,
|
|
||||||
_ => unreachable!("globals should only contain leaf IR nodes"),
|
|
||||||
};
|
|
||||||
return Ok(self.new_expr(ir));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&Scope::Repl(repl_bindings) => {
|
|
||||||
if repl_bindings.contains(&sym) {
|
|
||||||
return Ok(self.new_expr(Ir::ReplBinding(sym)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Scope::ScopedImport(scoped_bindings) => {
|
|
||||||
if scoped_bindings.contains(&sym) {
|
|
||||||
return Ok(self.new_expr(Ir::ScopedImportBinding(sym)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Scope::Let(let_scope) => {
|
|
||||||
if let Some(&expr) = let_scope.get(&sym) {
|
|
||||||
return Ok(self.new_expr(Ir::Thunk(expr)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&Scope::Param(param_sym, id) => {
|
|
||||||
if param_sym == sym {
|
|
||||||
return Ok(self.new_expr(Ir::Arg(id)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.with_scope_count > 0 {
|
|
||||||
Ok(self.new_expr(Ir::WithLookup(sym)))
|
|
||||||
} else {
|
|
||||||
Err(Error::downgrade_error(
|
|
||||||
format!("'{}' not found", self.get_sym(sym)),
|
|
||||||
self.get_current_source(),
|
|
||||||
span,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_source(&self) -> Source {
|
|
||||||
self.source.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_let_scope<F, R>(&mut self, keys: &[SymId], f: F) -> Result<R>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
|
|
||||||
{
|
|
||||||
let base = *self.thunk_count;
|
|
||||||
*self.thunk_count = self
|
|
||||||
.thunk_count
|
|
||||||
.checked_add(keys.len())
|
|
||||||
.expect("thunk id overflow");
|
|
||||||
let iter = keys.iter().enumerate().map(|(offset, &key)| {
|
|
||||||
(
|
|
||||||
key,
|
|
||||||
ThunkId(unsafe { base.checked_add(offset).unwrap_unchecked() }),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
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");
|
|
||||||
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ArgId, f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> R,
|
|
||||||
{
|
|
||||||
self.scopes.push(Scope::Param(param, arg));
|
|
||||||
let mut guard = ScopeGuard { ctx: self };
|
|
||||||
f(guard.as_ctx())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> R,
|
|
||||||
{
|
|
||||||
self.with_scope_count += 1;
|
|
||||||
let ret = f(self);
|
|
||||||
self.with_scope_count -= 1;
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_thunk_scope<F, R>(
|
|
||||||
&mut self,
|
|
||||||
f: F,
|
|
||||||
) -> (
|
|
||||||
R,
|
|
||||||
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
|
||||||
)
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Self) -> R,
|
|
||||||
{
|
|
||||||
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
|
||||||
let ret = f(self);
|
|
||||||
(
|
|
||||||
ret,
|
|
||||||
self.thunk_scopes
|
|
||||||
.pop()
|
|
||||||
.expect("no thunk scope left???")
|
|
||||||
.bindings,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bump(&self) -> &'ir bumpalo::Bump {
|
|
||||||
self.bump
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
|
||||||
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
|
||||||
let body = root.downgrade(&mut self)?;
|
|
||||||
let thunks = self
|
|
||||||
.thunk_scopes
|
|
||||||
.pop()
|
|
||||||
.expect("no thunk scope left???")
|
|
||||||
.bindings;
|
|
||||||
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
|
||||||
Ok(ir.freeze(self.token))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,11 +3,10 @@ use std::fmt::Write;
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use crate::bytecode::{Bytecode, Constant, Op};
|
use crate::codegen::{Bytecode, Op};
|
||||||
|
|
||||||
pub(crate) trait DisassemblerContext {
|
pub(crate) trait DisassemblerContext {
|
||||||
fn lookup_string(&self, id: u32) -> &str;
|
fn lookup_string(&self, id: u32) -> &str;
|
||||||
fn lookup_constant(&self, id: u32) -> &Constant;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Disassembler<'a, Ctx> {
|
pub(crate) struct Disassembler<'a, Ctx> {
|
||||||
@@ -55,6 +54,22 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
i32::from_le_bytes(bytes)
|
i32::from_le_bytes(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_i64(&mut self) -> i64 {
|
||||||
|
let bytes = self.code[self.pos..self.pos + 8]
|
||||||
|
.try_into()
|
||||||
|
.expect("no enough bytes");
|
||||||
|
self.pos += 8;
|
||||||
|
i64::from_le_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_f64(&mut self) -> f64 {
|
||||||
|
let bytes = self.code[self.pos..self.pos + 8]
|
||||||
|
.try_into()
|
||||||
|
.expect("no enough bytes");
|
||||||
|
self.pos += 8;
|
||||||
|
f64::from_le_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn disassemble(&mut self) -> String {
|
pub fn disassemble(&mut self) -> String {
|
||||||
self.disassemble_impl(false)
|
self.disassemble_impl(false)
|
||||||
}
|
}
|
||||||
@@ -144,14 +159,17 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
let op = Op::try_from_primitive(op_byte).expect("invalid op code");
|
let op = Op::try_from_primitive(op_byte).expect("invalid op code");
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
Op::PushConst => {
|
Op::PushSmi => {
|
||||||
let idx = self.read_u32();
|
let val = self.read_i32();
|
||||||
let val = self.ctx.lookup_constant(idx);
|
("PushSmi", format!("{}", val))
|
||||||
let val_str = match val {
|
}
|
||||||
Constant::Int(i) => format!("Int({})", i),
|
Op::PushBigInt => {
|
||||||
Constant::Float(f) => format!("Float(bits: {})", f),
|
let val = self.read_i64();
|
||||||
};
|
("PushBigInt", format!("{}", val))
|
||||||
("PushConst", format!("@{} ({})", idx, val_str))
|
}
|
||||||
|
Op::PushFloat => {
|
||||||
|
let val = self.read_f64();
|
||||||
|
("PushFloat", format!("{}", val))
|
||||||
}
|
}
|
||||||
Op::PushString => {
|
Op::PushString => {
|
||||||
let idx = self.read_u32();
|
let idx = self.read_u32();
|
||||||
|
|||||||
@@ -50,16 +50,16 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
|
|||||||
fn new_arg(&mut self) -> ArgId;
|
fn new_arg(&mut self) -> ArgId;
|
||||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>;
|
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>;
|
||||||
|
|
||||||
fn new_sym(&mut self, sym: String) -> SymId;
|
fn new_sym(&mut self, sym: String) -> StringId;
|
||||||
fn get_sym(&self, id: SymId) -> Symbol<'_>;
|
fn get_sym(&self, id: StringId) -> Symbol<'_>;
|
||||||
fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'id, 'ir>>;
|
fn lookup(&self, sym: StringId, span: TextRange) -> Result<IrRef<'id, 'ir>>;
|
||||||
|
|
||||||
fn get_current_source(&self) -> Source;
|
fn get_current_source(&self) -> Source;
|
||||||
|
|
||||||
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ArgId, f: F) -> R
|
fn with_param_scope<F, R>(&mut self, param: StringId, arg: ArgId, f: F) -> R
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> R;
|
F: FnOnce(&mut Self) -> R;
|
||||||
fn with_let_scope<F, R>(&mut self, bindings: &[SymId], f: F) -> Result<R>
|
fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, R)>;
|
F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, R)>;
|
||||||
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||||
@@ -456,8 +456,8 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
|||||||
|
|
||||||
enum PendingValue<'ir> {
|
enum PendingValue<'ir> {
|
||||||
Expr(ast::Expr),
|
Expr(ast::Expr),
|
||||||
InheritFrom(ast::Expr, SymId, TextRange),
|
InheritFrom(ast::Expr, StringId, TextRange),
|
||||||
InheritScope(SymId, TextRange),
|
InheritScope(StringId, TextRange),
|
||||||
Set(PendingAttrSet<'ir>),
|
Set(PendingAttrSet<'ir>),
|
||||||
ExtendedRecAttrSet {
|
ExtendedRecAttrSet {
|
||||||
base: ast::AttrSet,
|
base: ast::AttrSet,
|
||||||
@@ -466,7 +466,7 @@ enum PendingValue<'ir> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct PendingAttrSet<'ir> {
|
struct PendingAttrSet<'ir> {
|
||||||
stcs: HashMap<'ir, SymId, (PendingValue<'ir>, TextRange)>,
|
stcs: HashMap<'ir, StringId, (PendingValue<'ir>, TextRange)>,
|
||||||
dyns: Vec<'ir, (ast::Attr, PendingValue<'ir>, TextRange)>,
|
dyns: Vec<'ir, (ast::Attr, PendingValue<'ir>, TextRange)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +510,7 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
|
|||||||
|
|
||||||
fn insert_static(
|
fn insert_static(
|
||||||
&mut self,
|
&mut self,
|
||||||
sym: SymId,
|
sym: StringId,
|
||||||
span: TextRange,
|
span: TextRange,
|
||||||
path: &[ast::Attr],
|
path: &[ast::Attr],
|
||||||
value: ast::Expr,
|
value: ast::Expr,
|
||||||
@@ -846,7 +846,7 @@ fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct FinalizedAttrSet<'id, 'ir> {
|
struct FinalizedAttrSet<'id, 'ir> {
|
||||||
stcs: HashMap<'ir, SymId, (IrRef<'id, 'ir>, TextRange)>,
|
stcs: HashMap<'ir, StringId, (IrRef<'id, 'ir>, TextRange)>,
|
||||||
dyns: Vec<'ir, (IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)>,
|
dyns: Vec<'ir, (IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,23 +930,23 @@ fn downgrade_attrpath<'id, 'ir>(
|
|||||||
|
|
||||||
struct PatternBindings<'id, 'ir> {
|
struct PatternBindings<'id, 'ir> {
|
||||||
body: IrRef<'id, 'ir>,
|
body: IrRef<'id, 'ir>,
|
||||||
required: Vec<'ir, (SymId, TextRange)>,
|
required: Vec<'ir, (StringId, TextRange)>,
|
||||||
optional: Vec<'ir, (SymId, TextRange)>,
|
optional: Vec<'ir, (StringId, TextRange)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
|
fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
|
||||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||||
alias: Option<SymId>,
|
alias: Option<StringId>,
|
||||||
arg: ArgId,
|
arg: ArgId,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
body_fn: impl FnOnce(&mut Ctx, &[SymId]) -> Result<IrRef<'id, 'ir>>,
|
body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
|
||||||
) -> Result<PatternBindings<'id, 'ir>>
|
) -> Result<PatternBindings<'id, 'ir>>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext<'id, 'ir>,
|
Ctx: DowngradeContext<'id, 'ir>,
|
||||||
{
|
{
|
||||||
let arg = ctx.new_expr(Ir::Arg(arg));
|
let arg = ctx.new_expr(Ir::Arg(arg));
|
||||||
struct Param {
|
struct Param {
|
||||||
sym: SymId,
|
sym: StringId,
|
||||||
sym_span: TextRange,
|
sym_span: TextRange,
|
||||||
default: Option<ast::Expr>,
|
default: Option<ast::Expr>,
|
||||||
span: TextRange,
|
span: TextRange,
|
||||||
@@ -1045,7 +1045,7 @@ fn downgrade_let_bindings<'id, 'ir, Ctx, F>(
|
|||||||
) -> Result<IrRef<'id, 'ir>>
|
) -> Result<IrRef<'id, 'ir>>
|
||||||
where
|
where
|
||||||
Ctx: DowngradeContext<'id, 'ir>,
|
Ctx: DowngradeContext<'id, 'ir>,
|
||||||
F: FnOnce(&mut Ctx, &[SymId]) -> Result<IrRef<'id, 'ir>>,
|
F: FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
|
||||||
{
|
{
|
||||||
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| {
|
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| {
|
||||||
body_fn(ctx, binding_keys)
|
body_fn(ctx, binding_keys)
|
||||||
@@ -1081,7 +1081,7 @@ where
|
|||||||
Ctx: DowngradeContext<'id, 'ir>,
|
Ctx: DowngradeContext<'id, 'ir>,
|
||||||
F: FnOnce(
|
F: FnOnce(
|
||||||
&mut Ctx,
|
&mut Ctx,
|
||||||
&[SymId],
|
&[StringId],
|
||||||
&[(IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)],
|
&[(IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)],
|
||||||
) -> Result<IrRef<'id, 'ir>>,
|
) -> Result<IrRef<'id, 'ir>>,
|
||||||
{
|
{
|
||||||
@@ -1106,7 +1106,7 @@ where
|
|||||||
fn collect_inherit_lookups<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
fn collect_inherit_lookups<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
||||||
entries: &[ast::Entry],
|
entries: &[ast::Entry],
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
) -> Result<HashMap<'ir, SymId, (IrRef<'id, 'ir>, TextRange)>> {
|
) -> Result<HashMap<'ir, StringId, (IrRef<'id, 'ir>, TextRange)>> {
|
||||||
let mut inherit_lookups = HashMap::new_in(ctx.bump());
|
let mut inherit_lookups = HashMap::new_in(ctx.bump());
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
if let ast::Entry::Inherit(inherit) = entry
|
if let ast::Entry::Inherit(inherit) = entry
|
||||||
@@ -1128,7 +1128,7 @@ fn collect_inherit_lookups<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
|||||||
fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
||||||
pending: &PendingAttrSet,
|
pending: &PendingAttrSet,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
) -> Result<HashSet<SymId>> {
|
) -> Result<HashSet<StringId>> {
|
||||||
let mut binding_syms = HashSet::new();
|
let mut binding_syms = HashSet::new();
|
||||||
|
|
||||||
for (sym, (_, span)) in &pending.stcs {
|
for (sym, (_, span)) in &pending.stcs {
|
||||||
@@ -1146,7 +1146,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>(
|
fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
||||||
pending: PendingAttrSet,
|
pending: PendingAttrSet,
|
||||||
inherit_lookups: &HashMap<SymId, (IrRef<'id, 'ir>, TextRange)>,
|
inherit_lookups: &HashMap<StringId, (IrRef<'id, 'ir>, TextRange)>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
||||||
let mut stcs = HashMap::new_in(ctx.bump());
|
let mut stcs = HashMap::new_in(ctx.bump());
|
||||||
@@ -1176,7 +1176,7 @@ 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>(
|
fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
|
||||||
value: PendingValue,
|
value: PendingValue,
|
||||||
inherit_lookups: &HashMap<SymId, (IrRef<'id, 'ir>, TextRange)>,
|
inherit_lookups: &HashMap<StringId, (IrRef<'id, 'ir>, TextRange)>,
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
) -> Result<IrRef<'id, 'ir>> {
|
) -> Result<IrRef<'id, 'ir>> {
|
||||||
match value {
|
match value {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use bumpalo::{Bump, boxed::Box, collections::Vec};
|
use bumpalo::{Bump, boxed::Box, collections::Vec};
|
||||||
|
use gc_arena::Collect;
|
||||||
use ghost_cell::{GhostCell, GhostToken};
|
use ghost_cell::{GhostCell, GhostToken};
|
||||||
use rnix::{TextRange, ast};
|
use rnix::{TextRange, ast};
|
||||||
use string_interner::symbol::SymbolU32;
|
use string_interner::symbol::SymbolU32;
|
||||||
@@ -23,6 +24,10 @@ impl<'id, 'ir> IrRef<'id, 'ir> {
|
|||||||
Self(bump.alloc(GhostCell::new(ir)))
|
Self(bump.alloc(GhostCell::new(ir)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a Ir<'ir, Self> {
|
||||||
|
self.0.borrow(token)
|
||||||
|
}
|
||||||
|
|
||||||
/// Freeze a mutable IR reference into a read-only one, consuming the
|
/// Freeze a mutable IR reference into a read-only one, consuming the
|
||||||
/// `GhostToken` to prevent any further mutation.
|
/// `GhostToken` to prevent any further mutation.
|
||||||
///
|
///
|
||||||
@@ -42,13 +47,6 @@ impl<'id, 'ir> IrRef<'id, 'ir> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'id, 'ir> Deref for IrRef<'id, 'ir> {
|
|
||||||
type Target = GhostCell<'id, Ir<'ir, IrRef<'id, 'ir>>>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>);
|
pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>);
|
||||||
@@ -68,7 +66,7 @@ pub enum Ir<'ir, Ref> {
|
|||||||
Null,
|
Null,
|
||||||
Str(Box<'ir, String>),
|
Str(Box<'ir, String>),
|
||||||
AttrSet {
|
AttrSet {
|
||||||
stcs: HashMap<'ir, SymId, (Ref, TextRange)>,
|
stcs: HashMap<'ir, StringId, (Ref, TextRange)>,
|
||||||
dyns: Vec<'ir, (Ref, Ref, TextRange)>,
|
dyns: Vec<'ir, (Ref, Ref, TextRange)>,
|
||||||
},
|
},
|
||||||
List {
|
List {
|
||||||
@@ -119,7 +117,7 @@ pub enum Ir<'ir, Ref> {
|
|||||||
body: Ref,
|
body: Ref,
|
||||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||||
},
|
},
|
||||||
WithLookup(SymId),
|
WithLookup(StringId),
|
||||||
|
|
||||||
// Function related
|
// Function related
|
||||||
Func {
|
Func {
|
||||||
@@ -137,7 +135,7 @@ pub enum Ir<'ir, Ref> {
|
|||||||
|
|
||||||
// Builtins
|
// Builtins
|
||||||
Builtins,
|
Builtins,
|
||||||
Builtin(SymId),
|
Builtin(StringId),
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
TopLevel {
|
TopLevel {
|
||||||
@@ -146,19 +144,26 @@ pub enum Ir<'ir, Ref> {
|
|||||||
},
|
},
|
||||||
Thunk(ThunkId),
|
Thunk(ThunkId),
|
||||||
CurPos(TextRange),
|
CurPos(TextRange),
|
||||||
ReplBinding(SymId),
|
ReplBinding(StringId),
|
||||||
ScopedImportBinding(SymId),
|
ScopedImportBinding(StringId),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ThunkId(pub usize);
|
pub struct ThunkId(pub usize);
|
||||||
|
|
||||||
pub type SymId = SymbolU32;
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)]
|
||||||
|
#[collect(require_static)]
|
||||||
|
pub struct StringId(pub SymbolU32);
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ArgId(pub usize);
|
pub struct ArgId(pub u32);
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct SpanId(pub u32);
|
||||||
|
|
||||||
/// Represents a key in an attribute path.
|
/// Represents a key in an attribute path.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
@@ -169,7 +174,7 @@ pub enum Attr<Ref> {
|
|||||||
Dynamic(Ref, TextRange),
|
Dynamic(Ref, TextRange),
|
||||||
/// A static attribute key.
|
/// A static attribute key.
|
||||||
/// Example: `attrs.key`
|
/// Example: `attrs.key`
|
||||||
Str(SymId, TextRange),
|
Str(StringId, TextRange),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The kinds of binary operations supported in Nix.
|
/// The kinds of binary operations supported in Nix.
|
||||||
@@ -248,8 +253,8 @@ impl From<ast::UnaryOpKind> for UnOpKind {
|
|||||||
/// Describes the parameters of a function.
|
/// Describes the parameters of a function.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Param<'ir> {
|
pub struct Param<'ir> {
|
||||||
pub required: Vec<'ir, (SymId, TextRange)>,
|
pub required: Vec<'ir, (StringId, TextRange)>,
|
||||||
pub optional: Vec<'ir, (SymId, TextRange)>,
|
pub optional: Vec<'ir, (StringId, TextRange)>,
|
||||||
pub ellipsis: bool,
|
pub ellipsis: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#![warn(clippy::unwrap_used)]
|
#![warn(clippy::unwrap_used)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
pub mod context;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
|
pub mod runtime;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
|
||||||
mod bytecode;
|
mod codegen;
|
||||||
mod derivation;
|
mod derivation;
|
||||||
mod disassembler;
|
mod disassembler;
|
||||||
mod downgrade;
|
mod downgrade;
|
||||||
@@ -13,7 +14,6 @@ mod downgrade;
|
|||||||
mod ir;
|
mod ir;
|
||||||
mod nar;
|
mod nar;
|
||||||
mod nix_utils;
|
mod nix_utils;
|
||||||
mod runtime;
|
|
||||||
mod store;
|
mod store;
|
||||||
mod string_context;
|
mod string_context;
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use std::process::exit;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use hashbrown::HashSet;
|
|
||||||
use fix::context::Context;
|
|
||||||
use fix::error::Source;
|
use fix::error::Source;
|
||||||
|
use fix::runtime::Runtime;
|
||||||
|
use hashbrown::HashSet;
|
||||||
use rustyline::DefaultEditor;
|
use rustyline::DefaultEditor;
|
||||||
use rustyline::error::ReadlineError;
|
use rustyline::error::ReadlineError;
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ struct ExprSource {
|
|||||||
file: Option<PathBuf>,
|
file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<()> {
|
fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> {
|
||||||
let src = if let Some(expr) = src.expr {
|
let src = if let Some(expr) = src.expr {
|
||||||
Source::new_eval(expr)?
|
Source::new_eval(expr)?
|
||||||
} else if let Some(file) = src.file {
|
} else if let Some(file) = src.file {
|
||||||
@@ -48,10 +48,11 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
|
|||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
match context.compile_bytecode(src) {
|
todo!()
|
||||||
|
/* match runtime.compile_bytecode(src) {
|
||||||
Ok(compiled) => {
|
Ok(compiled) => {
|
||||||
if !silent {
|
if !silent {
|
||||||
println!("{}", context.disassemble_colored(&compiled));
|
println!("{}", runtime.disassemble_colored(&compiled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -59,10 +60,10 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(()) */
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
|
fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
|
||||||
let src = if let Some(expr) = src.expr {
|
let src = if let Some(expr) = src.expr {
|
||||||
Source::new_eval(expr)?
|
Source::new_eval(expr)?
|
||||||
} else if let Some(file) = src.file {
|
} else if let Some(file) = src.file {
|
||||||
@@ -70,7 +71,7 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
match context.eval_deep(src) {
|
match runtime.eval_deep(src) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
println!("{}", value.display_compat());
|
println!("{}", value.display_compat());
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_repl(context: &mut Context) -> Result<()> {
|
fn run_repl(runtime: &mut Runtime) -> Result<()> {
|
||||||
let mut rl = DefaultEditor::new()?;
|
let mut rl = DefaultEditor::new()?;
|
||||||
let mut scope = HashSet::new();
|
let mut scope = HashSet::new();
|
||||||
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
||||||
@@ -101,20 +102,20 @@ fn run_repl(context: &mut Context) -> Result<()> {
|
|||||||
eprintln!("Error: missing expression after '='");
|
eprintln!("Error: missing expression after '='");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match context.add_binding(ident, expr, &mut scope) {
|
match runtime.add_binding(ident, expr, &mut scope) {
|
||||||
Ok(value) => println!("{} = {}", ident, value),
|
Ok(value) => println!("{} = {}", ident, value),
|
||||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let src = Source::new_repl(line)?;
|
let src = Source::new_repl(line)?;
|
||||||
match context.eval_repl(src, &scope) {
|
match runtime.eval_repl(src, &scope) {
|
||||||
Ok(value) => println!("{value}"),
|
Ok(value) => println!("{value}"),
|
||||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let src = Source::new_repl(line)?;
|
let src = Source::new_repl(line)?;
|
||||||
match context.eval_shallow(src) {
|
match runtime.eval_repl(src, &scope) {
|
||||||
Ok(value) => println!("{value}"),
|
Ok(value) => println!("{value}"),
|
||||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||||
}
|
}
|
||||||
@@ -141,11 +142,11 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let mut context = Context::new()?;
|
let mut runtime = Runtime::new()?;
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Compile { source, silent } => run_compile(&mut context, source, silent),
|
Command::Compile { source, silent } => run_compile(&mut runtime, source, silent),
|
||||||
Command::Eval { source } => run_eval(&mut context, source),
|
Command::Eval { source } => run_eval(&mut runtime, source),
|
||||||
Command::Repl => run_repl(&mut context),
|
Command::Repl => run_repl(&mut runtime),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,531 @@
|
|||||||
|
use std::hash::BuildHasher;
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use gc_arena::{Arena, Rootable, arena::CollectionPhase};
|
||||||
|
use ghost_cell::{GhostCell, GhostToken};
|
||||||
|
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
|
||||||
|
use rnix::TextRange;
|
||||||
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
|
use crate::codegen::{BytecodeContext, InstructionPtr};
|
||||||
|
use crate::downgrade::{Downgrade as _, DowngradeContext};
|
||||||
|
use crate::error::{Error, Result, Source};
|
||||||
|
use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, StringId, ThunkId, ir_content_eq};
|
||||||
|
use crate::runtime::builtins::new_builtins_env;
|
||||||
|
use crate::store::{DaemonStore, StoreConfig};
|
||||||
|
use crate::value::Symbol;
|
||||||
|
|
||||||
|
mod builtins;
|
||||||
|
mod stack;
|
||||||
mod value;
|
mod value;
|
||||||
|
mod vm;
|
||||||
|
use vm::{Action, VM};
|
||||||
|
|
||||||
|
pub struct Runtime {
|
||||||
|
bytecode: Vec<u8>,
|
||||||
|
global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||||
|
sources: Vec<Source>,
|
||||||
|
store: DaemonStore,
|
||||||
|
spans: Vec<(usize, TextRange)>,
|
||||||
|
thunk_count: usize,
|
||||||
|
strings: DefaultStringInterner,
|
||||||
|
arena: Arena<Rootable![VM<'_>]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runtime {
|
||||||
|
const COLLECTOR_GRANULARITY: f64 = 1024.0;
|
||||||
|
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let mut strings = DefaultStringInterner::new();
|
||||||
|
let global_env = new_builtins_env(&mut strings);
|
||||||
|
|
||||||
|
let config = StoreConfig::from_env();
|
||||||
|
let store = DaemonStore::connect(&config.daemon_socket)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
global_env,
|
||||||
|
store,
|
||||||
|
strings,
|
||||||
|
thunk_count: 0,
|
||||||
|
bytecode: Vec::new(),
|
||||||
|
sources: Vec::new(),
|
||||||
|
spans: Vec::new(),
|
||||||
|
arena: Arena::new(|mc| VM::new(mc)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(&mut self, source: Source) -> Result<crate::value::Value> {
|
||||||
|
let root = self.downgrade(source, None)?;
|
||||||
|
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
|
||||||
|
self.run(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_shallow(&mut self, _source: Source) -> Result<crate::value::Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_deep(&mut self, source: Source) -> Result<crate::value::Value> {
|
||||||
|
// FIXME: deep
|
||||||
|
let root = self.downgrade(source, None)?;
|
||||||
|
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
|
||||||
|
self.run(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_repl(
|
||||||
|
&mut self,
|
||||||
|
source: Source,
|
||||||
|
scope: &HashSet<StringId>,
|
||||||
|
) -> Result<crate::value::Value> {
|
||||||
|
// FIXME: shallow
|
||||||
|
let root = self.downgrade(source, Some(Scope::Repl(scope)))?;
|
||||||
|
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
|
||||||
|
self.run(ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_binding(
|
||||||
|
&mut self,
|
||||||
|
_ident: &str,
|
||||||
|
_expr: &str,
|
||||||
|
_scope: &mut HashSet<StringId>,
|
||||||
|
) -> Result<crate::value::Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downgrade_ctx<'a, 'bump, 'id>(
|
||||||
|
&'a mut self,
|
||||||
|
bump: &'bump Bump,
|
||||||
|
token: GhostToken<'id>,
|
||||||
|
extra_scope: Option<Scope<'a>>,
|
||||||
|
) -> DowngradeCtx<'a, 'id, 'bump> {
|
||||||
|
let Runtime {
|
||||||
|
global_env,
|
||||||
|
sources,
|
||||||
|
thunk_count,
|
||||||
|
strings,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
DowngradeCtx {
|
||||||
|
bump,
|
||||||
|
token,
|
||||||
|
strings,
|
||||||
|
source: sources.last().unwrap().clone(),
|
||||||
|
scopes: [Scope::Global(global_env)].into_iter().chain(extra_scope.into_iter()).collect(),
|
||||||
|
with_scope_count: 0,
|
||||||
|
arg_count: 0,
|
||||||
|
thunk_count,
|
||||||
|
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downgrade<'a>(&'a mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<OwnedIr> {
|
||||||
|
tracing::debug!("Parsing Nix expression");
|
||||||
|
|
||||||
|
self.sources.push(source.clone());
|
||||||
|
|
||||||
|
let root = rnix::Root::parse(&source.src);
|
||||||
|
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
||||||
|
|
||||||
|
tracing::debug!("Downgrading Nix expression");
|
||||||
|
let expr = root
|
||||||
|
.tree()
|
||||||
|
.expr()
|
||||||
|
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
||||||
|
let bump = Bump::new();
|
||||||
|
GhostToken::new(|token| {
|
||||||
|
let ir = self
|
||||||
|
.downgrade_ctx(&bump, token, extra_scope)
|
||||||
|
.downgrade_toplevel(expr)?;
|
||||||
|
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
||||||
|
Ok(OwnedIr { _bump: bump, ir })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self, ip: InstructionPtr) -> Result<crate::value::Value> {
|
||||||
|
let mut pc = ip.0;
|
||||||
|
loop {
|
||||||
|
let Runtime {
|
||||||
|
bytecode,
|
||||||
|
strings,
|
||||||
|
arena,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
let action =
|
||||||
|
arena.mutate_root(|mc, root| root.run_batch(bytecode, &mut pc, mc, strings));
|
||||||
|
match action {
|
||||||
|
Action::NeedGc => {
|
||||||
|
if self.arena.collection_phase() == CollectionPhase::Sweeping {
|
||||||
|
self.arena.collect_debt();
|
||||||
|
} else if let Some(marked) = self.arena.mark_debt() {
|
||||||
|
marked.start_sweeping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action::Done(done) => {
|
||||||
|
break done;
|
||||||
|
}
|
||||||
|
Action::Continue => (),
|
||||||
|
Action::IoRequest(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
||||||
|
use rnix::ParseError::*;
|
||||||
|
match error {
|
||||||
|
Unexpected(range)
|
||||||
|
| UnexpectedExtra(range)
|
||||||
|
| UnexpectedWanted(_, range, _)
|
||||||
|
| UnexpectedDoubleBind(range)
|
||||||
|
| DuplicatedArgs(range, _) => Some(*range),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_parse_error<'a>(
|
||||||
|
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
||||||
|
source: Source,
|
||||||
|
) -> Option<Box<Error>> {
|
||||||
|
for err in errors {
|
||||||
|
if let Some(span) = parse_error_span(err) {
|
||||||
|
return Some(
|
||||||
|
Error::parse_error(err.to_string())
|
||||||
|
.with_source(source)
|
||||||
|
.with_span(span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
bump: &'ir Bump,
|
||||||
|
token: GhostToken<'id>,
|
||||||
|
strings: &'ctx mut DefaultStringInterner,
|
||||||
|
source: Source,
|
||||||
|
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> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
fn new(
|
||||||
|
bump: &'ir Bump,
|
||||||
|
token: GhostToken<'id>,
|
||||||
|
symbols: &'ctx mut DefaultStringInterner,
|
||||||
|
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||||
|
extra_scope: Option<Scope<'ctx>>,
|
||||||
|
thunk_count: &'ctx mut usize,
|
||||||
|
source: Source,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
bump,
|
||||||
|
token,
|
||||||
|
strings: symbols,
|
||||||
|
source,
|
||||||
|
scopes: std::iter::once(Scope::Global(global))
|
||||||
|
.chain(extra_scope)
|
||||||
|
.collect(),
|
||||||
|
thunk_count,
|
||||||
|
arg_count: 0,
|
||||||
|
with_scope_count: 0,
|
||||||
|
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||||
|
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_arg(&mut self) -> ArgId {
|
||||||
|
self.arg_count += 1;
|
||||||
|
ArgId(self.arg_count - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
|
||||||
|
if !should_thunk(ir, &self.token) {
|
||||||
|
return ir;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cached = self
|
||||||
|
.thunk_scopes
|
||||||
|
.last()
|
||||||
|
.expect("no active cache scope")
|
||||||
|
.lookup_cache(ir, &self.token);
|
||||||
|
|
||||||
|
if let Some(id) = cached {
|
||||||
|
return IrRef::alloc(self.bump, Ir::Thunk(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
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.token);
|
||||||
|
IrRef::alloc(self.bump, Ir::Thunk(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_sym(&mut self, sym: String) -> StringId {
|
||||||
|
StringId(self.strings.get_or_intern(sym))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sym(&self, id: StringId) -> Symbol<'_> {
|
||||||
|
self.strings.resolve(id.0).expect("no symbol found").into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup(&self, sym: StringId, span: TextRange) -> Result<IrRef<'id, 'ir>> {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
match scope {
|
||||||
|
&Scope::Global(global_scope) => {
|
||||||
|
if let Some(expr) = global_scope.get(&sym) {
|
||||||
|
let ir = match expr {
|
||||||
|
Ir::Builtins => Ir::Builtins,
|
||||||
|
Ir::Builtin(s) => Ir::Builtin(*s),
|
||||||
|
Ir::Bool(b) => Ir::Bool(*b),
|
||||||
|
Ir::Null => Ir::Null,
|
||||||
|
_ => unreachable!("globals should only contain leaf IR nodes"),
|
||||||
|
};
|
||||||
|
return Ok(self.new_expr(ir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Scope::Repl(repl_bindings) => {
|
||||||
|
if repl_bindings.contains(&sym) {
|
||||||
|
return Ok(self.new_expr(Ir::ReplBinding(sym)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scope::ScopedImport(scoped_bindings) => {
|
||||||
|
if scoped_bindings.contains(&sym) {
|
||||||
|
return Ok(self.new_expr(Ir::ScopedImportBinding(sym)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scope::Let(let_scope) => {
|
||||||
|
if let Some(&expr) = let_scope.get(&sym) {
|
||||||
|
return Ok(self.new_expr(Ir::Thunk(expr)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Scope::Param(param_sym, id) => {
|
||||||
|
if param_sym == sym {
|
||||||
|
return Ok(self.new_expr(Ir::Arg(id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.with_scope_count > 0 {
|
||||||
|
Ok(self.new_expr(Ir::WithLookup(sym)))
|
||||||
|
} else {
|
||||||
|
Err(Error::downgrade_error(
|
||||||
|
format!("'{}' not found", self.get_sym(sym)),
|
||||||
|
self.get_current_source(),
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_source(&self) -> Source {
|
||||||
|
self.source.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_let_scope<F, R>(&mut self, keys: &[StringId], f: F) -> Result<R>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
|
||||||
|
{
|
||||||
|
let base = *self.thunk_count;
|
||||||
|
*self.thunk_count = self
|
||||||
|
.thunk_count
|
||||||
|
.checked_add(keys.len())
|
||||||
|
.expect("thunk id overflow");
|
||||||
|
let iter = keys.iter().enumerate().map(|(offset, &key)| {
|
||||||
|
(
|
||||||
|
key,
|
||||||
|
ThunkId(unsafe { base.checked_add(offset).unwrap_unchecked() }),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
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");
|
||||||
|
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_param_scope<F, R>(&mut self, param: StringId, arg: ArgId, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> R,
|
||||||
|
{
|
||||||
|
self.scopes.push(Scope::Param(param, arg));
|
||||||
|
let mut guard = ScopeGuard { ctx: self };
|
||||||
|
f(guard.as_ctx())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> R,
|
||||||
|
{
|
||||||
|
self.with_scope_count += 1;
|
||||||
|
let ret = f(self);
|
||||||
|
self.with_scope_count -= 1;
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_thunk_scope<F, R>(
|
||||||
|
&mut self,
|
||||||
|
f: F,
|
||||||
|
) -> (
|
||||||
|
R,
|
||||||
|
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Self) -> R,
|
||||||
|
{
|
||||||
|
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
||||||
|
let ret = f(self);
|
||||||
|
(
|
||||||
|
ret,
|
||||||
|
self.thunk_scopes
|
||||||
|
.pop()
|
||||||
|
.expect("no thunk scope left???")
|
||||||
|
.bindings,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bump(&self) -> &'ir bumpalo::Bump {
|
||||||
|
self.bump
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
||||||
|
let body = root.downgrade(&mut self)?;
|
||||||
|
let thunks = self
|
||||||
|
.thunk_scopes
|
||||||
|
.pop()
|
||||||
|
.expect("no thunk scope left???")
|
||||||
|
.bindings;
|
||||||
|
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, IrRef<'id, 'ir>)>,
|
||||||
|
cache: HashTable<(IrRef<'id, 'ir>, ThunkId)>,
|
||||||
|
hasher: DefaultHashBuilder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||||
|
fn new_in(bump: &'ir Bump) -> Self {
|
||||||
|
Self {
|
||||||
|
bindings: bumpalo::collections::Vec::new_in(bump),
|
||||||
|
cache: HashTable::new(),
|
||||||
|
hasher: DefaultHashBuilder::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_cache(&self, key: IrRef<'id, 'ir>, token: &GhostToken<'id>) -> Option<ThunkId> {
|
||||||
|
let hash = self.hasher.hash_one(IrKey(key, token));
|
||||||
|
self.cache
|
||||||
|
.find(hash, |&(ir, _)| ir_content_eq(key, ir, token))
|
||||||
|
.map(|&(_, id)| id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, token: &GhostToken<'id>) {
|
||||||
|
self.bindings.push((id, ir));
|
||||||
|
let hash = self.hasher.hash_one(IrKey(ir, token));
|
||||||
|
self.cache.insert_unique(hash, (ir, id), |&(ir, _)| {
|
||||||
|
self.hasher.hash_one(IrKey(ir, token))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
||||||
|
self.bindings.extend(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Scope<'ctx> {
|
||||||
|
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
||||||
|
Repl(&'ctx HashSet<StringId>),
|
||||||
|
ScopedImport(HashSet<StringId>),
|
||||||
|
Let(HashMap<StringId, ThunkId>),
|
||||||
|
Param(StringId, ArgId),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
|
||||||
|
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ScopeGuard<'_, '_, '_, '_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.ctx.scopes.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
|
||||||
|
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
|
||||||
|
self.ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OwnedIr {
|
||||||
|
_bump: Bump,
|
||||||
|
ir: RawIrRef<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedIr {
|
||||||
|
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
||||||
|
Self {
|
||||||
|
_bump: bump,
|
||||||
|
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_ref(&self) -> RawIrRef<'_> {
|
||||||
|
self.ir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BytecodeContext for Runtime {
|
||||||
|
fn intern_string(&mut self, s: &str) -> StringId {
|
||||||
|
StringId(self.strings.get_or_intern(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_span(&mut self, range: TextRange) -> u32 {
|
||||||
|
let id = self.spans.len();
|
||||||
|
let source_id = self
|
||||||
|
.sources
|
||||||
|
.len()
|
||||||
|
.checked_sub(1)
|
||||||
|
.expect("current_source not set");
|
||||||
|
self.spans.push((source_id, range));
|
||||||
|
id as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_code(&self) -> &[u8] {
|
||||||
|
&self.bytecode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_code_mut(&mut self) -> &mut Vec<u8> {
|
||||||
|
&mut self.bytecode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
51
fix/src/runtime/builtins.rs
Normal file
51
fix/src/runtime/builtins.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use hashbrown::HashMap;
|
||||||
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
|
use crate::ir::{Ir, RawIrRef, StringId};
|
||||||
|
|
||||||
|
pub(super) fn new_builtins_env(
|
||||||
|
interner: &mut DefaultStringInterner,
|
||||||
|
) -> HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
|
||||||
|
let mut builtins = HashMap::new();
|
||||||
|
let builtins_sym = StringId(interner.get_or_intern("builtins"));
|
||||||
|
builtins.insert(builtins_sym, Ir::Builtins);
|
||||||
|
|
||||||
|
let free_globals = [
|
||||||
|
"abort",
|
||||||
|
"baseNameOf",
|
||||||
|
"break",
|
||||||
|
"dirOf",
|
||||||
|
"derivation",
|
||||||
|
"derivationStrict",
|
||||||
|
"fetchGit",
|
||||||
|
"fetchMercurial",
|
||||||
|
"fetchTarball",
|
||||||
|
"fetchTree",
|
||||||
|
"fromTOML",
|
||||||
|
"import",
|
||||||
|
"isNull",
|
||||||
|
"map",
|
||||||
|
"placeholder",
|
||||||
|
"removeAttrs",
|
||||||
|
"scopedImport",
|
||||||
|
"throw",
|
||||||
|
"toString",
|
||||||
|
];
|
||||||
|
let consts = [
|
||||||
|
("true", Ir::Bool(true)),
|
||||||
|
("false", Ir::Bool(false)),
|
||||||
|
("null", Ir::Null),
|
||||||
|
];
|
||||||
|
|
||||||
|
for name in free_globals {
|
||||||
|
let name = StringId(interner.get_or_intern(name));
|
||||||
|
let value = Ir::Builtin(name);
|
||||||
|
builtins.insert(name, value);
|
||||||
|
}
|
||||||
|
for (name, value) in consts {
|
||||||
|
let name = StringId(interner.get_or_intern(name));
|
||||||
|
builtins.insert(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
builtins
|
||||||
|
}
|
||||||
89
fix/src/runtime/stack.rs
Normal file
89
fix/src/runtime/stack.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
|
use gc_arena::Collect;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
// FIXME: Drop???
|
||||||
|
pub(super) struct Stack<const N: usize, T> {
|
||||||
|
inner: Box<[MaybeUninit<T>; N]>,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<'gc, const N: usize, T: Collect<'gc> + 'gc> Collect<'gc> for Stack<N, T> {
|
||||||
|
const NEEDS_TRACE: bool = true;
|
||||||
|
fn trace<U: gc_arena::collect::Trace<'gc>>(&self, cc: &mut U) {
|
||||||
|
for item in self.inner[..self.len].iter() {
|
||||||
|
unsafe {
|
||||||
|
item.assume_init_ref().trace(cc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize, T> Default for Stack<N, T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize, T> Stack<N, T> {
|
||||||
|
pub(super) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Box::new([const { MaybeUninit::uninit() }; N]),
|
||||||
|
len: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn push(&mut self, val: T) -> Result<(), T> {
|
||||||
|
if self.len == N {
|
||||||
|
return Err(val);
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
self.inner.get_unchecked_mut(self.len).write(val);
|
||||||
|
}
|
||||||
|
self.len += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn pop(&mut self) -> Option<T> {
|
||||||
|
if self.len == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let ret = unsafe {
|
||||||
|
self.inner
|
||||||
|
.get_unchecked_mut(self.len - 1)
|
||||||
|
.assume_init_read()
|
||||||
|
};
|
||||||
|
self.len -= 1;
|
||||||
|
Some(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn tos(&self) -> Option<&T> {
|
||||||
|
if self.len == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(unsafe { self.inner.get_unchecked(self.len - 1).assume_init_ref() })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn tos_mut(&mut self) -> Option<&mut T> {
|
||||||
|
if self.len == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(unsafe { self.inner.get_unchecked_mut(self.len - 1).assume_init_mut() })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn pop_n<const M: usize>(&mut self, n: usize) -> SmallVec<[T; M]> {
|
||||||
|
assert!(n <= self.len, "pop_n: not enough items on stack");
|
||||||
|
let mut result = SmallVec::new();
|
||||||
|
let start = self.len - n;
|
||||||
|
for i in start..self.len {
|
||||||
|
result.push(unsafe { self.inner.get_unchecked(i).assume_init_read() });
|
||||||
|
}
|
||||||
|
self.len = start;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +1,121 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::mem::size_of;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use boxing::nan::raw::{RawBox, RawStore, RawTag, Value as RawValue};
|
use boxing::nan::raw::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||||
use gc_arena::{Collect, Gc};
|
use gc_arena::{Collect, Gc, Mutation, RefLock, collect::Trace};
|
||||||
use hashbrown::HashTable;
|
use sealed::sealed;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use string_interner::{Symbol, symbol::SymbolU32};
|
||||||
|
|
||||||
trait Storable {
|
use crate::ir::StringId;
|
||||||
|
|
||||||
|
#[sealed]
|
||||||
|
pub(crate) trait Storable {
|
||||||
const TAG: (bool, u8);
|
const TAG: (bool, u8);
|
||||||
}
|
}
|
||||||
trait InlineStorable: Storable + RawStore {}
|
pub(crate) trait InlineStorable: Storable + RawStore {}
|
||||||
trait GcStorable: Storable {}
|
pub(crate) trait GcStorable: Storable {}
|
||||||
|
|
||||||
macro_rules! inline_types {
|
macro_rules! define_value_types {
|
||||||
($($type:ty => $id:expr),*$(,)?) => {
|
(
|
||||||
|
inline { $($itype:ty => $itag:expr, $iname:literal;)* }
|
||||||
|
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* }
|
||||||
|
) => {
|
||||||
$(
|
$(
|
||||||
impl Storable for $type {
|
#[sealed]
|
||||||
const TAG: (bool, u8) = (false, $id);
|
impl Storable for $itype {
|
||||||
|
const TAG: (bool, u8) = $itag;
|
||||||
}
|
}
|
||||||
impl InlineStorable for $type {}
|
impl InlineStorable for $itype {}
|
||||||
)*
|
)*
|
||||||
|
$(
|
||||||
|
#[sealed]
|
||||||
|
impl Storable for $gtype {
|
||||||
|
const TAG: (bool, u8) = $gtag;
|
||||||
|
}
|
||||||
|
impl GcStorable for $gtype {}
|
||||||
|
)*
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<Value<'static>>() == 8);
|
||||||
|
$(const _: () = assert!(size_of::<$itype>() <= 6);)*
|
||||||
|
$(const _: () = { let (_, val) = $itag; assert!(val >= 1 && val <= 7); };)*
|
||||||
|
$(const _: () = { let (_, val) = $gtag; assert!(val >= 1 && val <= 7); };)*
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
let tags: &[(bool, u8)] = &[$($itag),*, $($gtag),*];
|
||||||
|
let mut mask_false: u8 = 0;
|
||||||
|
let mut mask_true: u8 = 0;
|
||||||
|
let mut i = 0;
|
||||||
|
while i < tags.len() {
|
||||||
|
let (neg, val) = tags[i];
|
||||||
|
let bit = 1 << val;
|
||||||
|
if neg {
|
||||||
|
assert!(mask_true & bit == 0, "duplicate true tag id");
|
||||||
|
mask_true |= bit;
|
||||||
|
} else {
|
||||||
|
assert!(mask_false & bit == 0, "duplicate false tag id");
|
||||||
|
mask_false |= bit;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
macro_rules! gc_types {
|
unsafe impl<'gc> Collect<'gc> for Value<'gc> {
|
||||||
($($type:ty => $id:expr),*$(,)?) => {
|
const NEEDS_TRACE: bool = true;
|
||||||
$(
|
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
|
||||||
impl Storable for $type {
|
let Some(tag) = self.raw.tag() else { return };
|
||||||
const TAG: (bool, u8) = (true, $id);
|
match tag.neg_val() {
|
||||||
|
$(<$gtype as Storable>::TAG => unsafe {
|
||||||
|
self.load_gc::<$gtype>().trace(cc)
|
||||||
|
},)*
|
||||||
|
$(<$itype as Storable>::TAG => (),)*
|
||||||
|
(neg, val) => unreachable!("invalid tag: neg={neg}, val={val}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Value<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self.tag() {
|
||||||
|
None => write!(f, "Float({:?})", unsafe {
|
||||||
|
self.raw.float().unwrap_unchecked()
|
||||||
|
}),
|
||||||
|
$(Some(<$itype as Storable>::TAG) => write!(f, "{}({:?})", $iname, unsafe {
|
||||||
|
self.as_inline::<$itype>().unwrap_unchecked()
|
||||||
|
}),)*
|
||||||
|
$(Some(<$gtype as Storable>::TAG) =>
|
||||||
|
write!(f, "{}({:?})", $gname, unsafe { self.as_gc::<$gtype>().unwrap_unchecked() }),)*
|
||||||
|
Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl GcStorable for $type {}
|
|
||||||
)*
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
inline_types! {
|
define_value_types! {
|
||||||
i32 => 1,
|
inline {
|
||||||
bool => 2,
|
i32 => (false, 1), "SmallInt";
|
||||||
Null => 3,
|
bool => (false, 2), "Bool";
|
||||||
SmallStringId => 4,
|
Null => (false, 3), "Null";
|
||||||
}
|
StringId => (false, 4), "SmallString";
|
||||||
gc_types! {
|
PrimOp => (false, 5), "PrimOp";
|
||||||
i64 => 1,
|
}
|
||||||
NixString => 2,
|
gc {
|
||||||
SmallAttrSet<'_> => 3,
|
i64 => (false, 6), "BigInt";
|
||||||
AttrSet<'_> => 4,
|
NixString => (false, 7), "String";
|
||||||
Box<[Value<'_>]> => 5,
|
AttrSet<'_> => (true, 1), "AttrSet";
|
||||||
|
List<'_> => (true, 2), "List";
|
||||||
|
Thunk<'_> => (true, 3), "Thunk";
|
||||||
|
Closure<'_> => (true, 4), "Closure";
|
||||||
|
PrimOpApp<'_> => (true, 5), "PrimOpApp";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Nix runtime value representation
|
/// # Nix runtime value representation
|
||||||
///
|
///
|
||||||
/// NaN-boxed value fitting in 8 bytes. Morally equivalent to:
|
/// NaN-boxed value fitting in 8 bytes.
|
||||||
/// ```ignore
|
|
||||||
/// enum Value<'gc> {
|
|
||||||
/// Float(SingleNaNF64),
|
|
||||||
/// SmallInt(i32),
|
|
||||||
/// BigInt(Gc<'gc, i64>),
|
|
||||||
/// Bool(bool),
|
|
||||||
/// Null,
|
|
||||||
/// SmallString(SmallStringId),
|
|
||||||
/// String(Gc<'gc, NixString>),
|
|
||||||
/// SmallAttrSet(Gc<'gc, SmallAttrSet<'gc>>),
|
|
||||||
/// AttrSet(Gc<'gc, AttrSet<'gc>>),
|
|
||||||
/// List(Gc<'gc, Box<[Value<'gc>]>>),
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub(crate) struct Value<'gc> {
|
pub(crate) struct Value<'gc> {
|
||||||
raw: RawBox,
|
raw: RawBox,
|
||||||
@@ -79,90 +132,16 @@ impl Clone for Value<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! trace_impl {
|
impl Default for Value<'_> {
|
||||||
($self:expr, $cc:expr, $tag:expr; $($type:ty),*$(,)?) => {
|
#[inline(always)]
|
||||||
match $tag.neg_val() {
|
fn default() -> Self {
|
||||||
// Positive tags are inline data - skip it.
|
Self::new_inline(Null)
|
||||||
(false, _) => (),
|
|
||||||
|
|
||||||
// Negative tags are Gc pointers - reconstruct and trace it.
|
|
||||||
$(
|
|
||||||
<$type as Storable>::TAG => unsafe { $self.load_gc::<$type>().trace($cc) },
|
|
||||||
)*
|
|
||||||
|
|
||||||
(true, val) => unreachable!("invalid negative tag value: {val}"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<'gc> Collect for Value<'gc> {
|
|
||||||
fn trace(&self, cc: &gc_arena::Collection) {
|
|
||||||
// No tag - raw float
|
|
||||||
let Some(tag) = self.raw.tag() else { return };
|
|
||||||
trace_impl! {
|
|
||||||
self, cc, tag;
|
|
||||||
i64,
|
|
||||||
NixString,
|
|
||||||
SmallAttrSet<'gc>,
|
|
||||||
AttrSet<'gc>,
|
|
||||||
Box<[Value<'gc>]>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_trace() -> bool
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! debug_impl {
|
|
||||||
($self:expr; $($type:ty),*$(,)?) => {
|
|
||||||
match $self.tag() {
|
|
||||||
None => write!(f, "Float({:?})", unsafe {
|
|
||||||
self.raw.float().unwrap_unchecked()
|
|
||||||
}),
|
|
||||||
|
|
||||||
$(
|
|
||||||
Some()
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Value<'_> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self.tag() {
|
|
||||||
None => write!(f, "Float({:?})", unsafe {
|
|
||||||
self.raw.float().unwrap_unchecked()
|
|
||||||
}),
|
|
||||||
Some(i32::TAG) => write!(f, "SmallInt({:?})", unsafe {
|
|
||||||
self.as_inline::<i32>().unwrap_unchecked()
|
|
||||||
}),
|
|
||||||
Some(bool::TAG) => write!(f, "Bool({:?})", unsafe {
|
|
||||||
self.as_inline::<bool>().unwrap_unchecked()
|
|
||||||
}),
|
|
||||||
Some(Null::TAG) => write!(f, "Null"),
|
|
||||||
Some(SmallStringId::TAG) => {
|
|
||||||
write!(f, "SmallString({:?})", unsafe {
|
|
||||||
self.as_inline::<SmallStringId>().unwrap_unchecked().0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Some(i64::TAG) => write!(f, "BigInt(Gc<..>)"),
|
|
||||||
Some(NixString::TAG) => write!(f, "String(Gc<..>)"),
|
|
||||||
Some(SmallAttrSet::TAG) => write!(f, "SmallAttrSet(Gc<..>)"),
|
|
||||||
Some(AttrSet::TAG) => write!(f, "AttrSet(Gc<..>)"),
|
|
||||||
Some(<Box<[Value<'_>]> as Storable>::TAG) => write!(f, "List(Gc<..>)"),
|
|
||||||
Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Value<'gc> {
|
impl<'gc> Value<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn mk_tag(neg: bool, val: u8) -> RawTag {
|
fn mk_tag(neg: bool, val: u8) -> RawTag {
|
||||||
debug_assert!((1..=7).contains(&val));
|
|
||||||
// Safety: val is asserted to be in 1..=7.
|
// Safety: val is asserted to be in 1..=7.
|
||||||
unsafe { RawTag::new_unchecked(neg, val) }
|
unsafe { RawTag::new_unchecked(neg, val) }
|
||||||
}
|
}
|
||||||
@@ -175,20 +154,13 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store a GC pointer with the given (negative) tag value.
|
|
||||||
#[inline(always)]
|
|
||||||
fn store_gc<T>(tag_val: u8, gc: Gc<'gc, T>) -> Self {
|
|
||||||
let ptr = Gc::as_ptr(gc);
|
|
||||||
Self::from_raw_value(RawValue::store(Self::mk_tag(true, tag_val), ptr))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a GC pointer from a value with a negative tag.
|
/// Load a GC pointer from a value with a negative tag.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// The value must actually store a `Gc<'gc, T>` with the matching type.
|
/// The value must actually store a `Gc<'gc, T>` with the matching type.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
unsafe fn load_gc<T>(&self) -> Gc<'gc, T> {
|
unsafe fn load_gc<T: GcStorable>(&self) -> Gc<'gc, T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let rv = self.raw.value().unwrap_unchecked();
|
let rv = self.raw.value().unwrap_unchecked();
|
||||||
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
||||||
@@ -214,13 +186,13 @@ impl<'gc> Value<'gc> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
|
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
|
||||||
Self::from_raw_value(RawValue::store(Self::mk_tag(false, T::TAG.1), val))
|
Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), val))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
|
pub(crate) fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
|
||||||
let ptr = Gc::as_ptr(gc);
|
let ptr = Gc::as_ptr(gc);
|
||||||
Self::from_raw_value(RawValue::store(Self::mk_tag(true, T::TAG.1), ptr))
|
Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), ptr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,6 +240,7 @@ impl<'gc> Value<'gc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub(crate) struct Null;
|
pub(crate) struct Null;
|
||||||
impl RawStore for Null {
|
impl RawStore for Null {
|
||||||
fn to_val(self, value: &mut RawValue) {
|
fn to_val(self, value: &mut RawValue) {
|
||||||
@@ -278,17 +251,15 @@ impl RawStore for Null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: size?
|
impl RawStore for StringId {
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Collect)]
|
|
||||||
#[collect(require_static)]
|
|
||||||
pub(crate) struct SmallStringId(u32);
|
|
||||||
impl RawStore for SmallStringId {
|
|
||||||
fn to_val(self, value: &mut RawValue) {
|
fn to_val(self, value: &mut RawValue) {
|
||||||
self.0.to_val(value);
|
(self.0.to_usize() as u32).to_val(value);
|
||||||
}
|
}
|
||||||
fn from_val(value: &RawValue) -> Self {
|
fn from_val(value: &RawValue) -> Self {
|
||||||
Self(u32::from_val(value))
|
Self(
|
||||||
|
SymbolU32::try_from_usize(u32::from_val(value) as usize)
|
||||||
|
.expect("failed to read StringId from Value"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,45 +291,199 @@ impl fmt::Debug for NixString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fixed-size attribute set (up to 8 entries).
|
#[derive(Collect, Debug)]
|
||||||
#[derive(Collect)]
|
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) struct SmallAttrSet<'gc> {
|
|
||||||
// TODO: proper key storage, length tracking, and lookup
|
|
||||||
inner: [Value<'gc>; 8],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hash-table-backed attribute set.
|
|
||||||
pub(crate) struct AttrSet<'gc> {
|
pub(crate) struct AttrSet<'gc> {
|
||||||
inner: HashTable<AttrSetEntry<'gc>>,
|
pub(crate) entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'gc> Collect for AttrSet<'gc> {
|
impl<'gc> AttrSet<'gc> {
|
||||||
fn trace(&self, cc: &gc_arena::Collection) {
|
pub(crate) fn from_sorted(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
||||||
for entry in self.inner.iter() {
|
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||||
Collect::trace(&entry.key, cc);
|
Self { entries }
|
||||||
Collect::trace(&entry.value, cc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn needs_trace() -> bool
|
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||||
where
|
self.entries
|
||||||
Self: Sized,
|
.binary_search_by_key(&key, |(k, _)| *k)
|
||||||
{
|
.ok()
|
||||||
true
|
.map(|i| self.entries[i].1.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn has(&self, key: StringId) -> bool {
|
||||||
|
self.entries
|
||||||
|
.binary_search_by_key(&key, |(k, _)| *k)
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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) {
|
||||||
|
Less => {
|
||||||
|
entries.push(self.entries[i].clone());
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
Greater => {
|
||||||
|
entries.push(other.entries[j].clone());
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
Equal => {
|
||||||
|
entries.push(other.entries[j].clone());
|
||||||
|
i += 1;
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Collect)]
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
struct AttrSetEntry<'gc> {
|
pub(crate) struct List<'gc> {
|
||||||
key: AttrKey<'gc>,
|
pub(crate) inner: SmallVec<[Value<'gc>; 4]>,
|
||||||
value: Value<'gc>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Collect)]
|
pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
||||||
|
|
||||||
|
#[derive(Collect, Debug)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub(crate) enum AttrKey<'gc> {
|
pub(crate) enum ThunkState<'gc> {
|
||||||
Small(SmallStringId),
|
Pending {
|
||||||
Large(Gc<'gc, str>),
|
ip: u32,
|
||||||
|
env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||||
|
},
|
||||||
|
Blackhole,
|
||||||
|
Evaluated(Value<'gc>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Collect, Debug)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub(crate) struct Env<'gc> {
|
||||||
|
pub(crate) locals: SmallVec<[Value<'gc>; 4]>,
|
||||||
|
pub(crate) prev: Option<Gc<'gc, RefLock<Env<'gc>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Env<'gc> {
|
||||||
|
pub(crate) fn empty() -> Self {
|
||||||
|
Env {
|
||||||
|
locals: SmallVec::new(),
|
||||||
|
prev: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
locals,
|
||||||
|
prev: Some(prev),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Collect, Debug)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
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(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)]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Collect)]
|
||||||
|
#[collect(require_static)]
|
||||||
|
pub(crate) struct PrimOp {
|
||||||
|
pub(crate) id: u8,
|
||||||
|
pub(crate) arity: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawStore for PrimOp {
|
||||||
|
fn to_val(self, value: &mut RawValue) {
|
||||||
|
value.set_data([0, 0, 0, 0, self.id, self.arity]);
|
||||||
|
}
|
||||||
|
fn from_val(value: &RawValue) -> Self {
|
||||||
|
let [.., id, arity] = *value.data();
|
||||||
|
Self { id, arity }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Collect, Debug)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub(crate) struct PrimOpApp<'gc> {
|
||||||
|
pub(crate) primop: PrimOp,
|
||||||
|
pub(crate) args: SmallVec<[Value<'gc>; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub(crate) struct StrictValue<'gc>(Value<'gc>);
|
||||||
|
|
||||||
|
impl<'gc> StrictValue<'gc> {
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn try_from_forced(val: Value<'gc>) -> Option<Self> {
|
||||||
|
if !val.is::<Thunk<'gc>>() {
|
||||||
|
Some(Self(val))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn into_relaxed(self) -> Value<'gc> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Deref for StrictValue<'gc> {
|
||||||
|
type Target = Value<'gc>;
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Value<'gc> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for StrictValue<'_> {
|
||||||
|
#[inline]
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for StrictValue<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<'gc> Collect<'gc> for StrictValue<'gc> {
|
||||||
|
const NEEDS_TRACE: bool = true;
|
||||||
|
fn trace<T: gc_arena::collect::Trace<'gc>>(&self, cc: &mut T) {
|
||||||
|
self.0.trace(cc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1849
fix/src/runtime/vm.rs
Normal file
1849
fix/src/runtime/vm.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
use fix::context::Context;
|
|
||||||
use fix::error::Source;
|
use fix::error::Source;
|
||||||
|
use fix::runtime::Runtime;
|
||||||
use fix::value::Value;
|
use fix::value::Value;
|
||||||
|
|
||||||
use crate::utils::{eval, eval_result};
|
use crate::utils::{eval, eval_result};
|
||||||
@@ -97,7 +97,7 @@ fn import_with_complex_dependency_graph() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn path_with_file() {
|
fn path_with_file() {
|
||||||
let mut ctx = Context::new().unwrap();
|
let mut ctx = Runtime::new().unwrap();
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let test_file = temp_dir.path().join("test.txt");
|
let test_file = temp_dir.path().join("test.txt");
|
||||||
std::fs::write(&test_file, "Hello, World!").unwrap();
|
std::fs::write(&test_file, "Hello, World!").unwrap();
|
||||||
@@ -107,7 +107,7 @@ fn path_with_file() {
|
|||||||
|
|
||||||
// Should return a store path string
|
// Should return a store path string
|
||||||
if let Value::String(store_path) = result {
|
if let Value::String(store_path) = result {
|
||||||
assert!(store_path.starts_with(ctx.get_store_dir()));
|
assert!(store_path.starts_with("/nix/store"));
|
||||||
assert!(store_path.contains("test.txt"));
|
assert!(store_path.contains("test.txt"));
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected string, got {:?}", result);
|
panic!("Expected string, got {:?}", result);
|
||||||
@@ -136,7 +136,7 @@ fn path_with_custom_name() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn path_with_directory_recursive() {
|
fn path_with_directory_recursive() {
|
||||||
let mut ctx = Context::new().unwrap();
|
let mut ctx = Runtime::new().unwrap();
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let test_dir = temp_dir.path().join("mydir");
|
let test_dir = temp_dir.path().join("mydir");
|
||||||
std::fs::create_dir_all(&test_dir).unwrap();
|
std::fs::create_dir_all(&test_dir).unwrap();
|
||||||
@@ -150,7 +150,7 @@ fn path_with_directory_recursive() {
|
|||||||
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
|
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
|
||||||
|
|
||||||
if let Value::String(store_path) = result {
|
if let Value::String(store_path) = result {
|
||||||
assert!(store_path.starts_with(ctx.get_store_dir()));
|
assert!(store_path.starts_with("/nix/store"));
|
||||||
assert!(store_path.contains("mydir"));
|
assert!(store_path.contains("mydir"));
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected string, got {:?}", result);
|
panic!("Expected string, got {:?}", result);
|
||||||
@@ -159,7 +159,7 @@ fn path_with_directory_recursive() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn path_flat_with_file() {
|
fn path_flat_with_file() {
|
||||||
let mut ctx = Context::new().unwrap();
|
let mut ctx = Runtime::new().unwrap();
|
||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let test_file = temp_dir.path().join("flat.txt");
|
let test_file = temp_dir.path().join("flat.txt");
|
||||||
std::fs::write(&test_file, "Flat content").unwrap();
|
std::fs::write(&test_file, "Flat content").unwrap();
|
||||||
@@ -171,7 +171,7 @@ fn path_flat_with_file() {
|
|||||||
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
|
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
|
||||||
|
|
||||||
if let Value::String(store_path) = result {
|
if let Value::String(store_path) = result {
|
||||||
assert!(store_path.starts_with(ctx.get_store_dir()));
|
assert!(store_path.starts_with("/nix/store"));
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected string, got {:?}", result);
|
panic!("Expected string, got {:?}", result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use fix::context::Context;
|
|
||||||
use fix::error::Source;
|
use fix::error::Source;
|
||||||
|
use fix::runtime::Runtime;
|
||||||
use fix::value::Value;
|
use fix::value::Value;
|
||||||
|
|
||||||
fn get_lang_dir() -> PathBuf {
|
fn get_lang_dir() -> PathBuf {
|
||||||
@@ -16,7 +16,7 @@ fn eval_file(name: &str) -> Result<(Value, Source), String> {
|
|||||||
|
|
||||||
let expr = format!(r#"import "{}""#, nix_path.display());
|
let expr = format!(r#"import "{}""#, nix_path.display());
|
||||||
|
|
||||||
let mut ctx = Context::new().map_err(|e| e.to_string())?;
|
let mut ctx = Runtime::new().map_err(|e| e.to_string())?;
|
||||||
let source = Source {
|
let source = Source {
|
||||||
ty: fix::error::SourceType::File(nix_path.into()),
|
ty: fix::error::SourceType::File(nix_path.into()),
|
||||||
src: expr.into(),
|
src: expr.into(),
|
||||||
@@ -247,7 +247,7 @@ eval_fail_test!(fail_abort);
|
|||||||
eval_fail_test!(fail_addDrvOutputDependencies_empty_context);
|
eval_fail_test!(fail_addDrvOutputDependencies_empty_context);
|
||||||
eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context);
|
eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context);
|
||||||
eval_fail_test!(fail_addDrvOutputDependencies_wrong_element_kind);
|
eval_fail_test!(fail_addDrvOutputDependencies_wrong_element_kind);
|
||||||
eval_fail_test!(fail_addErrorContext_example);
|
eval_fail_test!(fail_addErrorRuntime_example);
|
||||||
eval_fail_test!(fail_assert);
|
eval_fail_test!(fail_assert);
|
||||||
eval_fail_test!(fail_assert_equal_attrs_names);
|
eval_fail_test!(fail_assert_equal_attrs_names);
|
||||||
eval_fail_test!(fail_assert_equal_attrs_names_2);
|
eval_fail_test!(fail_assert_equal_attrs_names_2);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use fix::context::Context;
|
use fix::runtime::Runtime;
|
||||||
use fix::value::Value;
|
use fix::value::Value;
|
||||||
|
|
||||||
use crate::utils::eval_result;
|
use crate::utils::eval_result;
|
||||||
@@ -153,8 +153,8 @@ fn string_add_merges_context() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn context_in_derivation_args() {
|
fn context_in_derivation_args() {
|
||||||
let mut ctx = Context::new().unwrap();
|
let mut rt = Runtime::new().unwrap();
|
||||||
let result = ctx
|
let result = rt
|
||||||
.eval(
|
.eval(
|
||||||
r#"
|
r#"
|
||||||
let
|
let
|
||||||
@@ -173,7 +173,7 @@ fn context_in_derivation_args() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
match result {
|
match result {
|
||||||
Value::String(s) => {
|
Value::String(s) => {
|
||||||
assert!(s.starts_with(ctx.get_store_dir()), "Should be a store path");
|
assert!(s.starts_with("/nix/store"), "Should be a store path");
|
||||||
assert!(s.ends_with(".drv"), "Should be a .drv file");
|
assert!(s.ends_with(".drv"), "Should be a .drv file");
|
||||||
}
|
}
|
||||||
_ => panic!("Expected String, got {:?}", result),
|
_ => panic!("Expected String, got {:?}", result),
|
||||||
@@ -182,8 +182,8 @@ fn context_in_derivation_args() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn context_in_derivation_env() {
|
fn context_in_derivation_env() {
|
||||||
let mut ctx = Context::new().unwrap();
|
let mut rt = Runtime::new().unwrap();
|
||||||
let result = ctx
|
let result = rt
|
||||||
.eval(
|
.eval(
|
||||||
r#"
|
r#"
|
||||||
let
|
let
|
||||||
@@ -202,7 +202,7 @@ fn context_in_derivation_env() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
match result {
|
match result {
|
||||||
Value::String(s) => {
|
Value::String(s) => {
|
||||||
assert!(s.starts_with(ctx.get_store_dir()), "Should be a store path");
|
assert!(s.starts_with("/nix/store"), "Should be a store path");
|
||||||
assert!(s.ends_with(".drv"), "Should be a .drv file");
|
assert!(s.ends_with(".drv"), "Should be a .drv file");
|
||||||
}
|
}
|
||||||
_ => panic!("Expected String, got {:?}", result),
|
_ => panic!("Expected String, got {:?}", result),
|
||||||
@@ -224,8 +224,8 @@ fn tostring_preserves_context() {
|
|||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn interpolation_derivation_returns_outpath() {
|
fn interpolation_derivation_returns_outpath() {
|
||||||
let mut ctx = Context::new().unwrap();
|
let mut rt = Runtime::new().unwrap();
|
||||||
let result = ctx
|
let result = rt
|
||||||
.eval(
|
.eval(
|
||||||
r#"
|
r#"
|
||||||
let
|
let
|
||||||
@@ -238,7 +238,7 @@ fn interpolation_derivation_returns_outpath() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
match result {
|
match result {
|
||||||
Value::String(s) => {
|
Value::String(s) => {
|
||||||
assert!(s.starts_with(ctx.get_store_dir()), "Should be a store path");
|
assert!(s.starts_with("/nix/store"), "Should be a store path");
|
||||||
assert!(s.ends_with("-test"), "Should end with derivation name");
|
assert!(s.ends_with("-test"), "Should end with derivation name");
|
||||||
}
|
}
|
||||||
_ => panic!("Expected String, got {:?}", result),
|
_ => panic!("Expected String, got {:?}", result),
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use fix::context::Context;
|
|
||||||
use fix::error::{Result, Source};
|
use fix::error::{Result, Source};
|
||||||
|
use fix::runtime::Runtime;
|
||||||
use fix::value::Value;
|
use fix::value::Value;
|
||||||
|
|
||||||
pub fn eval(expr: &str) -> Value {
|
pub fn eval(expr: &str) -> Value {
|
||||||
Context::new()
|
Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.eval(Source::new_eval(expr.into()).unwrap())
|
.eval(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_deep(expr: &str) -> Value {
|
pub fn eval_deep(expr: &str) -> Value {
|
||||||
Context::new()
|
Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.eval_deep(Source::new_eval(expr.into()).unwrap())
|
.eval_deep(Source::new_eval(expr.into()).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_deep_result(expr: &str) -> Result<Value> {
|
pub fn eval_deep_result(expr: &str) -> Result<Value> {
|
||||||
Context::new()
|
Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.eval_deep(Source::new_eval(expr.into()).unwrap())
|
.eval_deep(Source::new_eval(expr.into()).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eval_result(expr: &str) -> Result<Value> {
|
pub fn eval_result(expr: &str) -> Result<Value> {
|
||||||
Context::new()
|
Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.eval(Source::new_eval(expr.into()).unwrap())
|
.eval(Source::new_eval(expr.into()).unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
18
flake.lock
generated
18
flake.lock
generated
@@ -8,11 +8,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770966612,
|
"lastModified": 1773471952,
|
||||||
"narHash": "sha256-S6k14z/JsDwX6zZyLucDBTOe/9RsvxH9GTUxHn2o4vc=",
|
"narHash": "sha256-kIRggXyT8RzijtfvyRIzj+zIDWM2fnCp8t0X4BkkTVc=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "e90d48dcfaebac7ea7a5687888a2d0733be26343",
|
"rev": "a1b770adbc3f6c27485d03b90462ec414d4e1ce5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -37,11 +37,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770841267,
|
"lastModified": 1773282481,
|
||||||
"narHash": "sha256-9xejG0KoqsoKEGp2kVbXRlEYtFFcDTHjidiuX8hGO44=",
|
"narHash": "sha256-b/GV2ysM8mKHhinse2wz+uP37epUrSE+sAKXy/xvBY4=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ec7c70d12ce2fc37cb92aff673dcdca89d187bae",
|
"rev": "fe416aaedd397cacb33a610b33d60ff2b431b127",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -61,11 +61,11 @@
|
|||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770934477,
|
"lastModified": 1773326183,
|
||||||
"narHash": "sha256-GX0cINHhhzUbQHyDYN2Mc+ovb6Sx/4yrF95VVou9aW4=",
|
"narHash": "sha256-tj3piRd9RnnP36HwHmQD4O4XZeowsH/rvMeyp9Pmot0=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "931cd553be123b11db1435ac7ea5657e62e5e601",
|
"rev": "6254616e97f358e67b70dfc0463687f5f7911c1a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
Reference in New Issue
Block a user