Compare commits

..

3 Commits

Author SHA1 Message Date
imxyy1soope1 198d847151 feat: init Rust VM 2026-03-15 17:53:19 +08:00
imxyy1soope1 40d00a6c47 feat: value 2026-03-14 10:28:43 +08:00
imxyy1soope1 0c9a391618 clean up 2026-03-14 10:28:43 +08:00
519 changed files with 3964 additions and 13709 deletions
-3
View File
@@ -16,9 +16,6 @@ vim.lsp.config("rust_analyzer", {
settings = { settings = {
["rust-analyzer"] = { ["rust-analyzer"] = {
cargo = { cargo = {
features = {
"inspector"
}
} }
} }
} }
Generated
+101 -969
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,7 +1,8 @@
[workspace] [workspace]
resolver = "3" resolver = "3"
members = [ members = [
"nix-js" "fix",
"boxing",
] ]
[profile.profiling] [profile.profiling]
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "boxing"
version = "0.1.3"
edition = "2021"
description = "NaN-boxing primitives (local fork with bool fix)"
[dependencies]
sptr = "0.3"
+2
View File
@@ -0,0 +1,2 @@
pub mod nan;
mod utils;
+7
View File
@@ -0,0 +1,7 @@
pub mod raw;
pub use raw::RawBox;
const SIGN_MASK: u64 = 0x7FFF_FFFF_FFFF_FFFF;
const QUIET_NAN: u64 = 0x7FF8_0000_0000_0000;
const NEG_QUIET_NAN: u64 = 0xFFF8_0000_0000_0000;
+471
View File
@@ -0,0 +1,471 @@
use super::{NEG_QUIET_NAN, QUIET_NAN, SIGN_MASK};
use crate::utils::ArrayExt;
use sptr::Strict;
use std::fmt;
use std::mem::ManuallyDrop;
use std::num::NonZeroU8;
pub trait RawStore: Sized {
fn to_val(self, value: &mut Value);
fn from_val(value: &Value) -> Self;
}
impl RawStore for [u8; 6] {
#[inline]
fn to_val(self, value: &mut Value) {
value.set_data(self);
}
#[inline]
fn from_val(value: &Value) -> Self {
*value.data()
}
}
impl RawStore for bool {
#[inline]
fn to_val(self, value: &mut Value) {
value.set_data([u8::from(self)].truncate_to());
}
#[inline]
fn from_val(value: &Value) -> Self {
value.data()[0] == 1
}
}
macro_rules! int_store {
($ty:ty) => {
impl RawStore for $ty {
#[inline]
fn to_val(self, value: &mut Value) {
let bytes = self.to_ne_bytes();
value.set_data(bytes.truncate_to());
}
#[inline]
fn from_val(value: &Value) -> Self {
<$ty>::from_ne_bytes(value.data().truncate_to())
}
}
};
}
int_store!(u8);
int_store!(u16);
int_store!(u32);
int_store!(i8);
int_store!(i16);
int_store!(i32);
fn store_ptr<P: Strict + Copy>(value: &mut Value, ptr: P) {
#[cfg(target_pointer_width = "64")]
{
assert!(
ptr.addr() <= 0x0000_FFFF_FFFF_FFFF,
"Pointer too large to store in NaN box"
);
let val = (unsafe { value.whole_mut() } as *mut [u8; 8]).cast::<P>();
let ptr = Strict::map_addr(ptr, |addr| {
addr | (usize::from(value.header().into_raw()) << 48)
});
unsafe { val.write(ptr) };
}
#[cfg(target_pointer_width = "32")]
{
let _ = (value, ptr);
unimplemented!("32-bit pointer storage not supported");
}
}
fn load_ptr<P: Strict>(value: &Value) -> P {
#[cfg(target_pointer_width = "64")]
{
let val = (unsafe { value.whole() } as *const [u8; 8]).cast::<P>();
let ptr = unsafe { val.read() };
Strict::map_addr(ptr, |addr| addr & 0x0000_FFFF_FFFF_FFFF)
}
#[cfg(target_pointer_width = "32")]
{
let _ = value;
unimplemented!("32-bit pointer storage not supported");
}
}
impl<T> RawStore for *const T {
fn to_val(self, value: &mut Value) {
store_ptr::<*const T>(value, self);
}
fn from_val(value: &Value) -> Self {
load_ptr::<*const T>(value)
}
}
impl<T> RawStore for *mut T {
fn to_val(self, value: &mut Value) {
store_ptr::<*mut T>(value, self);
}
fn from_val(value: &Value) -> Self {
load_ptr::<*mut T>(value)
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum TagVal {
_P1,
_P2,
_P3,
_P4,
_P5,
_P6,
_P7,
_N1,
_N2,
_N3,
_N4,
_N5,
_N6,
_N7,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct RawTag(TagVal);
impl RawTag {
#[inline]
#[must_use]
pub fn new(neg: bool, val: NonZeroU8) -> RawTag {
unsafe { Self::new_unchecked(neg, val.get() & 0x07) }
}
#[inline]
#[must_use]
pub fn new_checked(neg: bool, val: u8) -> Option<RawTag> {
Some(RawTag(match (neg, val) {
(false, 1) => TagVal::_P1,
(false, 2) => TagVal::_P2,
(false, 3) => TagVal::_P3,
(false, 4) => TagVal::_P4,
(false, 5) => TagVal::_P5,
(false, 6) => TagVal::_P6,
(false, 7) => TagVal::_P7,
(true, 1) => TagVal::_N1,
(true, 2) => TagVal::_N2,
(true, 3) => TagVal::_N3,
(true, 4) => TagVal::_N4,
(true, 5) => TagVal::_N5,
(true, 6) => TagVal::_N6,
(true, 7) => TagVal::_N7,
_ => return None,
}))
}
/// # Safety
///
/// `val` must be in the range `1..8`
#[inline]
#[must_use]
pub unsafe fn new_unchecked(neg: bool, val: u8) -> RawTag {
RawTag(match (neg, val) {
(false, 1) => TagVal::_P1,
(false, 2) => TagVal::_P2,
(false, 3) => TagVal::_P3,
(false, 4) => TagVal::_P4,
(false, 5) => TagVal::_P5,
(false, 6) => TagVal::_P6,
(false, 7) => TagVal::_P7,
(true, 1) => TagVal::_N1,
(true, 2) => TagVal::_N2,
(true, 3) => TagVal::_N3,
(true, 4) => TagVal::_N4,
(true, 5) => TagVal::_N5,
(true, 6) => TagVal::_N6,
(true, 7) => TagVal::_N7,
_ => unsafe { core::hint::unreachable_unchecked() },
})
}
#[inline]
#[must_use]
pub fn is_neg(self) -> bool {
matches!(self.0, |TagVal::_N1| TagVal::_N2
| TagVal::_N3
| TagVal::_N4
| TagVal::_N5
| TagVal::_N6
| TagVal::_N7)
}
#[inline]
#[must_use]
pub fn val(self) -> NonZeroU8 {
match self.0 {
TagVal::_P1 | TagVal::_N1 => NonZeroU8::MIN,
TagVal::_P2 | TagVal::_N2 => NonZeroU8::MIN.saturating_add(1),
TagVal::_P3 | TagVal::_N3 => NonZeroU8::MIN.saturating_add(2),
TagVal::_P4 | TagVal::_N4 => NonZeroU8::MIN.saturating_add(3),
TagVal::_P5 | TagVal::_N5 => NonZeroU8::MIN.saturating_add(4),
TagVal::_P6 | TagVal::_N6 => NonZeroU8::MIN.saturating_add(5),
TagVal::_P7 | TagVal::_N7 => NonZeroU8::MIN.saturating_add(6),
}
}
#[inline]
#[must_use]
pub fn neg_val(self) -> (bool, u8) {
match self.0 {
TagVal::_P1 => (false, 1),
TagVal::_P2 => (false, 2),
TagVal::_P3 => (false, 3),
TagVal::_P4 => (false, 4),
TagVal::_P5 => (false, 5),
TagVal::_P6 => (false, 6),
TagVal::_P7 => (false, 7),
TagVal::_N1 => (true, 1),
TagVal::_N2 => (true, 2),
TagVal::_N3 => (true, 3),
TagVal::_N4 => (true, 4),
TagVal::_N5 => (true, 5),
TagVal::_N6 => (true, 6),
TagVal::_N7 => (true, 7),
}
}
}
impl fmt::Debug for RawTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawTag")
.field("neg", &self.is_neg())
.field("val", &self.val())
.finish()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(transparent)]
struct Header(u16);
impl Header {
#[inline]
fn new(tag: RawTag) -> Header {
let (neg, val) = tag.neg_val();
Header(0x7FF8 | (u16::from(neg) << 15) | u16::from(val))
}
#[inline]
fn tag(self) -> RawTag {
unsafe { RawTag::new_unchecked(self.get_sign(), self.get_tag()) }
}
#[inline]
fn get_sign(self) -> bool {
self.0 & 0x8000 != 0
}
#[inline]
fn get_tag(self) -> u8 {
(self.0 & 0x0007) as u8
}
#[inline]
fn into_raw(self) -> u16 {
self.0
}
}
#[derive(Clone, Debug, PartialEq)]
#[repr(C, align(8))]
pub struct Value {
#[cfg(target_endian = "big")]
header: Header,
data: [u8; 6],
#[cfg(target_endian = "little")]
header: Header,
}
impl Value {
#[inline]
pub fn new(tag: RawTag, data: [u8; 6]) -> Value {
Value {
header: Header::new(tag),
data,
}
}
#[inline]
pub fn empty(tag: RawTag) -> Value {
Value::new(tag, [0; 6])
}
pub fn store<T: RawStore>(tag: RawTag, val: T) -> Value {
let mut v = Value::new(tag, [0; 6]);
T::to_val(val, &mut v);
v
}
pub fn load<T: RawStore>(self) -> T {
T::from_val(&self)
}
#[inline]
#[must_use]
pub fn tag(&self) -> RawTag {
self.header.tag()
}
#[inline]
fn header(&self) -> &Header {
&self.header
}
#[inline]
pub fn set_data(&mut self, val: [u8; 6]) {
self.data = val;
}
#[inline]
#[must_use]
pub fn data(&self) -> &[u8; 6] {
&self.data
}
#[inline]
#[must_use]
pub fn data_mut(&mut self) -> &mut [u8; 6] {
&mut self.data
}
#[inline]
#[must_use]
pub unsafe fn whole(&self) -> &[u8; 8] {
let ptr = (self as *const Value).cast::<[u8; 8]>();
unsafe { &*ptr }
}
#[inline]
#[must_use]
pub unsafe fn whole_mut(&mut self) -> &mut [u8; 8] {
let ptr = (self as *mut Value).cast::<[u8; 8]>();
unsafe { &mut *ptr }
}
}
#[repr(C)]
pub union RawBox {
float: f64,
value: ManuallyDrop<Value>,
bits: u64,
#[cfg(target_pointer_width = "64")]
ptr: *const (),
#[cfg(target_pointer_width = "32")]
ptr: (u32, *const ()),
}
impl RawBox {
#[inline]
#[must_use]
pub fn from_float(val: f64) -> RawBox {
match (val.is_nan(), val.is_sign_positive()) {
(true, true) => RawBox {
float: f64::from_bits(QUIET_NAN),
},
(true, false) => RawBox {
float: f64::from_bits(NEG_QUIET_NAN),
},
(false, _) => RawBox { float: val },
}
}
#[inline]
#[must_use]
pub fn from_value(value: Value) -> RawBox {
RawBox {
value: ManuallyDrop::new(value),
}
}
#[inline]
#[must_use]
pub fn tag(&self) -> Option<RawTag> {
if self.is_value() {
Some(unsafe { self.value.tag() })
} else {
None
}
}
#[inline]
#[must_use]
pub fn is_float(&self) -> bool {
(unsafe { !self.float.is_nan() } || unsafe { self.bits & SIGN_MASK == QUIET_NAN })
}
#[inline]
#[must_use]
pub fn is_value(&self) -> bool {
(unsafe { self.float.is_nan() } && unsafe { self.bits & SIGN_MASK != QUIET_NAN })
}
#[inline]
#[must_use]
pub fn float(&self) -> Option<&f64> {
if self.is_float() {
Some(unsafe { &self.float })
} else {
None
}
}
#[inline]
#[must_use]
pub fn value(&self) -> Option<&Value> {
if self.is_value() {
Some(unsafe { &self.value })
} else {
None
}
}
#[inline]
pub fn into_float_unchecked(self) -> f64 {
unsafe { self.float }
}
}
impl Clone for RawBox {
#[inline]
fn clone(&self) -> Self {
RawBox {
ptr: unsafe { self.ptr },
}
}
}
impl fmt::Debug for RawBox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.float() {
Some(val) => f.debug_tuple("RawBox::Float").field(val).finish(),
None => {
let val = self.value().expect("RawBox is neither float nor value");
f.debug_struct("RawBox::Data")
.field("tag", &val.tag())
.field("data", val.data())
.finish()
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
pub trait ArrayExt<const LEN: usize> {
type Elem;
fn truncate_to<const M: usize>(self) -> [Self::Elem; M];
}
impl<T: Default + Copy, const N: usize> ArrayExt<N> for [T; N] {
type Elem = T;
fn truncate_to<const M: usize>(self) -> [Self::Elem; M] {
let copy_len = usize::min(N, M);
let mut out = [T::default(); M];
out[0..copy_len].copy_from_slice(&self[0..copy_len]);
out
}
}
+29 -28
View File
@@ -1,13 +1,21 @@
[package] [package]
name = "nix-js" name = "fix"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
mimalloc = "0.1" mimalloc = "0.1"
tokio = { version = "1.41", features = ["rt-multi-thread", "sync", "net", "io-util"] } tokio = { version = "1.41", features = [
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = ["wire", "async"] } "rt-multi-thread",
"sync",
"net",
"io-util",
] }
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = [
"wire",
"async",
] }
# REPL # REPL
anyhow = "1.0" anyhow = "1.0"
@@ -26,17 +34,18 @@ miette = { version = "7.4", features = ["fancy"] }
hashbrown = "0.16" hashbrown = "0.16"
string-interner = "0.19" string-interner = "0.19"
bumpalo = { version = "3.20", features = ["allocator-api2", "boxed", "collections"] } bumpalo = { version = "3.20", features = [
"allocator-api2",
"boxed",
"collections",
] }
rust-embed="8.11" rust-embed = "8.11"
itertools = "0.14" itertools = "0.14"
regex = "1.11" regex = "1.11"
deno_core = "0.385"
deno_error = "0.7"
nix-nar = "0.3" nix-nar = "0.3"
sha2 = "0.10" sha2 = "0.10"
sha1 = "0.10" sha1 = "0.10"
@@ -45,7 +54,10 @@ hex = "0.4"
base64 = "0.22" base64 = "0.22"
reqwest = { version = "0.13", features = ["blocking", "rustls"], default-features = false } reqwest = { version = "0.13", features = [
"blocking",
"rustls",
], default-features = false }
tar = "0.4" tar = "0.4"
flate2 = "1.0" flate2 = "1.0"
xz2 = "0.1" xz2 = "0.1"
@@ -65,24 +77,17 @@ ere = "0.2.4"
num_enum = "0.7.5" num_enum = "0.7.5"
tap = "1.0.1" 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" ghost-cell = "0.2"
colored = "3.1" colored = "3.1"
boxing = "0.1" boxing = { path = "../boxing" }
gc-arena = { version = "0.5.3", features = ["allocator-api2"] } sealed = "0.6"
allocator-api2 = "0.4.0" small-map = "0.1"
smallvec = "1.15.1" smallvec = "1.15"
[features] [dependencies.gc-arena]
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"] git = "https://github.com/kyren/gc-arena"
prof = [] rev = "75671ae03f53718357b741ed4027560f14e90836"
features = ["allocator-api2", "hashbrown", "smallvec"]
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] } criterion = { version = "0.8", features = ["html_reports"] }
@@ -99,7 +104,3 @@ harness = false
[[bench]] [[bench]]
name = "thunk_scope" name = "thunk_scope"
harness = false harness = false
[[bench]]
name = "compile_time"
harness = false
+18
View File
@@ -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())
}
+154 -193
View File
@@ -1,101 +1,101 @@
use std::ops::Deref; use std::ops::Deref;
use std::path::Path;
use gc_arena::Collect;
use hashbrown::HashMap; use hashbrown::HashMap;
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use rnix::TextRange; use rnix::TextRange;
use string_interner::Symbol as _;
use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, SymId, ThunkId, UnOpKind}; use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind};
#[derive(Clone, Hash, Eq, PartialEq)] pub(crate) struct InstructionPtr(pub usize);
pub(crate) enum Constant {
Int(i64),
Float(u64),
}
#[derive(Collect)]
#[collect(require_static)]
pub struct Bytecode { pub struct Bytecode {
pub code: Box<[u8]>, pub code: Box<[u8]>,
pub current_dir: String, pub current_dir: String,
} }
pub(crate) trait BytecodeContext { pub(crate) trait BytecodeContext {
fn intern_string(&mut self, s: &str) -> u32; fn intern_string(&mut self, s: &str) -> StringId;
fn intern_constant(&mut self, c: Constant) -> u32; fn register_span(&mut self, range: TextRange) -> u32;
fn register_span(&self, range: TextRange) -> u32; fn get_code(&self) -> &[u8];
fn get_sym(&self, id: SymId) -> &str; fn get_code_mut(&mut self) -> &mut Vec<u8>;
fn get_current_dir(&self) -> &Path;
} }
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, TryFromPrimitive)] #[derive(Clone, Copy, TryFromPrimitive)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
pub enum Op { pub enum Op {
PushConst = 0x01, PushSmi,
PushString = 0x02, PushBigInt,
PushNull = 0x03, PushFloat,
PushTrue = 0x04, PushString,
PushFalse = 0x05, PushNull,
PushTrue,
PushFalse,
LoadLocal = 0x06, LoadLocal,
LoadOuter = 0x07, LoadOuter,
StoreLocal = 0x08, StoreLocal,
AllocLocals = 0x09, AllocLocals,
MakeThunk = 0x0A, MakeThunk,
MakeClosure = 0x0B, MakeClosure,
MakePatternClosure = 0x0C, MakePatternClosure,
Call = 0x0D, Call,
CallNoSpan = 0x0E, CallNoSpan,
MakeAttrs = 0x0F, MakeAttrs,
MakeAttrsDyn = 0x10, MakeAttrsDyn,
MakeEmptyAttrs = 0x11, MakeEmptyAttrs,
Select = 0x12, Select,
SelectDefault = 0x13, SelectDefault,
HasAttr = 0x14, HasAttr,
MakeList = 0x15, MakeList,
OpAdd = 0x16, OpAdd,
OpSub = 0x17, OpSub,
OpMul = 0x18, OpMul,
OpDiv = 0x19, OpDiv,
OpEq = 0x20, OpEq,
OpNeq = 0x21, OpNeq,
OpLt = 0x22, OpLt,
OpGt = 0x23, OpGt,
OpLeq = 0x24, OpLeq,
OpGeq = 0x25, OpGeq,
OpConcat = 0x26, OpConcat,
OpUpdate = 0x27, OpUpdate,
OpNeg = 0x28, OpNeg,
OpNot = 0x29, OpNot,
ForceBool = 0x30, ForceBool,
JumpIfFalse = 0x31, JumpIfFalse,
JumpIfTrue = 0x32, JumpIfTrue,
Jump = 0x33, Jump,
ConcatStrings = 0x34, ConcatStrings,
ResolvePath = 0x35, ResolvePath,
Assert = 0x36, Assert,
PushWith = 0x37, PushWith,
PopWith = 0x38, PopWith,
WithLookup = 0x39, WithLookup,
LoadBuiltins = 0x40, LoadBuiltins,
LoadBuiltin = 0x41, LoadBuiltin,
MkPos = 0x43, MkPos,
LoadReplBinding = 0x44, LoadReplBinding,
LoadScopedBinding = 0x45, LoadScopedBinding,
Return = 0x46, Return,
} }
struct ScopeInfo { struct ScopeInfo {
@@ -106,71 +106,78 @@ struct ScopeInfo {
struct BytecodeEmitter<'a, Ctx: BytecodeContext> { struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
ctx: &'a mut Ctx, ctx: &'a mut Ctx,
code: Vec<u8>,
scope_stack: Vec<ScopeInfo>, scope_stack: Vec<ScopeInfo>,
} }
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> Bytecode { pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
let current_dir = ctx.get_current_dir().to_string_lossy().to_string(); let ip = ctx.get_code().len();
let mut emitter = BytecodeEmitter::new(ctx); let mut emitter = BytecodeEmitter::new(ctx);
emitter.emit_toplevel(ir); emitter.emit_toplevel(ir);
Bytecode { InstructionPtr(ip)
code: emitter.code.into_boxed_slice(),
current_dir,
}
}
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,
}
} }
impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn new(ctx: &'a mut Ctx) -> Self { fn new(ctx: &'a mut Ctx) -> Self {
Self { Self {
ctx, ctx,
code: Vec::with_capacity(4096),
scope_stack: Vec::with_capacity(32), scope_stack: Vec::with_capacity(32),
} }
} }
#[inline] #[inline]
fn emit_op(&mut self, op: Op) { fn emit_op(&mut self, op: Op) {
self.code.push(op as u8); self.ctx.get_code_mut().push(op as u8);
} }
#[inline] #[inline]
fn emit_u8(&mut self, val: u8) { fn emit_u8(&mut self, val: u8) {
self.code.push(val); self.ctx.get_code_mut().push(val);
} }
#[inline] #[inline]
fn emit_u16(&mut self, val: u16) { fn emit_u16(&mut self, val: u16) {
self.code.extend_from_slice(&val.to_le_bytes()); self.ctx
.get_code_mut()
.extend_from_slice(&val.to_le_bytes());
} }
#[inline] #[inline]
fn emit_u32(&mut self, val: u32) { fn emit_u32(&mut self, val: u32) {
self.code.extend_from_slice(&val.to_le_bytes()); self.ctx
.get_code_mut()
.extend_from_slice(&val.to_le_bytes());
}
#[inline]
fn emit_i32(&mut self, val: i32) {
self.ctx
.get_code_mut()
.extend_from_slice(&val.to_le_bytes());
}
#[inline]
fn emit_i64(&mut self, val: i64) {
self.ctx
.get_code_mut()
.extend_from_slice(&val.to_le_bytes());
}
#[inline]
fn emit_f64(&mut self, val: f64) {
self.ctx
.get_code_mut()
.extend_from_slice(&val.to_le_bytes());
} }
#[inline] #[inline]
fn emit_i32_placeholder(&mut self) -> usize { fn emit_i32_placeholder(&mut self) -> usize {
let offset = self.code.len(); let offset = self.ctx.get_code_mut().len();
self.code.extend_from_slice(&[0u8; 4]); self.ctx.get_code_mut().extend_from_slice(&[0u8; 4]);
offset offset
} }
#[inline] #[inline]
fn patch_i32(&mut self, offset: usize, val: i32) { fn patch_i32(&mut self, offset: usize, val: i32) {
self.code[offset..offset + 4].copy_from_slice(&val.to_le_bytes()); self.ctx.get_code_mut()[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
} }
#[inline] #[inline]
@@ -181,11 +188,18 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
#[inline] #[inline]
fn patch_jump_target(&mut self, placeholder_offset: usize) { fn patch_jump_target(&mut self, placeholder_offset: usize) {
let current_pos = self.code.len(); let current_pos = self.ctx.get_code_mut().len();
let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4; let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4;
self.patch_i32(placeholder_offset, relative_offset); self.patch_i32(placeholder_offset, relative_offset);
} }
#[inline]
fn emit_str_id(&mut self, id: StringId) {
self.ctx
.get_code_mut()
.extend_from_slice(&(id.0.to_usize() as u32).to_le_bytes());
}
fn current_depth(&self) -> u16 { fn current_depth(&self) -> u16 {
self.scope_stack.last().map_or(0, |s| s.depth) self.scope_stack.last().map_or(0, |s| s.depth)
} }
@@ -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<'_>)]) { fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) {
for &(id, inner) in thunks { for &(id, inner) in thunks {
let label = format!("e{}", id.0); let label = format!("e{}", id.0);
let label_idx = self.ctx.intern_string(&label); let label_idx = self.ctx.intern_string(&label);
let skip_patch = self.emit_jump_placeholder(); let skip_patch = self.emit_jump_placeholder();
let entry_point = self.code.len() as u32; let entry_point = self.ctx.get_code_mut().len() as u32;
self.emit_expr(inner); self.emit_expr(inner);
self.emit_op(Op::Return); self.emit_op(Op::Return);
self.patch_jump_target(skip_patch); self.patch_jump_target(skip_patch);
self.emit_op(Op::MakeThunk); self.emit_op(Op::MakeThunk);
self.emit_u32(entry_point); self.emit_u32(entry_point);
self.emit_u32(label_idx); self.emit_str_id(label_idx);
let (_, local_idx) = self.resolve_thunk(id); let (_, local_idx) = self.resolve_thunk(id);
self.emit_op(Op::StoreLocal); self.emit_op(Op::StoreLocal);
self.emit_u32(local_idx); self.emit_u32(local_idx);
@@ -450,14 +433,17 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_expr(&mut self, ir: RawIrRef<'_>) { fn emit_expr(&mut self, ir: RawIrRef<'_>) {
match ir.deref() { match ir.deref() {
&Ir::Int(x) => { &Ir::Int(x) => {
let idx = self.ctx.intern_constant(Constant::Int(x)); if x <= i32::MAX as i64 {
self.emit_op(Op::PushConst); self.emit_op(Op::PushSmi);
self.emit_u32(idx); self.emit_i32(x as i32);
} else {
self.emit_op(Op::PushBigInt);
self.emit_i64(x);
}
} }
&Ir::Float(x) => { &Ir::Float(x) => {
let idx = self.ctx.intern_constant(Constant::Float(x.to_bits())); self.emit_op(Op::PushFloat);
self.emit_op(Op::PushConst); self.emit_f64(x);
self.emit_u32(idx);
} }
&Ir::Bool(true) => self.emit_op(Op::PushTrue), &Ir::Bool(true) => self.emit_op(Op::PushTrue),
&Ir::Bool(false) => self.emit_op(Op::PushFalse), &Ir::Bool(false) => self.emit_op(Op::PushFalse),
@@ -465,7 +451,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
Ir::Str(s) => { Ir::Str(s) => {
let idx = self.ctx.intern_string(s.deref()); let idx = self.ctx.intern_string(s.deref());
self.emit_op(Op::PushString); self.emit_op(Op::PushString);
self.emit_u32(idx); self.emit_str_id(idx);
} }
&Ir::Path(p) => { &Ir::Path(p) => {
self.emit_expr(p); self.emit_expr(p);
@@ -477,20 +463,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::JumpIfFalse); self.emit_op(Op::JumpIfFalse);
let else_placeholder = self.emit_i32_placeholder(); let else_placeholder = self.emit_i32_placeholder();
let after_jif = self.code.len(); let after_jif = self.ctx.get_code_mut().len();
self.emit_expr(consq); self.emit_expr(consq);
self.emit_op(Op::Jump); self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder(); let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.code.len(); let after_jump = self.ctx.get_code_mut().len();
let else_offset = (after_jump as i32) - (after_jif as i32); let else_offset = (after_jump as i32) - (after_jif as i32);
self.patch_i32(else_placeholder, else_offset); self.patch_i32(else_placeholder, else_offset);
self.emit_expr(alter); self.emit_expr(alter);
let end_offset = (self.code.len() as i32) - (after_jump as i32); let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
self.patch_i32(end_placeholder, end_offset); self.patch_i32(end_placeholder, end_offset);
} }
&Ir::BinOp { lhs, rhs, kind } => { &Ir::BinOp { lhs, rhs, kind } => {
@@ -554,10 +540,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::LoadBuiltins); self.emit_op(Op::LoadBuiltins);
} }
&Ir::Builtin(name) => { &Ir::Builtin(name) => {
let sym = self.ctx.get_sym(name).to_string();
let idx = self.ctx.intern_string(&sym);
self.emit_op(Op::LoadBuiltin); self.emit_op(Op::LoadBuiltin);
self.emit_u32(idx); self.emit_u32(name.0.to_usize() as u32);
} }
&Ir::ConcatStrings { &Ir::ConcatStrings {
ref parts, ref parts,
@@ -584,7 +568,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_expr(*assertion); self.emit_expr(*assertion);
self.emit_expr(*expr); self.emit_expr(*expr);
self.emit_op(Op::Assert); self.emit_op(Op::Assert);
self.emit_u32(raw_idx); self.emit_str_id(raw_idx);
self.emit_u32(span_id); self.emit_u32(span_id);
} }
&Ir::CurPos(span) => { &Ir::CurPos(span) => {
@@ -593,16 +577,12 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_u32(span_id); self.emit_u32(span_id);
} }
&Ir::ReplBinding(name) => { &Ir::ReplBinding(name) => {
let sym = self.ctx.get_sym(name).to_string();
let idx = self.ctx.intern_string(&sym);
self.emit_op(Op::LoadReplBinding); self.emit_op(Op::LoadReplBinding);
self.emit_u32(idx); self.emit_str_id(name);
} }
&Ir::ScopedImportBinding(name) => { &Ir::ScopedImportBinding(name) => {
let sym = self.ctx.get_sym(name).to_string();
let idx = self.ctx.intern_string(&sym);
self.emit_op(Op::LoadScopedBinding); self.emit_op(Op::LoadScopedBinding);
self.emit_u32(idx); self.emit_str_id(name);
} }
&Ir::With { &Ir::With {
namespace, namespace,
@@ -612,10 +592,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_with(namespace, body, thunks); self.emit_with(namespace, body, thunks);
} }
&Ir::WithLookup(name) => { &Ir::WithLookup(name) => {
let sym = self.ctx.get_sym(name).to_string();
let idx = self.ctx.intern_string(&sym);
self.emit_op(Op::WithLookup); self.emit_op(Op::WithLookup);
self.emit_u32(idx); self.emit_str_id(name);
} }
} }
} }
@@ -628,20 +606,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::ForceBool); self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfFalse); self.emit_op(Op::JumpIfFalse);
let skip_placeholder = self.emit_i32_placeholder(); let skip_placeholder = self.emit_i32_placeholder();
let after_jif = self.code.len(); let after_jif = self.ctx.get_code_mut().len();
self.emit_expr(rhs); self.emit_expr(rhs);
self.emit_op(Op::ForceBool); self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump); self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder(); let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.code.len(); let after_jump = self.ctx.get_code_mut().len();
let false_offset = (after_jump as i32) - (after_jif as i32); let false_offset = (after_jump as i32) - (after_jif as i32);
self.patch_i32(skip_placeholder, false_offset); self.patch_i32(skip_placeholder, false_offset);
self.emit_op(Op::PushFalse); self.emit_op(Op::PushFalse);
let end_offset = (self.code.len() as i32) - (after_jump as i32); let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
self.patch_i32(end_placeholder, end_offset); self.patch_i32(end_placeholder, end_offset);
} }
Or => { Or => {
@@ -649,20 +627,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::ForceBool); self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfTrue); self.emit_op(Op::JumpIfTrue);
let skip_placeholder = self.emit_i32_placeholder(); let skip_placeholder = self.emit_i32_placeholder();
let after_jit = self.code.len(); let after_jit = self.ctx.get_code_mut().len();
self.emit_expr(rhs); self.emit_expr(rhs);
self.emit_op(Op::ForceBool); self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump); self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder(); let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.code.len(); let after_jump = self.ctx.get_code_mut().len();
let true_offset = (after_jump as i32) - (after_jit as i32); let true_offset = (after_jump as i32) - (after_jit as i32);
self.patch_i32(skip_placeholder, true_offset); self.patch_i32(skip_placeholder, true_offset);
self.emit_op(Op::PushTrue); self.emit_op(Op::PushTrue);
let end_offset = (self.code.len() as i32) - (after_jump as i32); let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
self.patch_i32(end_placeholder, end_offset); self.patch_i32(end_placeholder, end_offset);
} }
Impl => { Impl => {
@@ -670,20 +648,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::ForceBool); self.emit_op(Op::ForceBool);
self.emit_op(Op::JumpIfFalse); self.emit_op(Op::JumpIfFalse);
let skip_placeholder = self.emit_i32_placeholder(); let skip_placeholder = self.emit_i32_placeholder();
let after_jif = self.code.len(); let after_jif = self.ctx.get_code_mut().len();
self.emit_expr(rhs); self.emit_expr(rhs);
self.emit_op(Op::ForceBool); self.emit_op(Op::ForceBool);
self.emit_op(Op::Jump); self.emit_op(Op::Jump);
let end_placeholder = self.emit_i32_placeholder(); let end_placeholder = self.emit_i32_placeholder();
let after_jump = self.code.len(); let after_jump = self.ctx.get_code_mut().len();
let true_offset = (after_jump as i32) - (after_jif as i32); let true_offset = (after_jump as i32) - (after_jif as i32);
self.patch_i32(skip_placeholder, true_offset); self.patch_i32(skip_placeholder, true_offset);
self.emit_op(Op::PushTrue); self.emit_op(Op::PushTrue);
let end_offset = (self.code.len() as i32) - (after_jump as i32); let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
self.patch_i32(end_placeholder, end_offset); self.patch_i32(end_placeholder, end_offset);
} }
PipeL => { PipeL => {
@@ -732,7 +710,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect(); let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
let skip_patch = self.emit_jump_placeholder(); let skip_patch = self.emit_jump_placeholder();
let entry_point = self.code.len() as u32; let entry_point = self.ctx.get_code().len() as u32;
self.push_scope(true, Some(arg), &thunk_ids); self.push_scope(true, Some(arg), &thunk_ids);
self.emit_scope_thunks(thunks); self.emit_scope_thunks(thunks);
self.emit_expr(body); self.emit_expr(body);
@@ -754,20 +732,14 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_u8(if *ellipsis { 1 } else { 0 }); self.emit_u8(if *ellipsis { 1 } else { 0 });
for &(sym, _) in required.iter() { for &(sym, _) in required.iter() {
let name = self.ctx.get_sym(sym).to_string(); self.emit_str_id(sym);
let idx = self.ctx.intern_string(&name);
self.emit_u32(idx);
} }
for &(sym, _) in optional.iter() { for &(sym, _) in optional.iter() {
let name = self.ctx.get_sym(sym).to_string(); self.emit_str_id(sym);
let idx = self.ctx.intern_string(&name);
self.emit_u32(idx);
} }
for &(sym, span) in required.iter().chain(optional.iter()) { for &(sym, span) in required.iter().chain(optional.iter()) {
let name = self.ctx.get_sym(sym).to_string();
let name_idx = self.ctx.intern_string(&name);
let span_id = self.ctx.register_span(span); let span_id = self.ctx.register_span(span);
self.emit_u32(name_idx); self.emit_str_id(sym);
self.emit_u32(span_id); self.emit_u32(span_id);
} }
} else { } else {
@@ -779,7 +751,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_attrset( fn emit_attrset(
&mut self, &mut self,
stcs: &crate::ir::HashMap<'_, SymId, (RawIrRef<'_>, TextRange)>, stcs: &crate::ir::HashMap<'_, StringId, (RawIrRef<'_>, TextRange)>,
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)], dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
) { ) {
if stcs.is_empty() && dyns.is_empty() { if stcs.is_empty() && dyns.is_empty() {
@@ -789,42 +761,35 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
if !dyns.is_empty() { if !dyns.is_empty() {
for (&sym, &(val, _)) in stcs.iter() { for (&sym, &(val, _)) in stcs.iter() {
let key = self.ctx.get_sym(sym).to_string();
let idx = self.ctx.intern_string(&key);
self.emit_op(Op::PushString); self.emit_op(Op::PushString);
self.emit_u32(idx); self.emit_str_id(sym);
self.emit_expr(val); self.emit_expr(val);
} }
for (_, &(_, span)) in stcs.iter() { for (_, &(_, span)) in stcs.iter() {
let span_id = self.ctx.register_span(span); let span_id = self.ctx.register_span(span);
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64)); self.emit_op(Op::PushSmi);
self.emit_op(Op::PushConst); self.emit_u32(span_id);
self.emit_u32(idx);
} }
for &(key, val, span) in dyns.iter() { for &(key, val, span) in dyns.iter() {
self.emit_expr(key); self.emit_expr(key);
self.emit_expr(val); self.emit_expr(val);
let span_id = self.ctx.register_span(span); let span_id = self.ctx.register_span(span);
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64)); self.emit_op(Op::PushSmi);
self.emit_op(Op::PushConst); self.emit_u32(span_id);
self.emit_u32(idx);
} }
self.emit_op(Op::MakeAttrsDyn); self.emit_op(Op::MakeAttrsDyn);
self.emit_u32(stcs.len() as u32); self.emit_u32(stcs.len() as u32);
self.emit_u32(dyns.len() as u32); self.emit_u32(dyns.len() as u32);
} else { } else {
for (&sym, &(val, _)) in stcs.iter() { for (&sym, &(val, _)) in stcs.iter() {
let key = self.ctx.get_sym(sym).to_string();
let idx = self.ctx.intern_string(&key);
self.emit_op(Op::PushString); self.emit_op(Op::PushString);
self.emit_u32(idx); self.emit_str_id(sym);
self.emit_expr(val); self.emit_expr(val);
} }
for (_, &(_, span)) in stcs.iter() { for (_, &(_, span)) in stcs.iter() {
let span_id = self.ctx.register_span(span); let span_id = self.ctx.register_span(span);
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64)); self.emit_op(Op::PushSmi);
self.emit_op(Op::PushConst); self.emit_u32(span_id);
self.emit_u32(idx);
} }
self.emit_op(Op::MakeAttrs); self.emit_op(Op::MakeAttrs);
self.emit_u32(stcs.len() as u32); self.emit_u32(stcs.len() as u32);
@@ -840,15 +805,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
) { ) {
self.emit_expr(expr); self.emit_expr(expr);
for attr in attrpath.iter() { for attr in attrpath.iter() {
match attr { match *attr {
Attr::Str(sym, _) => { Attr::Str(sym, _) => {
let key = self.ctx.get_sym(*sym).to_string();
let idx = self.ctx.intern_string(&key);
self.emit_op(Op::PushString); self.emit_op(Op::PushString);
self.emit_u32(idx); self.emit_str_id(sym);
} }
Attr::Dynamic(expr, _) => { Attr::Dynamic(expr, _) => {
self.emit_expr(*expr); self.emit_expr(expr);
} }
} }
} }
@@ -870,15 +833,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) { fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
self.emit_expr(lhs); self.emit_expr(lhs);
for attr in rhs.iter() { for attr in rhs.iter() {
match attr { match *attr {
Attr::Str(sym, _) => { Attr::Str(sym, _) => {
let key = self.ctx.get_sym(*sym).to_string();
let idx = self.ctx.intern_string(&key);
self.emit_op(Op::PushString); self.emit_op(Op::PushString);
self.emit_u32(idx); self.emit_str_id(sym);
} }
Attr::Dynamic(expr, _) => { Attr::Dynamic(expr, _) => {
self.emit_expr(*expr); self.emit_expr(expr);
} }
} }
} }
@@ -3,11 +3,10 @@ use std::fmt::Write;
use colored::Colorize; use colored::Colorize;
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use crate::bytecode::{Bytecode, Constant, Op}; use crate::codegen::{Bytecode, Op};
pub(crate) trait DisassemblerContext { pub(crate) trait DisassemblerContext {
fn lookup_string(&self, id: u32) -> &str; fn lookup_string(&self, id: u32) -> &str;
fn lookup_constant(&self, id: u32) -> &Constant;
} }
pub(crate) struct Disassembler<'a, Ctx> { pub(crate) struct Disassembler<'a, Ctx> {
@@ -55,6 +54,22 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
i32::from_le_bytes(bytes) i32::from_le_bytes(bytes)
} }
fn read_i64(&mut self) -> i64 {
let bytes = self.code[self.pos..self.pos + 8]
.try_into()
.expect("no enough bytes");
self.pos += 8;
i64::from_le_bytes(bytes)
}
fn read_f64(&mut self) -> f64 {
let bytes = self.code[self.pos..self.pos + 8]
.try_into()
.expect("no enough bytes");
self.pos += 8;
f64::from_le_bytes(bytes)
}
pub fn disassemble(&mut self) -> String { pub fn disassemble(&mut self) -> String {
self.disassemble_impl(false) self.disassemble_impl(false)
} }
@@ -144,14 +159,17 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
let op = Op::try_from_primitive(op_byte).expect("invalid op code"); let op = Op::try_from_primitive(op_byte).expect("invalid op code");
match op { match op {
Op::PushConst => { Op::PushSmi => {
let idx = self.read_u32(); let val = self.read_i32();
let val = self.ctx.lookup_constant(idx); ("PushSmi", format!("{}", val))
let val_str = match val { }
Constant::Int(i) => format!("Int({})", i), Op::PushBigInt => {
Constant::Float(f) => format!("Float(bits: {})", f), let val = self.read_i64();
}; ("PushBigInt", format!("{}", val))
("PushConst", format!("@{} ({})", idx, val_str)) }
Op::PushFloat => {
let val = self.read_f64();
("PushFloat", format!("{}", val))
} }
Op::PushString => { Op::PushString => {
let idx = self.read_u32(); let idx = self.read_u32();
@@ -50,16 +50,16 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
fn new_arg(&mut self) -> ArgId; fn new_arg(&mut self) -> ArgId;
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>; fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>;
fn new_sym(&mut self, sym: String) -> SymId; fn new_sym(&mut self, sym: String) -> StringId;
fn get_sym(&self, id: SymId) -> Symbol<'_>; fn get_sym(&self, id: StringId) -> Symbol<'_>;
fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'id, 'ir>>; fn lookup(&self, sym: StringId, span: TextRange) -> Result<IrRef<'id, 'ir>>;
fn get_current_source(&self) -> Source; fn get_current_source(&self) -> Source;
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ArgId, f: F) -> R fn with_param_scope<F, R>(&mut self, param: StringId, arg: ArgId, f: F) -> R
where where
F: FnOnce(&mut Self) -> R; F: FnOnce(&mut Self) -> R;
fn with_let_scope<F, R>(&mut self, bindings: &[SymId], f: F) -> Result<R> fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R>
where where
F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, R)>; F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, R)>;
fn with_with_scope<F, R>(&mut self, f: F) -> R fn with_with_scope<F, R>(&mut self, f: F) -> R
@@ -456,8 +456,8 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
enum PendingValue<'ir> { enum PendingValue<'ir> {
Expr(ast::Expr), Expr(ast::Expr),
InheritFrom(ast::Expr, SymId, TextRange), InheritFrom(ast::Expr, StringId, TextRange),
InheritScope(SymId, TextRange), InheritScope(StringId, TextRange),
Set(PendingAttrSet<'ir>), Set(PendingAttrSet<'ir>),
ExtendedRecAttrSet { ExtendedRecAttrSet {
base: ast::AttrSet, base: ast::AttrSet,
@@ -466,7 +466,7 @@ enum PendingValue<'ir> {
} }
struct PendingAttrSet<'ir> { struct PendingAttrSet<'ir> {
stcs: HashMap<'ir, SymId, (PendingValue<'ir>, TextRange)>, stcs: HashMap<'ir, StringId, (PendingValue<'ir>, TextRange)>,
dyns: Vec<'ir, (ast::Attr, PendingValue<'ir>, TextRange)>, dyns: Vec<'ir, (ast::Attr, PendingValue<'ir>, TextRange)>,
} }
@@ -510,7 +510,7 @@ impl<'id: 'ir, 'ir> PendingAttrSet<'ir> {
fn insert_static( fn insert_static(
&mut self, &mut self,
sym: SymId, sym: StringId,
span: TextRange, span: TextRange,
path: &[ast::Attr], path: &[ast::Attr],
value: ast::Expr, value: ast::Expr,
@@ -846,7 +846,7 @@ fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) -
} }
struct FinalizedAttrSet<'id, 'ir> { struct FinalizedAttrSet<'id, 'ir> {
stcs: HashMap<'ir, SymId, (IrRef<'id, 'ir>, TextRange)>, stcs: HashMap<'ir, StringId, (IrRef<'id, 'ir>, TextRange)>,
dyns: Vec<'ir, (IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)>, dyns: Vec<'ir, (IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)>,
} }
@@ -930,23 +930,23 @@ fn downgrade_attrpath<'id, 'ir>(
struct PatternBindings<'id, 'ir> { struct PatternBindings<'id, 'ir> {
body: IrRef<'id, 'ir>, body: IrRef<'id, 'ir>,
required: Vec<'ir, (SymId, TextRange)>, required: Vec<'ir, (StringId, TextRange)>,
optional: Vec<'ir, (SymId, TextRange)>, optional: Vec<'ir, (StringId, TextRange)>,
} }
fn downgrade_pattern_bindings<'id, 'ir, Ctx>( fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
pat_entries: impl Iterator<Item = ast::PatEntry>, pat_entries: impl Iterator<Item = ast::PatEntry>,
alias: Option<SymId>, alias: Option<StringId>,
arg: ArgId, arg: ArgId,
ctx: &mut Ctx, ctx: &mut Ctx,
body_fn: impl FnOnce(&mut Ctx, &[SymId]) -> Result<IrRef<'id, 'ir>>, body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
) -> Result<PatternBindings<'id, 'ir>> ) -> Result<PatternBindings<'id, 'ir>>
where where
Ctx: DowngradeContext<'id, 'ir>, Ctx: DowngradeContext<'id, 'ir>,
{ {
let arg = ctx.new_expr(Ir::Arg(arg)); let arg = ctx.new_expr(Ir::Arg(arg));
struct Param { struct Param {
sym: SymId, sym: StringId,
sym_span: TextRange, sym_span: TextRange,
default: Option<ast::Expr>, default: Option<ast::Expr>,
span: TextRange, span: TextRange,
@@ -1045,7 +1045,7 @@ fn downgrade_let_bindings<'id, 'ir, Ctx, F>(
) -> Result<IrRef<'id, 'ir>> ) -> Result<IrRef<'id, 'ir>>
where where
Ctx: DowngradeContext<'id, 'ir>, Ctx: DowngradeContext<'id, 'ir>,
F: FnOnce(&mut Ctx, &[SymId]) -> Result<IrRef<'id, 'ir>>, F: FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
{ {
downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| { downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| {
body_fn(ctx, binding_keys) body_fn(ctx, binding_keys)
@@ -1081,7 +1081,7 @@ where
Ctx: DowngradeContext<'id, 'ir>, Ctx: DowngradeContext<'id, 'ir>,
F: FnOnce( F: FnOnce(
&mut Ctx, &mut Ctx,
&[SymId], &[StringId],
&[(IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)], &[(IrRef<'id, 'ir>, IrRef<'id, 'ir>, TextRange)],
) -> Result<IrRef<'id, 'ir>>, ) -> Result<IrRef<'id, 'ir>>,
{ {
@@ -1106,7 +1106,7 @@ where
fn collect_inherit_lookups<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>>( fn collect_inherit_lookups<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
entries: &[ast::Entry], entries: &[ast::Entry],
ctx: &mut Ctx, ctx: &mut Ctx,
) -> Result<HashMap<'ir, SymId, (IrRef<'id, 'ir>, TextRange)>> { ) -> Result<HashMap<'ir, StringId, (IrRef<'id, 'ir>, TextRange)>> {
let mut inherit_lookups = HashMap::new_in(ctx.bump()); let mut inherit_lookups = HashMap::new_in(ctx.bump());
for entry in entries { for entry in entries {
if let ast::Entry::Inherit(inherit) = entry if let ast::Entry::Inherit(inherit) = entry
@@ -1128,7 +1128,7 @@ fn collect_inherit_lookups<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>( fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
pending: &PendingAttrSet, pending: &PendingAttrSet,
ctx: &mut Ctx, ctx: &mut Ctx,
) -> Result<HashSet<SymId>> { ) -> Result<HashSet<StringId>> {
let mut binding_syms = HashSet::new(); let mut binding_syms = HashSet::new();
for (sym, (_, span)) in &pending.stcs { for (sym, (_, span)) in &pending.stcs {
@@ -1146,7 +1146,7 @@ fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const AL
fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>( fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
pending: PendingAttrSet, pending: PendingAttrSet,
inherit_lookups: &HashMap<SymId, (IrRef<'id, 'ir>, TextRange)>, inherit_lookups: &HashMap<StringId, (IrRef<'id, 'ir>, TextRange)>,
ctx: &mut Ctx, ctx: &mut Ctx,
) -> Result<FinalizedAttrSet<'id, 'ir>> { ) -> Result<FinalizedAttrSet<'id, 'ir>> {
let mut stcs = HashMap::new_in(ctx.bump()); let mut stcs = HashMap::new_in(ctx.bump());
@@ -1176,7 +1176,7 @@ fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_D
fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>( fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
value: PendingValue, value: PendingValue,
inherit_lookups: &HashMap<SymId, (IrRef<'id, 'ir>, TextRange)>, inherit_lookups: &HashMap<StringId, (IrRef<'id, 'ir>, TextRange)>,
ctx: &mut Ctx, ctx: &mut Ctx,
) -> Result<IrRef<'id, 'ir>> { ) -> Result<IrRef<'id, 'ir>> {
match value { match value {
-129
View File
@@ -1,14 +1,9 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use deno_core::error::JsError;
use deno_error::JsErrorClass as _;
use itertools::Itertools as _;
use miette::{Diagnostic, NamedSource, SourceSpan}; use miette::{Diagnostic, NamedSource, SourceSpan};
use thiserror::Error; use thiserror::Error;
use crate::runtime::RuntimeContext;
pub type Result<T> = core::result::Result<T, Box<Error>>; pub type Result<T> = core::result::Result<T, Box<Error>>;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -229,127 +224,3 @@ pub struct StackFrame {
#[source_code] #[source_code]
pub src: NamedSource<Arc<str>>, 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 nix_compat::nixhash::NixHash;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use crate::runtime::OpStateExt;
use crate::runtime::RuntimeContext;
use crate::store::Store as _; use crate::store::Store as _;
mod archive; mod archive;
@@ -20,7 +18,6 @@ pub use download::Downloader;
pub use metadata_cache::MetadataCache; pub use metadata_cache::MetadataCache;
use crate::nar; use crate::nar;
use crate::runtime::NixRuntimeError;
#[derive(ToV8)] #[derive(ToV8)]
pub struct FetchUrlResult { pub struct FetchUrlResult {
@@ -306,11 +303,3 @@ fn normalize_hash(hash: &str) -> String {
} }
hash.to_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>(),
]
}
@@ -10,7 +10,7 @@ impl FetcherCache {
pub fn new() -> Result<Self, std::io::Error> { pub fn new() -> Result<Self, std::io::Error> {
let base_dir = dirs::cache_dir() let base_dir = dirs::cache_dir()
.unwrap_or_else(|| PathBuf::from("/tmp")) .unwrap_or_else(|| PathBuf::from("/tmp"))
.join("nix-js") .join("fix")
.join("fetchers"); .join("fetchers");
fs::create_dir_all(&base_dir)?; fs::create_dir_all(&base_dir)?;
+22 -17
View File
@@ -4,6 +4,7 @@ use std::{
}; };
use bumpalo::{Bump, boxed::Box, collections::Vec}; use bumpalo::{Bump, boxed::Box, collections::Vec};
use gc_arena::Collect;
use ghost_cell::{GhostCell, GhostToken}; use ghost_cell::{GhostCell, GhostToken};
use rnix::{TextRange, ast}; use rnix::{TextRange, ast};
use string_interner::symbol::SymbolU32; use string_interner::symbol::SymbolU32;
@@ -23,6 +24,10 @@ impl<'id, 'ir> IrRef<'id, 'ir> {
Self(bump.alloc(GhostCell::new(ir))) Self(bump.alloc(GhostCell::new(ir)))
} }
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a Ir<'ir, Self> {
self.0.borrow(token)
}
/// Freeze a mutable IR reference into a read-only one, consuming the /// Freeze a mutable IR reference into a read-only one, consuming the
/// `GhostToken` to prevent any further mutation. /// `GhostToken` to prevent any further mutation.
/// ///
@@ -42,13 +47,6 @@ impl<'id, 'ir> IrRef<'id, 'ir> {
} }
} }
impl<'id, 'ir> Deref for IrRef<'id, 'ir> {
type Target = GhostCell<'id, Ir<'ir, IrRef<'id, 'ir>>>;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>); pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>);
@@ -68,7 +66,7 @@ pub enum Ir<'ir, Ref> {
Null, Null,
Str(Box<'ir, String>), Str(Box<'ir, String>),
AttrSet { AttrSet {
stcs: HashMap<'ir, SymId, (Ref, TextRange)>, stcs: HashMap<'ir, StringId, (Ref, TextRange)>,
dyns: Vec<'ir, (Ref, Ref, TextRange)>, dyns: Vec<'ir, (Ref, Ref, TextRange)>,
}, },
List { List {
@@ -119,7 +117,7 @@ pub enum Ir<'ir, Ref> {
body: Ref, body: Ref,
thunks: Vec<'ir, (ThunkId, Ref)>, thunks: Vec<'ir, (ThunkId, Ref)>,
}, },
WithLookup(SymId), WithLookup(StringId),
// Function related // Function related
Func { Func {
@@ -137,7 +135,7 @@ pub enum Ir<'ir, Ref> {
// Builtins // Builtins
Builtins, Builtins,
Builtin(SymId), Builtin(StringId),
// Misc // Misc
TopLevel { TopLevel {
@@ -146,19 +144,26 @@ pub enum Ir<'ir, Ref> {
}, },
Thunk(ThunkId), Thunk(ThunkId),
CurPos(TextRange), CurPos(TextRange),
ReplBinding(SymId), ReplBinding(StringId),
ScopedImportBinding(SymId), ScopedImportBinding(StringId),
} }
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ThunkId(pub usize); pub struct ThunkId(pub usize);
pub type SymId = SymbolU32; #[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)]
#[collect(require_static)]
pub struct StringId(pub SymbolU32);
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ArgId(pub usize); pub struct ArgId(pub u32);
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SpanId(pub u32);
/// Represents a key in an attribute path. /// Represents a key in an attribute path.
#[allow(unused)] #[allow(unused)]
@@ -169,7 +174,7 @@ pub enum Attr<Ref> {
Dynamic(Ref, TextRange), Dynamic(Ref, TextRange),
/// A static attribute key. /// A static attribute key.
/// Example: `attrs.key` /// Example: `attrs.key`
Str(SymId, TextRange), Str(StringId, TextRange),
} }
/// The kinds of binary operations supported in Nix. /// The kinds of binary operations supported in Nix.
@@ -248,8 +253,8 @@ impl From<ast::UnaryOpKind> for UnOpKind {
/// Describes the parameters of a function. /// Describes the parameters of a function.
#[derive(Debug)] #[derive(Debug)]
pub struct Param<'ir> { pub struct Param<'ir> {
pub required: Vec<'ir, (SymId, TextRange)>, pub required: Vec<'ir, (StringId, TextRange)>,
pub optional: Vec<'ir, (SymId, TextRange)>, pub optional: Vec<'ir, (StringId, TextRange)>,
pub ellipsis: bool, pub ellipsis: bool,
} }
+3 -4
View File
@@ -1,20 +1,19 @@
#![warn(clippy::unwrap_used)] #![warn(clippy::unwrap_used)]
#![allow(dead_code)]
pub mod context;
pub mod error; pub mod error;
pub mod logging; pub mod logging;
pub mod runtime;
pub mod value; pub mod value;
mod bytecode;
mod codegen; mod codegen;
mod derivation; mod derivation;
mod disassembler; mod disassembler;
mod downgrade; mod downgrade;
mod fetcher; // mod fetcher;
mod ir; mod ir;
mod nar; mod nar;
mod nix_utils; mod nix_utils;
mod runtime;
mod store; mod store;
mod string_context; mod string_context;
+18 -53
View File
@@ -3,23 +3,15 @@ use std::process::exit;
use anyhow::Result; use anyhow::Result;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use fix::error::Source;
use fix::runtime::Runtime;
use hashbrown::HashSet; use hashbrown::HashSet;
use nix_js::context::Context;
use nix_js::error::Source;
use rustyline::DefaultEditor; use rustyline::DefaultEditor;
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "nix-js", about = "Nix expression evaluator")] #[command(name = "nix-js", about = "Nix expression evaluator")]
struct Cli { 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(subcommand)]
command: Command, command: Command,
} }
@@ -48,28 +40,7 @@ struct ExprSource {
file: Option<PathBuf>, file: Option<PathBuf>,
} }
fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> { fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> {
#[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<()> {
let src = if let Some(expr) = src.expr { let src = if let Some(expr) = src.expr {
Source::new_eval(expr)? Source::new_eval(expr)?
} else if let Some(file) = src.file { } else if let Some(file) = src.file {
@@ -77,10 +48,11 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
} else { } else {
unreachable!() unreachable!()
}; };
match context.compile_bytecode(src) { todo!()
/* match runtime.compile_bytecode(src) {
Ok(compiled) => { Ok(compiled) => {
if !silent { if !silent {
println!("{}", context.disassemble_colored(&compiled)); println!("{}", runtime.disassemble_colored(&compiled));
} }
} }
Err(err) => { Err(err) => {
@@ -88,12 +60,10 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
exit(1); exit(1);
} }
}; };
#[cfg(feature = "inspector")] Ok(()) */
context.wait_for_inspector_disconnect();
Ok(())
} }
fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> { fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
let src = if let Some(expr) = src.expr { let src = if let Some(expr) = src.expr {
Source::new_eval(expr)? Source::new_eval(expr)?
} else if let Some(file) = src.file { } else if let Some(file) = src.file {
@@ -101,7 +71,7 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
} else { } else {
unreachable!() unreachable!()
}; };
match context.eval_deep(src) { match runtime.eval_deep(src) {
Ok(value) => { Ok(value) => {
println!("{}", value.display_compat()); println!("{}", value.display_compat());
} }
@@ -110,12 +80,10 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
exit(1); exit(1);
} }
}; };
#[cfg(feature = "inspector")]
context.wait_for_inspector_disconnect();
Ok(()) Ok(())
} }
fn run_repl(context: &mut Context) -> Result<()> { fn run_repl(runtime: &mut Runtime) -> Result<()> {
let mut rl = DefaultEditor::new()?; let mut rl = DefaultEditor::new()?;
let mut scope = HashSet::new(); let mut scope = HashSet::new();
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$"); const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
@@ -134,20 +102,20 @@ fn run_repl(context: &mut Context) -> Result<()> {
eprintln!("Error: missing expression after '='"); eprintln!("Error: missing expression after '='");
continue; continue;
} }
match context.add_binding(ident, expr, &mut scope) { match runtime.add_binding(ident, expr, &mut scope) {
Ok(value) => println!("{} = {}", ident, value), Ok(value) => println!("{} = {}", ident, value),
Err(err) => eprintln!("{:?}", miette::Report::new(*err)), Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
} }
} else { } else {
let src = Source::new_repl(line)?; let src = Source::new_repl(line)?;
match context.eval_repl(src, &scope) { match runtime.eval_repl(src, &scope) {
Ok(value) => println!("{value}"), Ok(value) => println!("{value}"),
Err(err) => eprintln!("{:?}", miette::Report::new(*err)), Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
} }
} }
} else { } else {
let src = Source::new_repl(line)?; let src = Source::new_repl(line)?;
match context.eval_shallow(src) { match runtime.eval_repl(src, &scope) {
Ok(value) => println!("{value}"), Ok(value) => println!("{value}"),
Err(err) => eprintln!("{:?}", miette::Report::new(*err)), Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
} }
@@ -170,18 +138,15 @@ fn run_repl(context: &mut Context) -> Result<()> {
} }
fn main() -> Result<()> { fn main() -> Result<()> {
nix_js::logging::init_logging(); fix::logging::init_logging();
let cli = Cli::parse(); let cli = Cli::parse();
let mut context = create_context( let mut runtime = Runtime::new()?;
#[cfg(feature = "inspector")]
&cli,
)?;
match cli.command { match cli.command {
Command::Compile { source, silent } => run_compile(&mut context, source, silent), Command::Compile { source, silent } => run_compile(&mut runtime, source, silent),
Command::Eval { source } => run_eval(&mut context, source), Command::Eval { source } => run_eval(&mut runtime, source),
Command::Repl => run_repl(&mut context), Command::Repl => run_repl(&mut runtime),
} }
} }
+531
View File
@@ -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
}
}
+51
View File
@@ -0,0 +1,51 @@
use hashbrown::HashMap;
use string_interner::DefaultStringInterner;
use crate::ir::{Ir, RawIrRef, StringId};
pub(super) fn new_builtins_env(
interner: &mut DefaultStringInterner,
) -> HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
let mut builtins = HashMap::new();
let builtins_sym = StringId(interner.get_or_intern("builtins"));
builtins.insert(builtins_sym, Ir::Builtins);
let free_globals = [
"abort",
"baseNameOf",
"break",
"dirOf",
"derivation",
"derivationStrict",
"fetchGit",
"fetchMercurial",
"fetchTarball",
"fetchTree",
"fromTOML",
"import",
"isNull",
"map",
"placeholder",
"removeAttrs",
"scopedImport",
"throw",
"toString",
];
let consts = [
("true", Ir::Bool(true)),
("false", Ir::Bool(false)),
("null", Ir::Null),
];
for name in free_globals {
let name = StringId(interner.get_or_intern(name));
let value = Ir::Builtin(name);
builtins.insert(name, value);
}
for (name, value) in consts {
let name = StringId(interner.get_or_intern(name));
builtins.insert(name, value);
}
builtins
}
+89
View File
@@ -0,0 +1,89 @@
use std::mem::MaybeUninit;
use gc_arena::Collect;
use smallvec::SmallVec;
// FIXME: Drop???
pub(super) struct Stack<const N: usize, T> {
inner: Box<[MaybeUninit<T>; N]>,
len: usize,
}
unsafe impl<'gc, const N: usize, T: Collect<'gc> + 'gc> Collect<'gc> for Stack<N, T> {
const NEEDS_TRACE: bool = true;
fn trace<U: gc_arena::collect::Trace<'gc>>(&self, cc: &mut U) {
for item in self.inner[..self.len].iter() {
unsafe {
item.assume_init_ref().trace(cc);
}
}
}
}
impl<const N: usize, T> Default for Stack<N, T> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize, T> Stack<N, T> {
pub(super) fn new() -> Self {
Self {
inner: Box::new([const { MaybeUninit::uninit() }; N]),
len: 0,
}
}
pub(super) fn push(&mut self, val: T) -> Result<(), T> {
if self.len == N {
return Err(val);
}
unsafe {
self.inner.get_unchecked_mut(self.len).write(val);
}
self.len += 1;
Ok(())
}
pub(super) fn pop(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
let ret = unsafe {
self.inner
.get_unchecked_mut(self.len - 1)
.assume_init_read()
};
self.len -= 1;
Some(ret)
}
pub(super) fn tos(&self) -> Option<&T> {
if self.len == 0 {
return None;
}
Some(unsafe { self.inner.get_unchecked(self.len - 1).assume_init_ref() })
}
pub(super) fn tos_mut(&mut self) -> Option<&mut T> {
if self.len == 0 {
return None;
}
Some(unsafe { self.inner.get_unchecked_mut(self.len - 1).assume_init_mut() })
}
pub(super) fn len(&self) -> usize {
self.len
}
pub(super) fn pop_n<const M: usize>(&mut self, n: usize) -> SmallVec<[T; M]> {
assert!(n <= self.len, "pop_n: not enough items on stack");
let mut result = SmallVec::new();
let start = self.len - n;
for i in start..self.len {
result.push(unsafe { self.inner.get_unchecked(i).assume_init_read() });
}
self.len = start;
result
}
}
+489
View File
@@ -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
@@ -1,4 +1,4 @@
use nix_js::value::Value; use fix::value::Value;
use crate::utils::{eval, eval_result}; use crate::utils::{eval, eval_result};
@@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use nix_js::value::{AttrSet, List, Value}; use fix::value::{AttrSet, List, Value};
use crate::utils::eval; use crate::utils::eval;
@@ -1,4 +1,4 @@
use nix_js::value::Value; use fix::value::Value;
use crate::utils::eval_result; 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}; use crate::utils::{eval_deep, eval_deep_result};
@@ -1,4 +1,4 @@
use nix_js::value::{List, Value}; use fix::value::{List, Value};
use crate::utils::{eval, eval_result}; 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}; use crate::utils::{eval, eval_result};
@@ -1,6 +1,6 @@
use nix_js::context::Context; use fix::error::Source;
use nix_js::error::Source; use fix::runtime::Runtime;
use nix_js::value::Value; use fix::value::Value;
use crate::utils::{eval, eval_result}; use crate::utils::{eval, eval_result};
@@ -97,7 +97,7 @@ fn import_with_complex_dependency_graph() {
#[test_log::test] #[test_log::test]
fn path_with_file() { fn path_with_file() {
let mut ctx = Context::new().unwrap(); let mut ctx = Runtime::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.txt"); let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "Hello, World!").unwrap(); std::fs::write(&test_file, "Hello, World!").unwrap();
@@ -107,7 +107,7 @@ fn path_with_file() {
// Should return a store path string // Should return a store path string
if let Value::String(store_path) = result { if let Value::String(store_path) = result {
assert!(store_path.starts_with(ctx.get_store_dir())); assert!(store_path.starts_with("/nix/store"));
assert!(store_path.contains("test.txt")); assert!(store_path.contains("test.txt"));
} else { } else {
panic!("Expected string, got {:?}", result); panic!("Expected string, got {:?}", result);
@@ -136,7 +136,7 @@ fn path_with_custom_name() {
#[test_log::test] #[test_log::test]
fn path_with_directory_recursive() { fn path_with_directory_recursive() {
let mut ctx = Context::new().unwrap(); let mut ctx = Runtime::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("mydir"); let test_dir = temp_dir.path().join("mydir");
std::fs::create_dir_all(&test_dir).unwrap(); std::fs::create_dir_all(&test_dir).unwrap();
@@ -150,7 +150,7 @@ fn path_with_directory_recursive() {
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap(); let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
if let Value::String(store_path) = result { if let Value::String(store_path) = result {
assert!(store_path.starts_with(ctx.get_store_dir())); assert!(store_path.starts_with("/nix/store"));
assert!(store_path.contains("mydir")); assert!(store_path.contains("mydir"));
} else { } else {
panic!("Expected string, got {:?}", result); panic!("Expected string, got {:?}", result);
@@ -159,7 +159,7 @@ fn path_with_directory_recursive() {
#[test_log::test] #[test_log::test]
fn path_flat_with_file() { fn path_flat_with_file() {
let mut ctx = Context::new().unwrap(); let mut ctx = Runtime::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("flat.txt"); let test_file = temp_dir.path().join("flat.txt");
std::fs::write(&test_file, "Flat content").unwrap(); std::fs::write(&test_file, "Flat content").unwrap();
@@ -171,7 +171,7 @@ fn path_flat_with_file() {
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap(); let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
if let Value::String(store_path) = result { if let Value::String(store_path) = result {
assert!(store_path.starts_with(ctx.get_store_dir())); assert!(store_path.starts_with("/nix/store"));
} else { } else {
panic!("Expected string, got {:?}", result); panic!("Expected string, got {:?}", result);
} }
@@ -2,9 +2,9 @@
use std::path::PathBuf; use std::path::PathBuf;
use nix_js::context::Context; use fix::error::Source;
use nix_js::error::Source; use fix::runtime::Runtime;
use nix_js::value::Value; use fix::value::Value;
fn get_lang_dir() -> PathBuf { fn get_lang_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang") PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
@@ -16,9 +16,9 @@ fn eval_file(name: &str) -> Result<(Value, Source), String> {
let expr = format!(r#"import "{}""#, nix_path.display()); let expr = format!(r#"import "{}""#, nix_path.display());
let mut ctx = Context::new().map_err(|e| e.to_string())?; let mut ctx = Runtime::new().map_err(|e| e.to_string())?;
let source = Source { let source = Source {
ty: nix_js::error::SourceType::File(nix_path.into()), ty: fix::error::SourceType::File(nix_path.into()),
src: expr.into(), src: expr.into(),
}; };
ctx.eval_deep(source.clone()) 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_empty_context);
eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context); eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context);
eval_fail_test!(fail_addDrvOutputDependencies_wrong_element_kind); eval_fail_test!(fail_addDrvOutputDependencies_wrong_element_kind);
eval_fail_test!(fail_addErrorContext_example); eval_fail_test!(fail_addErrorRuntime_example);
eval_fail_test!(fail_assert); eval_fail_test!(fail_assert);
eval_fail_test!(fail_assert_equal_attrs_names); eval_fail_test!(fail_assert_equal_attrs_names);
eval_fail_test!(fail_assert_equal_attrs_names_2); eval_fail_test!(fail_assert_equal_attrs_names_2);

Some files were not shown because too many files have changed in this diff Show More