Compare commits
3
Commits
1083b8fdfa
...
198d847151
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
198d847151
|
||
|
|
40d00a6c47
|
||
|
|
0c9a391618
|
No files matched your search
@@ -16,9 +16,6 @@ vim.lsp.config("rust_analyzer", {
|
||||
settings = {
|
||||
["rust-analyzer"] = {
|
||||
cargo = {
|
||||
features = {
|
||||
"inspector"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+127
-963
File diff suppressed because it is too large.
Load diff
+2
-2
@@ -1,8 +1,8 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"nix-js",
|
||||
"nix-js-macros"
|
||||
"fix",
|
||||
"boxing",
|
||||
]
|
||||
|
||||
[profile.profiling]
|
||||
|
||||
@@ -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"
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod nan;
|
||||
mod utils;
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
[package]
|
||||
name = "nix-js"
|
||||
name = "fix"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
mimalloc = "0.1"
|
||||
|
||||
tokio = { version = "1.41", features = ["rt-multi-thread", "sync", "net", "io-util"] }
|
||||
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = ["wire", "async"] }
|
||||
tokio = { version = "1.41", features = [
|
||||
"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
|
||||
anyhow = "1.0"
|
||||
@@ -27,17 +34,18 @@ miette = { version = "7.4", features = ["fancy"] }
|
||||
|
||||
hashbrown = "0.16"
|
||||
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"
|
||||
|
||||
regex = "1.11"
|
||||
|
||||
deno_core = "0.385"
|
||||
deno_error = "0.7"
|
||||
|
||||
nix-nar = "0.3"
|
||||
sha2 = "0.10"
|
||||
sha1 = "0.10"
|
||||
@@ -46,7 +54,10 @@ hex = "0.4"
|
||||
|
||||
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"
|
||||
flate2 = "1.0"
|
||||
xz2 = "0.1"
|
||||
@@ -54,7 +65,7 @@ bzip2 = "0.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
# spec 1.0.0
|
||||
toml = "0.9.9"
|
||||
toml = "=0.9.9"
|
||||
dirs = "6.0"
|
||||
tempfile = "3.24"
|
||||
rusqlite = { version = "0.38", features = ["bundled"] }
|
||||
@@ -62,24 +73,21 @@ rusqlite = { version = "0.38", features = ["bundled"] }
|
||||
rnix = "0.14"
|
||||
rowan = "0.16"
|
||||
|
||||
nix-js-macros = { path = "../nix-js-macros" }
|
||||
ere = "0.2.4"
|
||||
num_enum = "0.7.5"
|
||||
tap = "1.0.1"
|
||||
|
||||
# Inspector (optional)
|
||||
fastwebsockets = { version = "0.10", features = ["upgrade"], optional = true }
|
||||
hyper = { version = "1", features = ["http1", "server"], optional = true }
|
||||
hyper-util = { version = "0.1", features = ["tokio"], optional = true }
|
||||
http-body-util = { version = "0.1", optional = true }
|
||||
http = { version = "1", optional = true }
|
||||
uuid = { version = "1", features = ["v4"], optional = true }
|
||||
ghost-cell = "0.2.6"
|
||||
colored = "3.1.1"
|
||||
ghost-cell = "0.2"
|
||||
colored = "3.1"
|
||||
boxing = { path = "../boxing" }
|
||||
sealed = "0.6"
|
||||
small-map = "0.1"
|
||||
smallvec = "1.15"
|
||||
|
||||
[features]
|
||||
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]
|
||||
prof = []
|
||||
[dependencies.gc-arena]
|
||||
git = "https://github.com/kyren/gc-arena"
|
||||
rev = "75671ae03f53718357b741ed4027560f14e90836"
|
||||
features = ["allocator-api2", "hashbrown", "smallvec"]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.8", features = ["html_reports"] }
|
||||
@@ -96,7 +104,3 @@ harness = false
|
||||
[[bench]]
|
||||
name = "thunk_scope"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "compile_time"
|
||||
harness = false
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,18 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use fix::error::{Result, Source};
|
||||
use fix::runtime::Runtime;
|
||||
use fix::value::Value;
|
||||
|
||||
pub fn eval(expr: &str) -> Value {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.eval(Source::new_eval(expr.into()).unwrap())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn eval_result(expr: &str) -> Result<Value> {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.eval(Source::new_eval(expr.into()).unwrap())
|
||||
}
|
||||
@@ -1,101 +1,101 @@
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use gc_arena::Collect;
|
||||
use hashbrown::HashMap;
|
||||
use num_enum::TryFromPrimitive;
|
||||
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) enum Constant {
|
||||
Int(i64),
|
||||
Float(u64),
|
||||
}
|
||||
pub(crate) struct InstructionPtr(pub usize);
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(require_static)]
|
||||
pub struct Bytecode {
|
||||
pub code: Box<[u8]>,
|
||||
pub current_dir: String,
|
||||
}
|
||||
|
||||
pub(crate) trait BytecodeContext {
|
||||
fn intern_string(&mut self, s: &str) -> u32;
|
||||
fn intern_constant(&mut self, c: Constant) -> u32;
|
||||
fn register_span(&self, range: TextRange) -> u32;
|
||||
fn get_sym(&self, id: SymId) -> &str;
|
||||
fn get_current_dir(&self) -> &Path;
|
||||
fn intern_string(&mut self, s: &str) -> StringId;
|
||||
fn register_span(&mut self, range: TextRange) -> u32;
|
||||
fn get_code(&self) -> &[u8];
|
||||
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, TryFromPrimitive)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum Op {
|
||||
PushConst = 0x01,
|
||||
PushString = 0x02,
|
||||
PushNull = 0x03,
|
||||
PushTrue = 0x04,
|
||||
PushFalse = 0x05,
|
||||
PushSmi,
|
||||
PushBigInt,
|
||||
PushFloat,
|
||||
PushString,
|
||||
PushNull,
|
||||
PushTrue,
|
||||
PushFalse,
|
||||
|
||||
LoadLocal = 0x06,
|
||||
LoadOuter = 0x07,
|
||||
StoreLocal = 0x08,
|
||||
AllocLocals = 0x09,
|
||||
LoadLocal,
|
||||
LoadOuter,
|
||||
StoreLocal,
|
||||
AllocLocals,
|
||||
|
||||
MakeThunk = 0x0A,
|
||||
MakeClosure = 0x0B,
|
||||
MakePatternClosure = 0x0C,
|
||||
MakeThunk,
|
||||
MakeClosure,
|
||||
MakePatternClosure,
|
||||
|
||||
Call = 0x0D,
|
||||
CallNoSpan = 0x0E,
|
||||
Call,
|
||||
CallNoSpan,
|
||||
|
||||
MakeAttrs = 0x0F,
|
||||
MakeAttrsDyn = 0x10,
|
||||
MakeEmptyAttrs = 0x11,
|
||||
Select = 0x12,
|
||||
SelectDefault = 0x13,
|
||||
HasAttr = 0x14,
|
||||
MakeAttrs,
|
||||
MakeAttrsDyn,
|
||||
MakeEmptyAttrs,
|
||||
Select,
|
||||
SelectDefault,
|
||||
HasAttr,
|
||||
|
||||
MakeList = 0x15,
|
||||
MakeList,
|
||||
|
||||
OpAdd = 0x16,
|
||||
OpSub = 0x17,
|
||||
OpMul = 0x18,
|
||||
OpDiv = 0x19,
|
||||
OpEq = 0x20,
|
||||
OpNeq = 0x21,
|
||||
OpLt = 0x22,
|
||||
OpGt = 0x23,
|
||||
OpLeq = 0x24,
|
||||
OpGeq = 0x25,
|
||||
OpConcat = 0x26,
|
||||
OpUpdate = 0x27,
|
||||
OpAdd,
|
||||
OpSub,
|
||||
OpMul,
|
||||
OpDiv,
|
||||
OpEq,
|
||||
OpNeq,
|
||||
OpLt,
|
||||
OpGt,
|
||||
OpLeq,
|
||||
OpGeq,
|
||||
OpConcat,
|
||||
OpUpdate,
|
||||
|
||||
OpNeg = 0x28,
|
||||
OpNot = 0x29,
|
||||
OpNeg,
|
||||
OpNot,
|
||||
|
||||
ForceBool = 0x30,
|
||||
JumpIfFalse = 0x31,
|
||||
JumpIfTrue = 0x32,
|
||||
Jump = 0x33,
|
||||
ForceBool,
|
||||
JumpIfFalse,
|
||||
JumpIfTrue,
|
||||
Jump,
|
||||
|
||||
ConcatStrings = 0x34,
|
||||
ResolvePath = 0x35,
|
||||
ConcatStrings,
|
||||
ResolvePath,
|
||||
|
||||
Assert = 0x36,
|
||||
Assert,
|
||||
|
||||
PushWith = 0x37,
|
||||
PopWith = 0x38,
|
||||
WithLookup = 0x39,
|
||||
PushWith,
|
||||
PopWith,
|
||||
WithLookup,
|
||||
|
||||
LoadBuiltins = 0x40,
|
||||
LoadBuiltin = 0x41,
|
||||
LoadBuiltins,
|
||||
LoadBuiltin,
|
||||
|
||||
MkPos = 0x43,
|
||||
MkPos,
|
||||
|
||||
LoadReplBinding = 0x44,
|
||||
LoadScopedBinding = 0x45,
|
||||
LoadReplBinding,
|
||||
LoadScopedBinding,
|
||||
|
||||
Return = 0x46,
|
||||
Return,
|
||||
}
|
||||
|
||||
struct ScopeInfo {
|
||||
@@ -106,71 +106,78 @@ struct ScopeInfo {
|
||||
|
||||
struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
||||
ctx: &'a mut Ctx,
|
||||
code: Vec<u8>,
|
||||
scope_stack: Vec<ScopeInfo>,
|
||||
}
|
||||
|
||||
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> Bytecode {
|
||||
let current_dir = ctx.get_current_dir().to_string_lossy().to_string();
|
||||
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
||||
let ip = ctx.get_code().len();
|
||||
let mut emitter = BytecodeEmitter::new(ctx);
|
||||
emitter.emit_toplevel(ir);
|
||||
Bytecode {
|
||||
code: emitter.code.into_boxed_slice(),
|
||||
current_dir,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compile_bytecode_scoped(
|
||||
ir: RawIrRef<'_>,
|
||||
ctx: &mut impl BytecodeContext,
|
||||
) -> Bytecode {
|
||||
let current_dir = ctx.get_current_dir().to_string_lossy().to_string();
|
||||
let mut emitter = BytecodeEmitter::new(ctx);
|
||||
emitter.emit_toplevel_scoped(ir);
|
||||
Bytecode {
|
||||
code: emitter.code.into_boxed_slice(),
|
||||
current_dir,
|
||||
}
|
||||
InstructionPtr(ip)
|
||||
}
|
||||
|
||||
impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
fn new(ctx: &'a mut Ctx) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
code: Vec::with_capacity(4096),
|
||||
scope_stack: Vec::with_capacity(32),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_op(&mut self, op: Op) {
|
||||
self.code.push(op as u8);
|
||||
self.ctx.get_code_mut().push(op as u8);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_u8(&mut self, val: u8) {
|
||||
self.code.push(val);
|
||||
self.ctx.get_code_mut().push(val);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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]
|
||||
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]
|
||||
fn emit_i32_placeholder(&mut self) -> usize {
|
||||
let offset = self.code.len();
|
||||
self.code.extend_from_slice(&[0u8; 4]);
|
||||
let offset = self.ctx.get_code_mut().len();
|
||||
self.ctx.get_code_mut().extend_from_slice(&[0u8; 4]);
|
||||
offset
|
||||
}
|
||||
#[inline]
|
||||
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]
|
||||
@@ -181,11 +188,18 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
#[inline]
|
||||
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;
|
||||
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 {
|
||||
self.scope_stack.last().map_or(0, |s| s.depth)
|
||||
}
|
||||
@@ -397,50 +411,19 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_toplevel_scoped(&mut self, ir: RawIrRef<'_>) {
|
||||
match ir.deref() {
|
||||
Ir::TopLevel { body, thunks } => {
|
||||
let with_thunk_count = self.count_with_thunks(*body);
|
||||
let total_slots = thunks.len() + with_thunk_count;
|
||||
|
||||
let all_thunks = self.collect_all_thunks(thunks, *body);
|
||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||
|
||||
self.push_scope(false, None, &thunk_ids);
|
||||
|
||||
if total_slots > 0 {
|
||||
self.emit_op(Op::AllocLocals);
|
||||
self.emit_u32(total_slots as u32);
|
||||
}
|
||||
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(*body);
|
||||
self.emit_op(Op::Return);
|
||||
|
||||
self.pop_scope();
|
||||
}
|
||||
_ => {
|
||||
self.push_scope(false, None, &[]);
|
||||
self.emit_expr(ir);
|
||||
self.emit_op(Op::Return);
|
||||
self.pop_scope();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
||||
for &(id, inner) in thunks {
|
||||
let label = format!("e{}", id.0);
|
||||
let label_idx = self.ctx.intern_string(&label);
|
||||
|
||||
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_op(Op::Return);
|
||||
self.patch_jump_target(skip_patch);
|
||||
self.emit_op(Op::MakeThunk);
|
||||
self.emit_u32(entry_point);
|
||||
self.emit_u32(label_idx);
|
||||
self.emit_str_id(label_idx);
|
||||
let (_, local_idx) = self.resolve_thunk(id);
|
||||
self.emit_op(Op::StoreLocal);
|
||||
self.emit_u32(local_idx);
|
||||
@@ -450,14 +433,17 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
||||
match ir.deref() {
|
||||
&Ir::Int(x) => {
|
||||
let idx = self.ctx.intern_constant(Constant::Int(x));
|
||||
self.emit_op(Op::PushConst);
|
||||
self.emit_u32(idx);
|
||||
if x <= i32::MAX as i64 {
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_i32(x as i32);
|
||||
} else {
|
||||
self.emit_op(Op::PushBigInt);
|
||||
self.emit_i64(x);
|
||||
}
|
||||
}
|
||||
&Ir::Float(x) => {
|
||||
let idx = self.ctx.intern_constant(Constant::Float(x.to_bits()));
|
||||
self.emit_op(Op::PushConst);
|
||||
self.emit_u32(idx);
|
||||
self.emit_op(Op::PushFloat);
|
||||
self.emit_f64(x);
|
||||
}
|
||||
&Ir::Bool(true) => self.emit_op(Op::PushTrue),
|
||||
&Ir::Bool(false) => self.emit_op(Op::PushFalse),
|
||||
@@ -465,7 +451,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
Ir::Str(s) => {
|
||||
let idx = self.ctx.intern_string(s.deref());
|
||||
self.emit_op(Op::PushString);
|
||||
self.emit_u32(idx);
|
||||
self.emit_str_id(idx);
|
||||
}
|
||||
&Ir::Path(p) => {
|
||||
self.emit_expr(p);
|
||||
@@ -477,20 +463,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
self.emit_op(Op::JumpIfFalse);
|
||||
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_op(Op::Jump);
|
||||
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);
|
||||
self.patch_i32(else_placeholder, else_offset);
|
||||
|
||||
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);
|
||||
}
|
||||
&Ir::BinOp { lhs, rhs, kind } => {
|
||||
@@ -554,10 +540,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_op(Op::LoadBuiltins);
|
||||
}
|
||||
&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_u32(idx);
|
||||
self.emit_u32(name.0.to_usize() as u32);
|
||||
}
|
||||
&Ir::ConcatStrings {
|
||||
ref parts,
|
||||
@@ -584,7 +568,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_expr(*assertion);
|
||||
self.emit_expr(*expr);
|
||||
self.emit_op(Op::Assert);
|
||||
self.emit_u32(raw_idx);
|
||||
self.emit_str_id(raw_idx);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
&Ir::CurPos(span) => {
|
||||
@@ -593,16 +577,12 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
&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_u32(idx);
|
||||
self.emit_str_id(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_u32(idx);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
&Ir::With {
|
||||
namespace,
|
||||
@@ -612,10 +592,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_with(namespace, body, thunks);
|
||||
}
|
||||
&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_u32(idx);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -628,20 +606,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_op(Op::ForceBool);
|
||||
self.emit_op(Op::JumpIfFalse);
|
||||
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_op(Op::ForceBool);
|
||||
self.emit_op(Op::Jump);
|
||||
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);
|
||||
self.patch_i32(skip_placeholder, false_offset);
|
||||
|
||||
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);
|
||||
}
|
||||
Or => {
|
||||
@@ -649,20 +627,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_op(Op::ForceBool);
|
||||
self.emit_op(Op::JumpIfTrue);
|
||||
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_op(Op::ForceBool);
|
||||
self.emit_op(Op::Jump);
|
||||
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);
|
||||
self.patch_i32(skip_placeholder, true_offset);
|
||||
|
||||
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);
|
||||
}
|
||||
Impl => {
|
||||
@@ -670,20 +648,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_op(Op::ForceBool);
|
||||
self.emit_op(Op::JumpIfFalse);
|
||||
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_op(Op::ForceBool);
|
||||
self.emit_op(Op::Jump);
|
||||
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);
|
||||
self.patch_i32(skip_placeholder, true_offset);
|
||||
|
||||
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);
|
||||
}
|
||||
PipeL => {
|
||||
@@ -732,7 +710,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||
|
||||
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.emit_scope_thunks(thunks);
|
||||
self.emit_expr(body);
|
||||
@@ -754,20 +732,14 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_u8(if *ellipsis { 1 } else { 0 });
|
||||
|
||||
for &(sym, _) in required.iter() {
|
||||
let name = self.ctx.get_sym(sym).to_string();
|
||||
let idx = self.ctx.intern_string(&name);
|
||||
self.emit_u32(idx);
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
for &(sym, _) in optional.iter() {
|
||||
let name = self.ctx.get_sym(sym).to_string();
|
||||
let idx = self.ctx.intern_string(&name);
|
||||
self.emit_u32(idx);
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
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);
|
||||
self.emit_u32(name_idx);
|
||||
self.emit_str_id(sym);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
} else {
|
||||
@@ -779,7 +751,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
fn emit_attrset(
|
||||
&mut self,
|
||||
stcs: &crate::ir::HashMap<'_, SymId, (RawIrRef<'_>, TextRange)>,
|
||||
stcs: &crate::ir::HashMap<'_, StringId, (RawIrRef<'_>, TextRange)>,
|
||||
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
|
||||
) {
|
||||
if stcs.is_empty() && dyns.is_empty() {
|
||||
@@ -789,42 +761,35 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
if !dyns.is_empty() {
|
||||
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_u32(idx);
|
||||
self.emit_str_id(sym);
|
||||
self.emit_expr(val);
|
||||
}
|
||||
for (_, &(_, span)) in stcs.iter() {
|
||||
let span_id = self.ctx.register_span(span);
|
||||
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
||||
self.emit_op(Op::PushConst);
|
||||
self.emit_u32(idx);
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
for &(key, val, span) in dyns.iter() {
|
||||
self.emit_expr(key);
|
||||
self.emit_expr(val);
|
||||
let span_id = self.ctx.register_span(span);
|
||||
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
||||
self.emit_op(Op::PushConst);
|
||||
self.emit_u32(idx);
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
self.emit_op(Op::MakeAttrsDyn);
|
||||
self.emit_u32(stcs.len() as u32);
|
||||
self.emit_u32(dyns.len() as u32);
|
||||
} else {
|
||||
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_u32(idx);
|
||||
self.emit_str_id(sym);
|
||||
self.emit_expr(val);
|
||||
}
|
||||
for (_, &(_, span)) in stcs.iter() {
|
||||
let span_id = self.ctx.register_span(span);
|
||||
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
||||
self.emit_op(Op::PushConst);
|
||||
self.emit_u32(idx);
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
self.emit_op(Op::MakeAttrs);
|
||||
self.emit_u32(stcs.len() as u32);
|
||||
@@ -840,15 +805,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
) {
|
||||
self.emit_expr(expr);
|
||||
for attr in attrpath.iter() {
|
||||
match attr {
|
||||
match *attr {
|
||||
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_u32(idx);
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
Attr::Dynamic(expr, _) => {
|
||||
self.emit_expr(*expr);
|
||||
self.emit_expr(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -870,15 +833,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
||||
self.emit_expr(lhs);
|
||||
for attr in rhs.iter() {
|
||||
match attr {
|
||||
match *attr {
|
||||
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_u32(idx);
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
Attr::Dynamic(expr, _) => {
|
||||
self.emit_expr(*expr);
|
||||
self.emit_expr(expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -3,11 +3,10 @@ use std::fmt::Write;
|
||||
use colored::Colorize;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::bytecode::{Bytecode, Constant, Op};
|
||||
use crate::codegen::{Bytecode, Op};
|
||||
|
||||
pub(crate) trait DisassemblerContext {
|
||||
fn lookup_string(&self, id: u32) -> &str;
|
||||
fn lookup_constant(&self, id: u32) -> &Constant;
|
||||
}
|
||||
|
||||
pub(crate) struct Disassembler<'a, Ctx> {
|
||||
@@ -55,6 +54,22 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
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 {
|
||||
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");
|
||||
|
||||
match op {
|
||||
Op::PushConst => {
|
||||
let idx = self.read_u32();
|
||||
let val = self.ctx.lookup_constant(idx);
|
||||
let val_str = match val {
|
||||
Constant::Int(i) => format!("Int({})", i),
|
||||
Constant::Float(f) => format!("Float(bits: {})", f),
|
||||
};
|
||||
("PushConst", format!("@{} ({})", idx, val_str))
|
||||
Op::PushSmi => {
|
||||
let val = self.read_i32();
|
||||
("PushSmi", format!("{}", val))
|
||||
}
|
||||
Op::PushBigInt => {
|
||||
let val = self.read_i64();
|
||||
("PushBigInt", format!("{}", val))
|
||||
}
|
||||
Op::PushFloat => {
|
||||
let val = self.read_f64();
|
||||
("PushFloat", format!("{}", val))
|
||||
}
|
||||
Op::PushString => {
|
||||
let idx = self.read_u32();
|
||||
@@ -50,16 +50,16 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
|
||||
fn new_arg(&mut self) -> ArgId;
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>;
|
||||
|
||||
fn new_sym(&mut self, sym: String) -> SymId;
|
||||
fn get_sym(&self, id: SymId) -> Symbol<'_>;
|
||||
fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'id, 'ir>>;
|
||||
fn new_sym(&mut self, sym: String) -> StringId;
|
||||
fn get_sym(&self, id: StringId) -> Symbol<'_>;
|
||||
fn lookup(&self, sym: StringId, span: TextRange) -> Result<IrRef<'id, 'ir>>;
|
||||
|
||||
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
|
||||
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
|
||||
F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, 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> {
|
||||
Expr(ast::Expr),
|
||||
InheritFrom(ast::Expr, SymId, TextRange),
|
||||
InheritScope(SymId, TextRange),
|
||||
InheritFrom(ast::Expr, StringId, TextRange),
|
||||
InheritScope(StringId, TextRange),
|
||||
Set(PendingAttrSet<'ir>),
|
||||
ExtendedRecAttrSet {
|
||||
base: ast::AttrSet,
|
||||
@@ -466,7 +466,7 @@ enum PendingValue<'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)>,
|
||||
}
|
||||
|
||||
@@ -510,7 +510,7 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
|
||||
|
||||
fn insert_static(
|
||||
&mut self,
|
||||
sym: SymId,
|
||||
sym: StringId,
|
||||
span: TextRange,
|
||||
path: &[ast::Attr],
|
||||
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> {
|
||||
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)>,
|
||||
}
|
||||
|
||||
@@ -930,23 +930,23 @@ fn downgrade_attrpath<'id, 'ir>(
|
||||
|
||||
struct PatternBindings<'id, 'ir> {
|
||||
body: IrRef<'id, 'ir>,
|
||||
required: Vec<'ir, (SymId, TextRange)>,
|
||||
optional: Vec<'ir, (SymId, TextRange)>,
|
||||
required: Vec<'ir, (StringId, TextRange)>,
|
||||
optional: Vec<'ir, (StringId, TextRange)>,
|
||||
}
|
||||
|
||||
fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
|
||||
pat_entries: impl Iterator<Item = ast::PatEntry>,
|
||||
alias: Option<SymId>,
|
||||
alias: Option<StringId>,
|
||||
arg: ArgId,
|
||||
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>>
|
||||
where
|
||||
Ctx: DowngradeContext<'id, 'ir>,
|
||||
{
|
||||
let arg = ctx.new_expr(Ir::Arg(arg));
|
||||
struct Param {
|
||||
sym: SymId,
|
||||
sym: StringId,
|
||||
sym_span: TextRange,
|
||||
default: Option<ast::Expr>,
|
||||
span: TextRange,
|
||||
@@ -1045,7 +1045,7 @@ fn downgrade_let_bindings<'id, 'ir, Ctx, F>(
|
||||
) -> Result<IrRef<'id, 'ir>>
|
||||
where
|
||||
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| {
|
||||
body_fn(ctx, binding_keys)
|
||||
@@ -1081,7 +1081,7 @@ where
|
||||
Ctx: DowngradeContext<'id, 'ir>,
|
||||
F: FnOnce(
|
||||
&mut Ctx,
|
||||
&[SymId],
|
||||
&[StringId],
|
||||
&[(IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)],
|
||||
) -> Result<IrRef<'id, 'ir>>,
|
||||
{
|
||||
@@ -1106,7 +1106,7 @@ where
|
||||
fn collect_inherit_lookups<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
||||
entries: &[ast::Entry],
|
||||
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());
|
||||
for entry in entries {
|
||||
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>(
|
||||
pending: &PendingAttrSet,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<HashSet<SymId>> {
|
||||
) -> Result<HashSet<StringId>> {
|
||||
let mut binding_syms = HashSet::new();
|
||||
|
||||
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>(
|
||||
pending: PendingAttrSet,
|
||||
inherit_lookups: &HashMap<SymId, (IrRef<'id, 'ir>, TextRange)>,
|
||||
inherit_lookups: &HashMap<StringId, (IrRef<'id, 'ir>, TextRange)>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
||||
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>(
|
||||
value: PendingValue,
|
||||
inherit_lookups: &HashMap<SymId, (IrRef<'id, 'ir>, TextRange)>,
|
||||
inherit_lookups: &HashMap<StringId, (IrRef<'id, 'ir>, TextRange)>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<IrRef<'id, 'ir>> {
|
||||
match value {
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use deno_core::error::JsError;
|
||||
use deno_error::JsErrorClass as _;
|
||||
use itertools::Itertools as _;
|
||||
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::runtime::RuntimeContext;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Box<Error>>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -229,127 +224,3 @@ pub struct StackFrame {
|
||||
#[source_code]
|
||||
pub src: NamedSource<Arc<str>>,
|
||||
}
|
||||
|
||||
const MAX_STACK_FRAMES: usize = 20;
|
||||
const FRAMES_AT_START: usize = 15;
|
||||
const FRAMES_AT_END: usize = 5;
|
||||
|
||||
pub(crate) fn parse_js_error(error: Box<JsError>, ctx: &impl RuntimeContext) -> Error {
|
||||
let (span, src, frames) = if let Some(stack) = &error.stack {
|
||||
let mut frames = parse_frames(stack, ctx);
|
||||
|
||||
if let Some(last_frame) = frames.pop() {
|
||||
(
|
||||
Some(text_range_to_source_span(last_frame.span)),
|
||||
Some(last_frame.src.into()),
|
||||
frames,
|
||||
)
|
||||
} else {
|
||||
(None, None, frames)
|
||||
}
|
||||
} else {
|
||||
(None, None, Vec::new())
|
||||
};
|
||||
let stack_trace = if std::env::var("NIX_JS_STACK_TRACE").is_ok() {
|
||||
truncate_stack_trace(frames)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let message = error.get_message().to_string();
|
||||
let js_backtrace = error.stack.map(|stack| {
|
||||
stack
|
||||
.lines()
|
||||
.filter(|line| !line.starts_with("NIX_STACK_FRAME:"))
|
||||
.join("\n")
|
||||
});
|
||||
|
||||
Error::EvalError {
|
||||
src,
|
||||
span,
|
||||
message,
|
||||
js_backtrace,
|
||||
stack_trace,
|
||||
}
|
||||
}
|
||||
|
||||
struct NixStackFrame {
|
||||
span: rnix::TextRange,
|
||||
message: String,
|
||||
src: Source,
|
||||
}
|
||||
|
||||
impl From<NixStackFrame> for StackFrame {
|
||||
fn from(NixStackFrame { span, message, src }: NixStackFrame) -> Self {
|
||||
StackFrame {
|
||||
span: text_range_to_source_span(span),
|
||||
message,
|
||||
src: src.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_frames(stack: &str, ctx: &impl RuntimeContext) -> Vec<NixStackFrame> {
|
||||
let mut frames = Vec::new();
|
||||
|
||||
for line in stack.lines() {
|
||||
// Format: NIX_STACK_FRAME:span_id:message
|
||||
let Some(rest) = line.strip_prefix("NIX_STACK_FRAME:") else {
|
||||
continue;
|
||||
};
|
||||
let parts: Vec<&str> = rest.splitn(2, ':').collect();
|
||||
|
||||
if parts.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let span_id: usize = match parts[0].parse() {
|
||||
Ok(id) => id,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let (source_id, span) = ctx.get_span(span_id);
|
||||
let src = ctx.get_source(source_id);
|
||||
|
||||
let message = if parts.len() == 2 {
|
||||
parts[1].to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
frames.push(NixStackFrame { span, message, src });
|
||||
}
|
||||
|
||||
frames.dedup_by(|a, b| a.span == b.span && a.message == b.message);
|
||||
|
||||
frames
|
||||
}
|
||||
|
||||
fn truncate_stack_trace(frames: Vec<NixStackFrame>) -> Vec<StackFrame> {
|
||||
let reversed: Vec<_> = frames.into_iter().rev().collect();
|
||||
let total = reversed.len();
|
||||
|
||||
if total <= MAX_STACK_FRAMES {
|
||||
return reversed.into_iter().map(Into::into).collect();
|
||||
}
|
||||
|
||||
let omitted_count = total - FRAMES_AT_START - FRAMES_AT_END;
|
||||
|
||||
reversed
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, frame)| {
|
||||
if i < FRAMES_AT_START {
|
||||
Some(frame.into())
|
||||
} else if i == FRAMES_AT_START {
|
||||
Some(StackFrame {
|
||||
span: text_range_to_source_span(frame.span),
|
||||
message: format!("... ({} more frames omitted)", omitted_count),
|
||||
src: frame.src.into(),
|
||||
})
|
||||
} else if i >= total - FRAMES_AT_END {
|
||||
Some(frame.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -5,8 +5,6 @@ use nix_compat::nixhash::HashAlgo;
|
||||
use nix_compat::nixhash::NixHash;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use crate::runtime::OpStateExt;
|
||||
use crate::runtime::RuntimeContext;
|
||||
use crate::store::Store as _;
|
||||
|
||||
mod archive;
|
||||
@@ -20,7 +18,6 @@ pub use download::Downloader;
|
||||
pub use metadata_cache::MetadataCache;
|
||||
|
||||
use crate::nar;
|
||||
use crate::runtime::NixRuntimeError;
|
||||
|
||||
#[derive(ToV8)]
|
||||
pub struct FetchUrlResult {
|
||||
@@ -306,11 +303,3 @@ fn normalize_hash(hash: &str) -> String {
|
||||
}
|
||||
hash.to_string()
|
||||
}
|
||||
|
||||
pub fn register_ops<Ctx: RuntimeContext>() -> Vec<deno_core::OpDecl> {
|
||||
vec![
|
||||
op_fetch_url::<Ctx>(),
|
||||
op_fetch_tarball::<Ctx>(),
|
||||
op_fetch_git::<Ctx>(),
|
||||
]
|
||||
}
|
||||
File renamed without changes.
@@ -10,7 +10,7 @@ impl FetcherCache {
|
||||
pub fn new() -> Result<Self, std::io::Error> {
|
||||
let base_dir = dirs::cache_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("/tmp"))
|
||||
.join("nix-js")
|
||||
.join("fix")
|
||||
.join("fetchers");
|
||||
|
||||
fs::create_dir_all(&base_dir)?;
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -4,6 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use bumpalo::{Bump, boxed::Box, collections::Vec};
|
||||
use gc_arena::Collect;
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use rnix::{TextRange, ast};
|
||||
use string_interner::symbol::SymbolU32;
|
||||
@@ -23,6 +24,10 @@ impl<'id, 'ir> IrRef<'id, '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
|
||||
/// `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)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>);
|
||||
@@ -68,7 +66,7 @@ pub enum Ir<'ir, Ref> {
|
||||
Null,
|
||||
Str(Box<'ir, String>),
|
||||
AttrSet {
|
||||
stcs: HashMap<'ir, SymId, (Ref, TextRange)>,
|
||||
stcs: HashMap<'ir, StringId, (Ref, TextRange)>,
|
||||
dyns: Vec<'ir, (Ref, Ref, TextRange)>,
|
||||
},
|
||||
List {
|
||||
@@ -119,7 +117,7 @@ pub enum Ir<'ir, Ref> {
|
||||
body: Ref,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
},
|
||||
WithLookup(SymId),
|
||||
WithLookup(StringId),
|
||||
|
||||
// Function related
|
||||
Func {
|
||||
@@ -137,7 +135,7 @@ pub enum Ir<'ir, Ref> {
|
||||
|
||||
// Builtins
|
||||
Builtins,
|
||||
Builtin(SymId),
|
||||
Builtin(StringId),
|
||||
|
||||
// Misc
|
||||
TopLevel {
|
||||
@@ -146,19 +144,26 @@ pub enum Ir<'ir, Ref> {
|
||||
},
|
||||
Thunk(ThunkId),
|
||||
CurPos(TextRange),
|
||||
ReplBinding(SymId),
|
||||
ScopedImportBinding(SymId),
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
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)]
|
||||
#[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.
|
||||
#[allow(unused)]
|
||||
@@ -169,7 +174,7 @@ pub enum Attr<Ref> {
|
||||
Dynamic(Ref, TextRange),
|
||||
/// A static attribute key.
|
||||
/// Example: `attrs.key`
|
||||
Str(SymId, TextRange),
|
||||
Str(StringId, TextRange),
|
||||
}
|
||||
|
||||
/// The kinds of binary operations supported in Nix.
|
||||
@@ -248,8 +253,8 @@ impl From<ast::UnaryOpKind> for UnOpKind {
|
||||
/// Describes the parameters of a function.
|
||||
#[derive(Debug)]
|
||||
pub struct Param<'ir> {
|
||||
pub required: Vec<'ir, (SymId, TextRange)>,
|
||||
pub optional: Vec<'ir, (SymId, TextRange)>,
|
||||
pub required: Vec<'ir, (StringId, TextRange)>,
|
||||
pub optional: Vec<'ir, (StringId, TextRange)>,
|
||||
pub ellipsis: bool,
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
pub mod logging;
|
||||
pub mod runtime;
|
||||
pub mod value;
|
||||
|
||||
mod bytecode;
|
||||
mod codegen;
|
||||
mod derivation;
|
||||
mod disassembler;
|
||||
mod downgrade;
|
||||
mod fetcher;
|
||||
// mod fetcher;
|
||||
mod ir;
|
||||
mod nar;
|
||||
mod nix_utils;
|
||||
mod runtime;
|
||||
mod store;
|
||||
mod string_context;
|
||||
|
||||
File renamed without changes.
@@ -3,23 +3,15 @@ use std::process::exit;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use fix::error::Source;
|
||||
use fix::runtime::Runtime;
|
||||
use hashbrown::HashSet;
|
||||
use nix_js::context::Context;
|
||||
use nix_js::error::Source;
|
||||
use rustyline::DefaultEditor;
|
||||
use rustyline::error::ReadlineError;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "nix-js", about = "Nix expression evaluator")]
|
||||
struct Cli {
|
||||
#[cfg(feature = "inspector")]
|
||||
#[arg(long, value_name = "HOST:PORT", num_args = 0..=1, default_missing_value = "127.0.0.1:9229")]
|
||||
inspect: Option<String>,
|
||||
|
||||
#[cfg(feature = "inspector")]
|
||||
#[arg(long, value_name = "HOST:PORT", num_args = 0..=1, default_missing_value = "127.0.0.1:9229")]
|
||||
inspect_brk: Option<String>,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
@@ -48,28 +40,7 @@ struct ExprSource {
|
||||
file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> {
|
||||
#[cfg(feature = "inspector")]
|
||||
{
|
||||
let (addr_str, wait) = if let Some(ref addr) = cli.inspect_brk {
|
||||
(Some(addr.as_str()), true)
|
||||
} else if let Some(ref addr) = cli.inspect {
|
||||
(Some(addr.as_str()), false)
|
||||
} else {
|
||||
(None, false)
|
||||
};
|
||||
|
||||
if let Some(addr_str) = addr_str {
|
||||
let addr: std::net::SocketAddr = addr_str
|
||||
.parse()
|
||||
.map_err(|e| anyhow::anyhow!("invalid inspector address '{}': {}", addr_str, e))?;
|
||||
return Ok(Context::new_with_inspector(addr, wait)?);
|
||||
}
|
||||
}
|
||||
Ok(Context::new()?)
|
||||
}
|
||||
|
||||
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 {
|
||||
Source::new_eval(expr)?
|
||||
} else if let Some(file) = src.file {
|
||||
@@ -77,10 +48,11 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
match context.compile_bytecode(src) {
|
||||
todo!()
|
||||
/* match runtime.compile_bytecode(src) {
|
||||
Ok(compiled) => {
|
||||
if !silent {
|
||||
println!("{}", context.disassemble_colored(&compiled));
|
||||
println!("{}", runtime.disassemble_colored(&compiled));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -88,12 +60,10 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "inspector")]
|
||||
context.wait_for_inspector_disconnect();
|
||||
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 {
|
||||
Source::new_eval(expr)?
|
||||
} else if let Some(file) = src.file {
|
||||
@@ -101,7 +71,7 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
match context.eval_deep(src) {
|
||||
match runtime.eval_deep(src) {
|
||||
Ok(value) => {
|
||||
println!("{}", value.display_compat());
|
||||
}
|
||||
@@ -110,12 +80,10 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "inspector")]
|
||||
context.wait_for_inspector_disconnect();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_repl(context: &mut Context) -> Result<()> {
|
||||
fn run_repl(runtime: &mut Runtime) -> Result<()> {
|
||||
let mut rl = DefaultEditor::new()?;
|
||||
let mut scope = HashSet::new();
|
||||
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
||||
@@ -134,20 +102,20 @@ fn run_repl(context: &mut Context) -> Result<()> {
|
||||
eprintln!("Error: missing expression after '='");
|
||||
continue;
|
||||
}
|
||||
match context.add_binding(ident, expr, &mut scope) {
|
||||
match runtime.add_binding(ident, expr, &mut scope) {
|
||||
Ok(value) => println!("{} = {}", ident, value),
|
||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||
}
|
||||
} else {
|
||||
let src = Source::new_repl(line)?;
|
||||
match context.eval_repl(src, &scope) {
|
||||
match runtime.eval_repl(src, &scope) {
|
||||
Ok(value) => println!("{value}"),
|
||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let src = Source::new_repl(line)?;
|
||||
match context.eval_shallow(src) {
|
||||
match runtime.eval_repl(src, &scope) {
|
||||
Ok(value) => println!("{value}"),
|
||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||
}
|
||||
@@ -170,18 +138,15 @@ fn run_repl(context: &mut Context) -> Result<()> {
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
nix_js::logging::init_logging();
|
||||
fix::logging::init_logging();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut context = create_context(
|
||||
#[cfg(feature = "inspector")]
|
||||
&cli,
|
||||
)?;
|
||||
let mut runtime = Runtime::new()?;
|
||||
|
||||
match cli.command {
|
||||
Command::Compile { source, silent } => run_compile(&mut context, source, silent),
|
||||
Command::Eval { source } => run_eval(&mut context, source),
|
||||
Command::Repl => run_repl(&mut context),
|
||||
Command::Compile { source, silent } => run_compile(&mut runtime, source, silent),
|
||||
Command::Eval { source } => run_eval(&mut runtime, source),
|
||||
Command::Repl => run_repl(&mut runtime),
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +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 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
use std::ops::Deref;
|
||||
|
||||
use boxing::nan::raw::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||
use gc_arena::{Collect, Gc, Mutation, RefLock, collect::Trace};
|
||||
use sealed::sealed;
|
||||
use smallvec::SmallVec;
|
||||
use string_interner::{Symbol, symbol::SymbolU32};
|
||||
|
||||
use crate::ir::StringId;
|
||||
|
||||
#[sealed]
|
||||
pub(crate) trait Storable {
|
||||
const TAG: (bool, u8);
|
||||
}
|
||||
pub(crate) trait InlineStorable: Storable + RawStore {}
|
||||
pub(crate) trait GcStorable: Storable {}
|
||||
|
||||
macro_rules! define_value_types {
|
||||
(
|
||||
inline { $($itype:ty => $itag:expr, $iname:literal;)* }
|
||||
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* }
|
||||
) => {
|
||||
$(
|
||||
#[sealed]
|
||||
impl Storable for $itype {
|
||||
const TAG: (bool, u8) = $itag;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
unsafe impl<'gc> Collect<'gc> for Value<'gc> {
|
||||
const NEEDS_TRACE: bool = true;
|
||||
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
|
||||
let Some(tag) = self.raw.tag() else { return };
|
||||
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})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_value_types! {
|
||||
inline {
|
||||
i32 => (false, 1), "SmallInt";
|
||||
bool => (false, 2), "Bool";
|
||||
Null => (false, 3), "Null";
|
||||
StringId => (false, 4), "SmallString";
|
||||
PrimOp => (false, 5), "PrimOp";
|
||||
}
|
||||
gc {
|
||||
i64 => (false, 6), "BigInt";
|
||||
NixString => (false, 7), "String";
|
||||
AttrSet<'_> => (true, 1), "AttrSet";
|
||||
List<'_> => (true, 2), "List";
|
||||
Thunk<'_> => (true, 3), "Thunk";
|
||||
Closure<'_> => (true, 4), "Closure";
|
||||
PrimOpApp<'_> => (true, 5), "PrimOpApp";
|
||||
}
|
||||
}
|
||||
|
||||
/// # Nix runtime value representation
|
||||
///
|
||||
/// NaN-boxed value fitting in 8 bytes.
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct Value<'gc> {
|
||||
raw: RawBox,
|
||||
_marker: PhantomData<Gc<'gc, ()>>,
|
||||
}
|
||||
|
||||
impl Clone for Value<'_> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
raw: self.raw.clone(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Value<'_> {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::new_inline(Null)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline(always)]
|
||||
fn mk_tag(neg: bool, val: u8) -> RawTag {
|
||||
// Safety: val is asserted to be in 1..=7.
|
||||
unsafe { RawTag::new_unchecked(neg, val) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn from_raw_value(rv: RawValue) -> Self {
|
||||
Self {
|
||||
raw: RawBox::from_value(rv),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a GC pointer from a value with a negative tag.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The value must actually store a `Gc<'gc, T>` with the matching type.
|
||||
#[inline(always)]
|
||||
unsafe fn load_gc<T: GcStorable>(&self) -> Gc<'gc, T> {
|
||||
unsafe {
|
||||
let rv = self.raw.value().unwrap_unchecked();
|
||||
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
||||
Gc::from_ptr(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `(negative, val)` tag, or `None` for a float.
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> Option<(bool, u8)> {
|
||||
self.raw.tag().map(|t| t.neg_val())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline]
|
||||
pub(crate) fn new_float(val: f64) -> Self {
|
||||
Self {
|
||||
raw: RawBox::from_float(val),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
|
||||
Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), val))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
|
||||
let ptr = Gc::as_ptr(gc);
|
||||
Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), ptr))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline]
|
||||
pub(crate) fn is_float(&self) -> bool {
|
||||
self.raw.is_float()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is<T: Storable>(&self) -> bool {
|
||||
self.tag() == Some(T::TAG)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline]
|
||||
pub(crate) fn as_float(&self) -> Option<f64> {
|
||||
self.raw.float().copied()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_inline<T: InlineStorable>(&self) -> Option<T> {
|
||||
if self.is::<T>() {
|
||||
Some(unsafe {
|
||||
let rv = self.raw.value().unwrap_unchecked();
|
||||
T::from_val(rv)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_gc<T: GcStorable>(&self) -> Option<Gc<'gc, T>> {
|
||||
if self.is::<T>() {
|
||||
Some(unsafe {
|
||||
let rv = self.raw.value().unwrap_unchecked();
|
||||
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
||||
Gc::from_ptr(ptr)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct Null;
|
||||
impl RawStore for Null {
|
||||
fn to_val(self, value: &mut RawValue) {
|
||||
value.set_data([0; 6]);
|
||||
}
|
||||
fn from_val(_: &RawValue) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl RawStore for StringId {
|
||||
fn to_val(self, value: &mut RawValue) {
|
||||
(self.0.to_usize() as u32).to_val(value);
|
||||
}
|
||||
fn from_val(value: &RawValue) -> Self {
|
||||
Self(
|
||||
SymbolU32::try_from_usize(u32::from_val(value) as usize)
|
||||
.expect("failed to read StringId from Value"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Heap-allocated Nix string.
|
||||
///
|
||||
/// Stored on the GC heap via `Gc<'gc, NixString>`. The string data itself
|
||||
/// lives in a standard `Box<str>` owned by this struct; the GC only manages
|
||||
/// the outer allocation.
|
||||
#[derive(Collect)]
|
||||
#[collect(require_static)]
|
||||
pub(crate) struct NixString {
|
||||
data: Box<str>,
|
||||
// TODO: string context for derivation dependency tracking
|
||||
}
|
||||
|
||||
impl NixString {
|
||||
pub(crate) fn new(s: impl Into<Box<str>>) -> Self {
|
||||
Self { data: s.into() }
|
||||
}
|
||||
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NixString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.data, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct AttrSet<'gc> {
|
||||
pub(crate) entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
||||
}
|
||||
|
||||
impl<'gc> AttrSet<'gc> {
|
||||
pub(crate) fn from_sorted(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self {
|
||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||
Self { entries }
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||
self.entries
|
||||
.binary_search_by_key(&key, |(k, _)| *k)
|
||||
.ok()
|
||||
.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, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct List<'gc> {
|
||||
pub(crate) inner: SmallVec<[Value<'gc>; 4]>,
|
||||
}
|
||||
|
||||
pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) enum ThunkState<'gc> {
|
||||
Pending {
|
||||
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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
use nix_js::value::Value;
|
||||
use fix::value::Value;
|
||||
|
||||
use crate::utils::{eval, eval_result};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use nix_js::value::{AttrSet, List, Value};
|
||||
use fix::value::{AttrSet, List, Value};
|
||||
|
||||
use crate::utils::eval;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use nix_js::value::Value;
|
||||
use fix::value::Value;
|
||||
|
||||
use crate::utils::eval_result;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use nix_js::value::Value;
|
||||
use fix::value::Value;
|
||||
|
||||
use crate::utils::{eval_deep, eval_deep_result};
|
||||
|
||||
File renamed without changes.
@@ -1,4 +1,4 @@
|
||||
use nix_js::value::{List, Value};
|
||||
use fix::value::{List, Value};
|
||||
|
||||
use crate::utils::{eval, eval_result};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use nix_js::value::Value;
|
||||
use fix::value::Value;
|
||||
|
||||
use crate::utils::{eval, eval_result};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use nix_js::context::Context;
|
||||
use nix_js::error::Source;
|
||||
use nix_js::value::Value;
|
||||
use fix::error::Source;
|
||||
use fix::runtime::Runtime;
|
||||
use fix::value::Value;
|
||||
|
||||
use crate::utils::{eval, eval_result};
|
||||
|
||||
@@ -97,7 +97,7 @@ fn import_with_complex_dependency_graph() {
|
||||
|
||||
#[test_log::test]
|
||||
fn path_with_file() {
|
||||
let mut ctx = Context::new().unwrap();
|
||||
let mut ctx = Runtime::new().unwrap();
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let test_file = temp_dir.path().join("test.txt");
|
||||
std::fs::write(&test_file, "Hello, World!").unwrap();
|
||||
@@ -107,7 +107,7 @@ fn path_with_file() {
|
||||
|
||||
// Should return a store path string
|
||||
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"));
|
||||
} else {
|
||||
panic!("Expected string, got {:?}", result);
|
||||
@@ -136,7 +136,7 @@ fn path_with_custom_name() {
|
||||
|
||||
#[test_log::test]
|
||||
fn path_with_directory_recursive() {
|
||||
let mut ctx = Context::new().unwrap();
|
||||
let mut ctx = Runtime::new().unwrap();
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let test_dir = temp_dir.path().join("mydir");
|
||||
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();
|
||||
|
||||
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"));
|
||||
} else {
|
||||
panic!("Expected string, got {:?}", result);
|
||||
@@ -159,7 +159,7 @@ fn path_with_directory_recursive() {
|
||||
|
||||
#[test_log::test]
|
||||
fn path_flat_with_file() {
|
||||
let mut ctx = Context::new().unwrap();
|
||||
let mut ctx = Runtime::new().unwrap();
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let test_file = temp_dir.path().join("flat.txt");
|
||||
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();
|
||||
|
||||
if let Value::String(store_path) = result {
|
||||
assert!(store_path.starts_with(ctx.get_store_dir()));
|
||||
assert!(store_path.starts_with("/nix/store"));
|
||||
} else {
|
||||
panic!("Expected string, got {:?}", result);
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nix_js::context::Context;
|
||||
use nix_js::error::Source;
|
||||
use nix_js::value::Value;
|
||||
use fix::error::Source;
|
||||
use fix::runtime::Runtime;
|
||||
use fix::value::Value;
|
||||
|
||||
fn get_lang_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
|
||||
@@ -16,9 +16,9 @@ fn eval_file(name: &str) -> Result<(Value, Source), String> {
|
||||
|
||||
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 {
|
||||
ty: nix_js::error::SourceType::File(nix_path.into()),
|
||||
ty: fix::error::SourceType::File(nix_path.into()),
|
||||
src: expr.into(),
|
||||
};
|
||||
ctx.eval_deep(source.clone())
|
||||
@@ -247,7 +247,7 @@ eval_fail_test!(fail_abort);
|
||||
eval_fail_test!(fail_addDrvOutputDependencies_empty_context);
|
||||
eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context);
|
||||
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_equal_attrs_names);
|
||||
eval_fail_test!(fail_assert_equal_attrs_names_2);
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 522 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user