feat: TODO

This commit is contained in:
2025-08-28 18:18:35 +08:00
parent 2fbd2a26a9
commit f7131079e5
26 changed files with 580 additions and 580 deletions

35
Cargo.lock generated
View File

@@ -56,6 +56,15 @@ dependencies = [
"allocator-api2", "allocator-api2",
] ]
[[package]]
name = "cc"
version = "1.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
dependencies = [
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.1" version = "1.0.1"
@@ -425,6 +434,16 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libmimalloc-sys"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.9.4" version = "0.9.4"
@@ -461,6 +480,15 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "mimalloc"
version = "0.1.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8"
dependencies = [
"libmimalloc-sys",
]
[[package]] [[package]]
name = "nibble_vec" name = "nibble_vec"
version = "0.1.0" version = "0.1.0"
@@ -488,6 +516,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bumpalo", "bumpalo",
"mimalloc",
"nixjit_context", "nixjit_context",
"nixjit_value", "nixjit_value",
"regex", "regex",
@@ -815,6 +844,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.1" version = "1.15.1"

View File

@@ -30,3 +30,9 @@ e.g.
3. HIR -> LIR (resolve var, build graph) 3. HIR -> LIR (resolve var, build graph)
4. LIR -> Value 4. LIR -> Value
为每个值分配 ID 的难点在于对动态表达式的引用。
动态表达式有:
- 依赖于函数参数的表达式
- 依赖于 with 的表达式
- 依赖于动态表达式的表达式
而这些表达式在每一次分配 ValueId 时指向的 ValueId 都不同,因此需要追踪这些变量。

View File

@@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
mimalloc = "0.1"
anyhow = "1.0" anyhow = "1.0"
bumpalo = "3.19" bumpalo = "3.19"
regex = "1.11" regex = "1.11"

View File

@@ -4,8 +4,13 @@
//! and evaluating Nix expressions. It integrates all the other `nixjit_*` //! and evaluating Nix expressions. It integrates all the other `nixjit_*`
//! components to provide a complete Nix evaluation environment. //! components to provide a complete Nix evaluation environment.
use mimalloc::MiMalloc;
pub use nixjit_context as context; pub use nixjit_context as context;
pub use nixjit_value as value; pub use nixjit_value as value;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
#[cfg(test)] #[cfg(test)]
mod test; mod test;

View File

@@ -1,7 +1,11 @@
use nixjit_error::Result;
use nixjit_eval::{Args, Value};
use nixjit_macros::builtins; use nixjit_macros::builtins;
pub trait BuiltinsContext {} pub trait BuiltinsContext {}
pub type BuiltinFn<Ctx> = fn(&mut Ctx, Args) -> Result<Value>;
#[builtins] #[builtins]
pub mod builtins { pub mod builtins {
use std::rc::Rc; use std::rc::Rc;

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
bumpalo = { version = "3.19", features = ["boxed"] } bumpalo = { version = "3.19", features = ["boxed", "collections"] }
derive_more = { version = "2.0", features = ["full"] } derive_more = { version = "2.0", features = ["full"] }
hashbrown = "0.15" hashbrown = "0.15"
itertools = "0.14" itertools = "0.14"

View File

@@ -43,6 +43,12 @@ impl DowngradeContext for DowngradeCtx<'_, '_> {
} }
fn with_expr_mut<T>(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T { fn with_expr_mut<T>(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T {
// SAFETY: This is a common pattern to temporarily bypass the borrow checker.
// We are creating a mutable reference to `self` from a raw pointer. This is safe
// because `self_mut` is only used within the closure `f`, and we are careful
// not to create aliasing mutable references. The `RefCell`'s runtime borrow
// checking further ensures that we don't have multiple mutable borrows of the
// same `Hir` expression simultaneously.
unsafe { unsafe {
let self_mut = &mut *(self as *mut Self); let self_mut = &mut *(self as *mut Self);
f(&mut self.get_ir(id).borrow_mut(), self_mut) f(&mut self.get_ir(id).borrow_mut(), self_mut)
@@ -57,6 +63,10 @@ impl DowngradeContext for DowngradeCtx<'_, '_> {
for (idx, ir) in self.ctx.hirs.iter().enumerate() { for (idx, ir) in self.ctx.hirs.iter().enumerate() {
println!( println!(
"{:?} {:#?}", "{:?} {:#?}",
// SAFETY: The index `idx` is obtained from iterating over `self.ctx.hirs`,
// so it is guaranteed to be a valid index. The length of `lirs` is added
// as an offset to ensure the `ExprId` correctly corresponds to its position
// in the combined IR storage.
unsafe { ExprId::from_raw(idx + self.ctx.lirs.len()) }, unsafe { ExprId::from_raw(idx + self.ctx.lirs.len()) },
&ir &ir
); );

View File

@@ -1,131 +1,95 @@
#[cfg(debug_assertions)]
use std::cell::OnceCell;
#[cfg(not(debug_assertions))]
use std::mem::MaybeUninit;
use std::rc::Rc; use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use petgraph::prelude::DiGraph;
use nixjit_error::Result; use nixjit_error::{Error, Result};
use nixjit_eval::{Args, EvalContext, Evaluate, StackFrame, Value}; use nixjit_eval::{Args, EvalContext, Evaluate, PrimOpApp, Value, ValueId};
use nixjit_ir::ExprId; use nixjit_ir::{ExprId, PrimOpId};
use nixjit_jit::JITContext; use nixjit_jit::JITContext;
use nixjit_lir::Lir; use nixjit_lir::Lir;
use petgraph::prelude::DiGraph;
use super::Context; use super::Context;
struct ValueCache( enum ValueCache {
#[cfg(debug_assertions)] Expr(ExprId),
Option<Value>, BlackHole,
#[cfg(not(debug_assertions))] Value(Value),
MaybeUninit<Value>
);
impl Default for ValueCache {
fn default() -> Self {
#[cfg(debug_assertions)]
{
Self(None)
}
#[cfg(not(debug_assertions))]
{
Self(MaybeUninit::uninit())
}
}
} }
impl ValueCache { impl ValueCache {
fn insert(&mut self, val: Value) { fn get_or_eval(&mut self, eval: impl FnOnce(ExprId) -> Result<Value>) -> Result<&Value> {
#[cfg(debug_assertions)] match self {
{ &mut Self::Expr(id) => {
assert!(self.0.is_none()); *self = Self::BlackHole;
let _ = self.0.insert(val); match eval(id) {
Ok(value) => {
*self = Self::Value(value);
let Self::Value(value) = self else {
unreachable!()
};
Ok(value)
} }
#[cfg(not(debug_assertions))] Err(err) => Err(err),
self.0.write(val);
} }
} }
Self::Value(value) => Ok(value),
impl Drop for ValueCache { Self::BlackHole => Err(Error::eval_error(format!("infinite recursion encountered"))),
fn drop(&mut self) {
#[cfg(not(debug_assertions))]
unsafe {
self.0.assume_init_drop();
} }
} }
} }
pub struct EvalCtx<'ctx, 'bump> { pub struct EvalCtx<'ctx, 'bump> {
ctx: &'ctx mut Context<'bump>, ctx: &'ctx mut Context<'bump>,
graph: DiGraph<ValueCache, ()>, graph: DiGraph<Vec<ValueId>, ()>,
stack: Vec<StackFrame>, caches: Vec<ValueCache>,
with_scopes: Vec<Rc<HashMap<String, Value>>>, with_scopes: Vec<Rc<HashMap<String, Value>>>,
} }
impl<'ctx, 'bump> EvalCtx<'ctx, 'bump> { impl<'ctx, 'bump> EvalCtx<'ctx, 'bump> {
pub fn new(ctx: &'ctx mut Context<'bump>) -> Self { pub fn new(ctx: &'ctx mut Context<'bump>) -> Self {
Self { Self {
ctx, graph: DiGraph::with_capacity(ctx.graph.node_count(), ctx.graph.edge_count()),
graph: DiGraph::new(), caches: Vec::new(),
stack: Vec::new(),
with_scopes: Vec::new(), with_scopes: Vec::new(),
ctx,
} }
} }
fn eval_deps(&mut self, expr: ExprId, arg: Option<Value>) -> Result<()> {
let deps = self
.ctx
.graph
.edges(expr)
.sorted_by_key(|(.., idx)| **idx)
.map(|(_, dep, idx)| (dep, *idx))
.collect_vec();
let mut frame = (0..deps.len())
.map(|_| Value::Blackhole)
.collect::<StackFrame>();
dbg!(&deps, &self.stack);
for (dep, idx) in deps {
unsafe {
if matches!(&**self.ctx.lirs.get_unchecked(dep.raw()), Lir::Arg(_)) {
*frame.get_unchecked_mut(idx.raw()) = arg.as_ref().unwrap().clone();
continue;
}
}
let dep = self.eval(dep)?;
unsafe {
*frame.get_unchecked_mut(idx.raw()) = dep;
}
}
*self.stack.last_mut().unwrap() = frame;
dbg!(&self.stack);
Ok(())
}
} }
impl EvalContext for EvalCtx<'_, '_> { impl EvalContext for EvalCtx<'_, '_> {
fn eval_root(mut self, expr: ExprId) -> Result<Value> {
self.stack.push(StackFrame::new());
self.eval_deps(expr, None)?;
self.eval(expr)
}
fn eval(&mut self, expr: ExprId) -> Result<Value> { fn eval(&mut self, expr: ExprId) -> Result<Value> {
// SAFETY: The `expr` `ExprId` is guaranteed to be a valid index into the `lirs`
// vector by the `downgrade` and `resolve` stages, which are responsible for
// creating and managing these IDs. The `get_unchecked` is safe under this invariant.
// The subsequent raw pointer operations are to safely extend the lifetime of the
// `Lir` reference. This is sound because the `lirs` vector is allocated within a
// `Bump` arena, ensuring that the `Lir` objects have a stable memory location
// and will not be deallocated or moved for the lifetime of the context.
let idx = unsafe { expr.raw() }; let idx = unsafe { expr.raw() };
let lir = unsafe { &*(&**self.ctx.lirs.get_unchecked(idx) as *const Lir) }; let lir = unsafe { &*(&**self.ctx.lirs.get_unchecked(idx) as *const Lir) };
lir.eval(self) lir.eval(self)
} }
fn call(&mut self, func: ExprId, arg: Option<Value>, frame: StackFrame) -> Result<Value> { fn resolve(&mut self, id: ExprId) -> Result<ValueId> {
self.stack.push(frame); let mut deps = Vec::new();
if let Err(err) = self.eval_deps(func, arg) {
self.stack.pop(); self.caches.push(ValueCache::Expr(id));
return Err(err); let id = self.graph.add_node(deps);
// SAFETY: The `id.index()` is guaranteed to be a valid raw ID for a `ValueId`
// because it is generated by the `petgraph::DiGraph`, which manages its own
// internal indices. This ensures that the raw value is unique and corresponds
// to a valid node in the graph.
Ok(unsafe { ValueId::from_raw(id.index()) })
} }
let ret = self.eval(func);
self.stack.pop(); fn call(&mut self, func: ValueId, arg: Value) -> Result<Value> {
ret todo!()
}
fn force(&mut self, id: ValueId) -> Result<Value> {
todo!()
} }
fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a Value> { fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a Value> {
@@ -137,34 +101,17 @@ impl EvalContext for EvalCtx<'_, '_> {
None None
} }
fn lookup_stack(&self, idx: nixjit_ir::StackIdx) -> &Value { fn call_primop(&mut self, id: PrimOpId, args: Args) -> Result<Value> {
if cfg!(debug_assertions) { // SAFETY: The `PrimOpId` is created and managed by the `Context` and is
self.stack // guaranteed to be a valid index into the `primops` array. The `get_unchecked`
.last() // is safe under this invariant, avoiding a bounds check for performance.
.unwrap() let &(arity, primop) = unsafe { self.ctx.primops.get_unchecked(id.raw()) };
.get(unsafe { idx.raw() }) if args.len() == arity {
.unwrap() primop(self.ctx, args)
} else { } else {
unsafe { Ok(Value::PrimOpApp(PrimOpApp::new(id, args).into()))
self.stack
.last()
.unwrap_unchecked()
.get_unchecked(idx.raw())
} }
} }
}
fn capture_stack(&self) -> &StackFrame {
self.stack.last().unwrap()
}
fn call_primop(&mut self, id: nixjit_ir::PrimOpId, args: Args) -> Result<Value> {
unsafe { (self.ctx.primops.get_unchecked(id.raw()).1)(self.ctx, args) }
}
fn get_primop_arity(&self, id: nixjit_ir::PrimOpId) -> usize {
unsafe { self.ctx.primops.get_unchecked(id.raw()).0 }
}
fn with_with_env<T>( fn with_with_env<T>(
&mut self, &mut self,

View File

@@ -1,4 +1,3 @@
use std::cell::Cell;
use std::ptr::NonNull; use std::ptr::NonNull;
use bumpalo::{Bump, boxed::Box}; use bumpalo::{Bump, boxed::Box};
@@ -7,13 +6,12 @@ use itertools::Itertools;
use petgraph::graphmap::DiGraphMap; use petgraph::graphmap::DiGraphMap;
use nixjit_builtins::{ use nixjit_builtins::{
Builtins, BuiltinsContext, builtins::{GLOBAL_LEN, SCOPED_LEN}, BuiltinFn, Builtins, BuiltinsContext
builtins::{GLOBAL_LEN, SCOPED_LEN},
}; };
use nixjit_error::{Error, Result}; use nixjit_error::{Error, Result};
use nixjit_eval::{Args, EvalContext, Value}; use nixjit_eval::{Args, EvalContext, Value};
use nixjit_hir::{DowngradeContext, Hir}; use nixjit_hir::{DowngradeContext, Hir};
use nixjit_ir::{AttrSet, ExprId, Param, PrimOpId, StackIdx}; use nixjit_ir::{AttrSet, ExprId, Param, PrimOpId};
use nixjit_lir::Lir; use nixjit_lir::Lir;
use crate::downgrade::DowngradeCtx; use crate::downgrade::DowngradeCtx;
@@ -39,16 +37,23 @@ pub struct Context<'bump> {
global_scope: NonNull<HashMap<&'static str, ExprId>>, global_scope: NonNull<HashMap<&'static str, ExprId>>,
/// A dependency graph between expressions. /// A dependency graph between expressions.
graph: DiGraphMap<ExprId, StackIdx>, graph: DiGraphMap<ExprId, ()>,
/// A table of primitive operation implementations. /// A table of primitive operation implementations.
primops: [(usize, fn(&mut Self, Args) -> Result<Value>); GLOBAL_LEN + SCOPED_LEN], primops: [(usize, BuiltinFn<Self>); GLOBAL_LEN + SCOPED_LEN],
bump: &'bump Bump, bump: &'bump Bump,
} }
impl Drop for Context<'_> { impl Drop for Context<'_> {
fn drop(&mut self) { fn drop(&mut self) {
// SAFETY: `repl_scope` and `global_scope` are `NonNull` pointers to `HashMap`s
// allocated within the `bump` arena. Because `NonNull` does not convey ownership,
// Rust's drop checker will not automatically drop the pointed-to `HashMap`s when
// the `Context` is dropped. We must manually call `drop_in_place` to ensure
// their destructors are run. This is safe because these pointers are guaranteed
// to be valid and non-null for the lifetime of the `Context`, as they are
// initialized in `new()` and never deallocated or changed.
unsafe { unsafe {
self.repl_scope.drop_in_place(); self.repl_scope.drop_in_place();
self.global_scope.drop_in_place(); self.global_scope.drop_in_place();
@@ -62,10 +67,18 @@ impl<'bump> Context<'bump> {
let global_scope = global let global_scope = global
.iter() .iter()
.enumerate() .enumerate()
.map(|(idx, (k, _, _))| (*k, unsafe { ExprId::from_raw(idx) })) .map(|(idx, (k, _, _))| {
.chain(core::iter::once(("builtins", unsafe { // SAFETY: The index `idx` comes from `enumerate()` on the `global` array,
ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN) // so it is guaranteed to be a valid, unique index for a primop LIR.
}))) (*k, unsafe { ExprId::from_raw(idx) })
})
.chain(core::iter::once((
"builtins",
// SAFETY: This ID corresponds to the `builtins` attrset LIR, which is
// constructed and placed after all the global and scoped primop LIRs.
// The index is calculated to be exactly at that position.
unsafe { ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN) },
)))
.collect(); .collect();
let primops = global let primops = global
.iter() .iter()
@@ -74,30 +87,48 @@ impl<'bump> Context<'bump> {
.collect_array() .collect_array()
.unwrap(); .unwrap();
let lirs = (0..global.len()) let lirs = (0..global.len())
.map(|idx| Lir::PrimOp(unsafe { PrimOpId::from_raw(idx) })) .map(|idx| {
.chain( // SAFETY: The index `idx` is guaranteed to be within the bounds of the
(0..scoped.len()) // `global` primops array, making it a valid raw ID for a `PrimOpId`.
.map(|idx| Lir::PrimOp(unsafe { PrimOpId::from_raw(idx + GLOBAL_LEN) })), Lir::PrimOp(unsafe { PrimOpId::from_raw(idx) })
) })
.chain((0..scoped.len()).map(|idx| {
// SAFETY: The index `idx` is within the bounds of the `scoped` primops
// array. Adding `GLOBAL_LEN` correctly offsets it to its position in
// the combined `primops` table.
Lir::PrimOp(unsafe { PrimOpId::from_raw(idx + GLOBAL_LEN) })
}))
.chain(core::iter::once(Lir::AttrSet(AttrSet { .chain(core::iter::once(Lir::AttrSet(AttrSet {
stcs: global stcs: global
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(idx, (name, ..))| (name.to_string(), unsafe { ExprId::from_raw(idx) })) .map(|(idx, (name, ..))| {
// SAFETY: `idx` from `enumerate` is a valid index for the LIR
// corresponding to this global primop.
(name.to_string(), unsafe { ExprId::from_raw(idx) })
})
.chain(scoped.into_iter().enumerate().map(|(idx, (name, ..))| { .chain(scoped.into_iter().enumerate().map(|(idx, (name, ..))| {
// SAFETY: `idx + GLOBAL_LEN` is a valid index for the LIR
// corresponding to this scoped primop.
(name.to_string(), unsafe { (name.to_string(), unsafe {
ExprId::from_raw(idx + GLOBAL_LEN) ExprId::from_raw(idx + GLOBAL_LEN)
}) })
})) }))
.chain(core::iter::once(("builtins".to_string(), unsafe { .chain(core::iter::once((
ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN + 1) "builtins".to_string(),
}))) // SAFETY: This ID points to the `Thunk` that wraps this very
// `AttrSet`. The index is calculated to be one position after
// the `AttrSet` itself.
unsafe { ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN + 1) },
)))
.collect(), .collect(),
..AttrSet::default() ..AttrSet::default()
}))) })))
.chain(core::iter::once(Lir::Thunk(unsafe { .chain(core::iter::once(Lir::Thunk(
ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN) // SAFETY: This ID points to the `builtins` `AttrSet` defined just above.
}))) // Its index is calculated to be at that exact position.
unsafe { ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN) },
)))
.map(|lir| Box::new_in(lir, bump)) .map(|lir| Box::new_in(lir, bump))
.collect_vec(); .collect_vec();
Self { Self {
@@ -144,8 +175,9 @@ impl<'bump> Context<'bump> {
let root = self let root = self
.downgrade_ctx() .downgrade_ctx()
.downgrade_root(root.tree().expr().unwrap())?; .downgrade_root(root.tree().expr().unwrap())?;
self.resolve_ctx(root).resolve_root()?; let ctx = self.resolve_ctx(root);
Ok(self.eval_ctx().eval_root(root)?.to_public()) ctx.resolve_root()?;
Ok(self.eval_ctx().eval(root)?.to_public())
} }
pub fn add_binding(&mut self, ident: &str, expr: &str) -> Result<()> { pub fn add_binding(&mut self, ident: &str, expr: &str) -> Result<()> {
@@ -157,6 +189,10 @@ impl<'bump> Context<'bump> {
.unwrap(); .unwrap();
let expr_id = self.downgrade_ctx().downgrade_root(root_expr)?; let expr_id = self.downgrade_ctx().downgrade_root(root_expr)?;
self.resolve_ctx(expr_id).resolve_root()?; self.resolve_ctx(expr_id).resolve_root()?;
// SAFETY: `repl_scope` is a `NonNull` pointer that is guaranteed to be valid
// for the lifetime of `Context`. It is initialized in `new()` and the memory
// it points to is managed by the `bump` arena. Therefore, it is safe to
// dereference it to a mutable reference here.
unsafe { self.repl_scope.as_mut() }.insert(ident.to_string(), expr_id); unsafe { self.repl_scope.as_mut() }.insert(ident.to_string(), expr_id);
Ok(()) Ok(())
} }
@@ -165,20 +201,15 @@ impl<'bump> Context<'bump> {
impl Context<'_> { impl Context<'_> {
fn alloc_id(&mut self) -> ExprId { fn alloc_id(&mut self) -> ExprId {
self.ir_count += 1; self.ir_count += 1;
// SAFETY: This function is the sole source of new `ExprId`s during the
// downgrade and resolve phases. By monotonically incrementing `ir_count`,
// we guarantee that each ID is unique and corresponds to a valid, soon-to-be-
// allocated slot in the IR vectors.
unsafe { ExprId::from_raw(self.ir_count - 1) } unsafe { ExprId::from_raw(self.ir_count - 1) }
} }
fn add_dep(&mut self, from: ExprId, to: ExprId, count: &Cell<usize>) -> StackIdx { fn add_dep(&mut self, from: ExprId, to: ExprId) {
if let Some(&idx) = self.graph.edge_weight(from, to) { self.graph.add_edge(from, to, ());
idx
} else {
let idx = count.get();
count.set(idx + 1);
let idx = unsafe { StackIdx::from_raw(idx) };
assert_ne!(from, to);
self.graph.add_edge(from, to, idx);
idx
}
} }
} }

View File

@@ -1,25 +1,26 @@
use std::cell::{Cell, RefCell}; use std::{cell::RefCell, ptr::NonNull};
use bumpalo::boxed::Box; use bumpalo::{boxed::Box, collections::Vec};
use derive_more::Unwrap; use derive_more::{Constructor, Unwrap};
use hashbrown::HashMap; use hashbrown::HashMap;
use replace_with::replace_with_and_return;
use nixjit_error::Result; use nixjit_error::Result;
use nixjit_hir::Hir; use nixjit_hir::Hir;
use nixjit_ir::{Const, ExprId, Param, StackIdx}; use nixjit_ir::{Const, ExprId, Param, StackIdx};
use nixjit_lir::{Lir, LookupResult, Resolve, ResolveContext}; use nixjit_lir::{Lir, LookupResult, Resolve, ResolveContext};
use replace_with::replace_with_and_return;
use super::Context; use super::Context;
#[derive(Clone)] #[derive(Clone)]
enum Scope<'ctx> { enum Scope {
/// A `let` binding scope, mapping variable names to their expression IDs. /// A `let` binding scope, mapping variable names to their expression IDs.
Let(HashMap<String, ExprId>), Let(HashMap<String, ExprId>),
/// A function argument scope. `Some` holds the name of the argument set if present. /// A function argument scope. `Some` holds the name of the argument set if present.
Arg(Option<String>), Arg(Option<String>),
Builtins(&'ctx HashMap<&'static str, ExprId>), // Not using &'ctx HashMap<_, _> because bumpalo's Vec<'bump, T> is invariant over T.
Repl(&'ctx HashMap<String, ExprId>), Builtins(NonNull<HashMap<&'static str, ExprId>>),
Repl(NonNull<HashMap<String, ExprId>>),
} }
/// Represents an expression at different stages of compilation. /// Represents an expression at different stages of compilation.
@@ -31,29 +32,18 @@ enum Ir {
Lir(Lir), Lir(Lir),
} }
#[derive(Constructor)]
struct Closure { struct Closure {
id: ExprId, id: ExprId,
arg: ExprId, arg: ExprId,
deps: Cell<usize>
}
impl Closure {
fn new(id: ExprId, arg: ExprId) -> Self {
Self {
id,
arg,
deps: Cell::new(0)
}
}
} }
pub struct ResolveCtx<'ctx, 'bump> { pub struct ResolveCtx<'ctx, 'bump> {
ctx: &'ctx mut Context<'bump>, ctx: &'ctx mut Context<'bump>,
irs: Vec<Box<'bump, RefCell<Ir>>>, irs: Vec<'bump, RefCell<Ir>>,
root: ExprId, root: ExprId,
root_deps: Cell<usize>, closures: Vec<'bump, Closure>,
closures: Vec<Closure>, scopes: Vec<'bump, Scope>,
scopes: Vec<Scope<'ctx>>,
has_with: bool, has_with: bool,
with_used: bool, with_used: bool,
} }
@@ -61,37 +51,38 @@ pub struct ResolveCtx<'ctx, 'bump> {
impl<'ctx, 'bump> ResolveCtx<'ctx, 'bump> { impl<'ctx, 'bump> ResolveCtx<'ctx, 'bump> {
pub fn new(ctx: &'ctx mut Context<'bump>, root: ExprId) -> Self { pub fn new(ctx: &'ctx mut Context<'bump>, root: ExprId) -> Self {
Self { Self {
scopes: vec![ scopes: {
Scope::Builtins(unsafe { ctx.global_scope.as_ref() }), let mut vec = Vec::new_in(ctx.bump);
Scope::Repl(unsafe { ctx.repl_scope.as_ref() }), vec.push(Scope::Builtins(ctx.global_scope));
], vec.push(Scope::Repl(ctx.repl_scope));
vec
},
has_with: false, has_with: false,
with_used: false, with_used: false,
irs: core::mem::take(&mut ctx.hirs) irs: Vec::from_iter_in(
core::mem::take(&mut ctx.hirs)
.into_iter() .into_iter()
.map(|hir| Ir::Hir(hir).into()) .map(Ir::Hir)
.map(|ir| Box::new_in(ir, ctx.bump)) .map(RefCell::new),
.collect(), ctx.bump,
),
closures: Vec::new_in(ctx.bump),
ctx, ctx,
root, root,
root_deps: Cell::new(0),
closures: Vec::new(),
} }
} }
pub fn resolve_root(mut self) -> Result<()> { pub fn resolve_root(mut self) -> Result<()> {
let ret = self.resolve(self.root); let ret = self.resolve(self.root);
if ret.is_ok() { ret.map(|_| {
self.ctx.lirs.extend( self.ctx.lirs.extend(
self.irs self.irs
.into_iter() .into_iter()
.map(Box::into_inner)
.map(RefCell::into_inner) .map(RefCell::into_inner)
.map(Ir::unwrap_lir) .map(Ir::unwrap_lir)
.map(|lir| Box::new_in(lir, self.ctx.bump)), .map(|lir| Box::new_in(lir, self.ctx.bump)),
); );
} })
ret
} }
fn get_ir(&self, id: ExprId) -> &RefCell<Ir> { fn get_ir(&self, id: ExprId) -> &RefCell<Ir> {
@@ -102,21 +93,15 @@ impl<'ctx, 'bump> ResolveCtx<'ctx, 'bump> {
unsafe { self.irs.get_unchecked(idx) } unsafe { self.irs.get_unchecked(idx) }
} }
} }
fn new_lir(&mut self, lir: Lir) -> ExprId {
self.irs
.push(Box::new_in(RefCell::new(Ir::Lir(lir)), self.ctx.bump));
self.ctx.alloc_id()
}
} }
impl ResolveContext for ResolveCtx<'_, '_> { impl ResolveContext for ResolveCtx<'_, '_> {
fn resolve(&mut self, expr: ExprId) -> Result<()> { fn resolve(&mut self, expr: ExprId) -> Result<ExprId> {
let result = unsafe { unsafe {
let ctx = &mut *(self as *mut Self); let ctx = &mut *(self as *mut Self);
let ir = self.get_ir(expr); let ir = self.get_ir(expr);
if !matches!(ir.try_borrow().as_deref(), Ok(Ir::Hir(_))) { if !matches!(ir.try_borrow().as_deref(), Ok(Ir::Hir(_))) {
return Ok(()); return Ok(expr);
} }
replace_with_and_return( replace_with_and_return(
&mut *ir.borrow_mut(), &mut *ir.borrow_mut(),
@@ -126,7 +111,8 @@ impl ResolveContext for ResolveCtx<'_, '_> {
})) }))
}, },
|ir| match ir.unwrap_hir().resolve(ctx) { |ir| match ir.unwrap_hir().resolve(ctx) {
Ok(lir) => (Ok(()), Ir::Lir(lir)), Ok(lir @ Lir::ExprRef(expr)) => (Ok(expr), Ir::Lir(lir)),
Ok(lir) => (Ok(expr), Ir::Lir(lir)),
Err(err) => ( Err(err) => (
Err(err), Err(err),
Ir::Hir(Hir::Const(Const { Ir::Hir(Hir::Const(Const {
@@ -135,44 +121,44 @@ impl ResolveContext for ResolveCtx<'_, '_> {
), ),
}, },
) )
}; }
result
} }
fn lookup(&mut self, name: &str) -> LookupResult { fn lookup(&mut self, name: &str) -> LookupResult {
let mut closure_depth = 0; let mut closure_depth = 0;
// Then search from outer to inner scopes for dependencies
for scope in self.scopes.iter().rev() { for scope in self.scopes.iter().rev() {
match scope { match scope {
Scope::Builtins(scope) => { Scope::Builtins(scope) => {
if let Some(&primop) = scope.get(&name) { if let Some(&primop) = unsafe { scope.as_ref() }.get(&name) {
return LookupResult::PrimOp(primop); return LookupResult::Expr(primop);
} }
} }
Scope::Let(scope) | &Scope::Repl(scope) => { Scope::Let(scope) => {
if let Some(&dep) = scope.get(name) { if let Some(&dep) = scope.get(name) {
let (expr, deps) = self.closures.last().map_or_else(|| (self.root, &self.root_deps), |closure| (closure.id, &closure.deps)); let expr = self
let idx = self.ctx.add_dep(expr, dep, deps); .closures
return LookupResult::Stack(idx); .last()
.map_or_else(|| self.root, |closure| closure.id);
self.ctx.add_dep(expr, dep);
return LookupResult::Expr(dep);
}
}
&Scope::Repl(scope) => {
if let Some(&dep) = unsafe { scope.as_ref() }.get(name) {
let expr = self
.closures
.last()
.map_or_else(|| self.root, |closure| closure.id);
self.ctx.add_dep(expr, dep);
return LookupResult::Expr(dep);
} }
} }
Scope::Arg(ident) => { Scope::Arg(ident) => {
if ident.as_deref() == Some(name) { if ident.as_deref() == Some(name) {
// This is an outer function's parameter, treat as dependency let &Closure { id: func, arg } =
// We need to find the corresponding parameter expression to create dependency self.closures.iter().nth_back(closure_depth).unwrap();
// For now, we need to handle this case by creating a dependency to the parameter self.ctx.add_dep(func, arg);
let mut iter = self.closures.iter().rev().take(closure_depth + 1).rev(); return LookupResult::Expr(arg);
let Closure { id: func, arg, deps: count } = iter.next().unwrap();
let mut cur = self.ctx.add_dep(*func, *arg, count);
for Closure { id: func, deps: count, .. } in iter {
self.irs.push(Box::new_in(
RefCell::new(Ir::Lir(Lir::StackRef(cur))),
self.ctx.bump,
));
let idx = self.ctx.alloc_id();
cur = self.ctx.add_dep(*func, idx, count);
}
return LookupResult::Stack(cur);
} }
closure_depth += 1; closure_depth += 1;
} }
@@ -186,9 +172,8 @@ impl ResolveContext for ResolveCtx<'_, '_> {
} }
} }
fn lookup_arg(&mut self) -> StackIdx { fn lookup_arg(&mut self) -> ExprId {
let Closure { id: func, arg, deps } = self.closures.last().unwrap(); self.closures.last().unwrap().arg
self.ctx.add_dep(*func, *arg, deps)
} }
fn new_func(&mut self, body: ExprId, param: Param) { fn new_func(&mut self, body: ExprId, param: Param) {
@@ -206,23 +191,23 @@ impl ResolveContext for ResolveCtx<'_, '_> {
res res
} }
fn with_with_env<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T) { fn with_with_env(&mut self, f: impl FnOnce(&mut Self) -> Result<()>) -> Result<bool> {
let has_with = self.has_with; let has_with = self.has_with;
let with_used = self.with_used; let with_used = self.with_used;
self.has_with = true; self.has_with = true;
self.with_used = false; self.with_used = false;
let res = f(self); let res = f(self);
self.has_with = has_with; self.has_with = has_with;
(core::mem::replace(&mut self.with_used, with_used), res) res.map(|_| core::mem::replace(&mut self.with_used, with_used))
} }
fn with_closure_env<T>( fn with_closure_env<T>(
&mut self, &mut self,
func: ExprId, func: ExprId,
arg: ExprId,
ident: Option<String>, ident: Option<String>,
f: impl FnOnce(&mut Self) -> T, f: impl FnOnce(&mut Self) -> T,
) -> T { ) -> T {
let arg = self.new_lir(Lir::Arg(nixjit_ir::Arg));
self.closures.push(Closure::new(func, arg)); self.closures.push(Closure::new(func, arg));
self.scopes.push(Scope::Arg(ident)); self.scopes.push(Scope::Arg(ident));
let res = f(self); let res = f(self);

View File

@@ -101,7 +101,7 @@ impl std::fmt::Display for Error {
write!(f, "\n --> {}:{}", start_line, start_col)?; write!(f, "\n --> {}:{}", start_line, start_col)?;
write!(f, "\n |\n")?; write!(f, "\n |\n")?;
write!(f, "{:4} | {}\n", start_line, line_str)?; writeln!(f, "{:4} | {}", start_line, line_str)?;
write!( write!(
f, f,
" | {}{}", " | {}{}",

View File

@@ -12,9 +12,9 @@ use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use nixjit_error::{Error, Result}; use nixjit_error::{Error, Result};
use nixjit_ir::{self as ir, ExprId, PrimOpId, StackIdx}; use nixjit_ir::{self as ir, ExprId, PrimOpId, SymId};
use nixjit_lir as lir; use nixjit_lir as lir;
use nixjit_value::{Const, format_symbol}; use nixjit_value::format_symbol;
pub use crate::value::*; pub use crate::value::*;
@@ -22,12 +22,13 @@ mod value;
/// A trait defining the context in which LIR expressions are evaluated. /// A trait defining the context in which LIR expressions are evaluated.
pub trait EvalContext { pub trait EvalContext {
fn eval_root(self, expr: ExprId) -> Result<Value>; fn eval(&mut self, id: ExprId) -> Result<Value>;
/// Evaluates an expression by its ID. fn resolve(&mut self, id: ExprId) -> Result<ValueId>;
fn eval(&mut self, expr: ExprId) -> Result<Value>;
fn call(&mut self, func: ExprId, arg: Option<Value>, frame: StackFrame) -> Result<Value>; fn force(&mut self, id: ValueId) -> Result<Value>;
fn call(&mut self, func: ValueId, arg: Value) -> Result<Value>;
/// Enters a `with` scope for the duration of a closure's execution. /// Enters a `with` scope for the duration of a closure's execution.
fn with_with_env<T>( fn with_with_env<T>(
@@ -36,18 +37,14 @@ pub trait EvalContext {
f: impl FnOnce(&mut Self) -> T, f: impl FnOnce(&mut Self) -> T,
) -> T; ) -> T;
/// Looks up a stack slot on the current stack frame.
fn lookup_stack(&self, idx: StackIdx) -> &Value;
fn capture_stack(&self) -> &StackFrame;
/// Looks up an identifier in the current `with` scope chain. /// Looks up an identifier in the current `with` scope chain.
fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a Value>; fn lookup_with<'a>(&'a self, ident: SymId) -> Option<&'a Value>;
/// Calls a primitive operation (builtin) by its ID. /// Calls a primitive operation (builtin) by its ID.
fn call_primop(&mut self, id: PrimOpId, args: Args) -> Result<Value>; fn call_primop(&mut self, id: PrimOpId, args: Args) -> Result<Value>;
fn get_primop_arity(&self, id: PrimOpId) -> usize; fn new_sym(&mut self, sym: String) -> SymId;
fn get_sym(&self, id: SymId) -> &str;
} }
/// A trait for types that can be evaluated within an `EvalContext`. /// A trait for types that can be evaluated within an `EvalContext`.
@@ -83,18 +80,12 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for lir::Lir {
Str(x) => x.eval(ctx), Str(x) => x.eval(ctx),
Var(x) => x.eval(ctx), Var(x) => x.eval(ctx),
Path(x) => x.eval(ctx), Path(x) => x.eval(ctx),
&StackRef(idx) => {
let mut val = ctx.lookup_stack(idx).clone();
val.force(ctx)?;
Ok(val)
}
&ExprRef(expr) => ctx.eval(expr), &ExprRef(expr) => ctx.eval(expr),
&FuncRef(body) => Ok(Value::Closure( &FuncRef(body) => ctx.resolve(body).map(Value::Closure),
Closure::new(body, ctx.capture_stack().clone()).into(),
)),
&Arg(_) => unreachable!(), &Arg(_) => unreachable!(),
&PrimOp(primop) => Ok(Value::PrimOp(primop)), &PrimOp(primop) => Ok(Value::PrimOp(primop)),
&Thunk(id) => Ok(Value::Thunk(id)), &Thunk(id) => ctx.resolve(id).map(Value::Thunk),
&StackRef(idx) => todo!(),
} }
} }
} }
@@ -105,15 +96,17 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::AttrSet {
let mut attrs = AttrSet::new( let mut attrs = AttrSet::new(
self.stcs self.stcs
.iter() .iter()
.map(|(k, v)| { .map(|(&k, &v)| {
let eval_result = v.eval(ctx); let eval_result = v.eval(ctx);
Ok((k.clone(), eval_result?)) Ok((k, eval_result?))
}) })
.collect::<Result<_>>()?, .collect::<Result<_>>()?,
); );
for (k, v) in self.dyns.iter() { for (k, v) in self.dyns.iter() {
let v = v.eval(ctx)?; let v = v.eval(ctx)?;
attrs.push_attr(k.eval(ctx)?.force_string_no_ctx()?, v)?; let sym = k.eval(ctx)?.force_string_no_ctx()?;
let sym = ctx.new_sym(sym);
attrs.push_attr(sym, v, ctx)?;
} }
let result = Value::AttrSet(attrs.into()); let result = Value::AttrSet(attrs.into());
Ok(result) Ok(result)
@@ -138,9 +131,14 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::HasAttr {
fn eval(&self, ctx: &mut Ctx) -> Result<Value> { fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
use ir::Attr::*; use ir::Attr::*;
let mut val = self.lhs.eval(ctx)?; let mut val = self.lhs.eval(ctx)?;
val.has_attr(self.rhs.iter().map(|attr| match attr { val.has_attr(self.rhs.iter().map(|attr| {
Str(ident) => Ok(Value::String(ident.clone())), match attr {
Dynamic(expr) => expr.eval(ctx), &Str(ident) => Ok(ident),
Dynamic(expr) => expr
.eval(ctx)?
.force_string_no_ctx()
.map(|sym| ctx.new_sym(sym)),
}
}))?; }))?;
Ok(val) Ok(val)
} }
@@ -165,9 +163,9 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
} }
Mul => lhs.mul(rhs)?, Mul => lhs.mul(rhs)?,
Div => lhs.div(rhs)?, Div => lhs.div(rhs)?,
Eq => Value::eq(&mut lhs, rhs), Eq => lhs.eq(rhs),
Neq => { Neq => {
Value::eq(&mut lhs, rhs); lhs.eq(rhs);
let _ = lhs.not(); let _ = lhs.not();
} }
Lt => lhs.lt(rhs)?, Lt => lhs.lt(rhs)?,
@@ -182,12 +180,12 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
} }
Geq => { Geq => {
lhs.lt(rhs)?; lhs.lt(rhs)?;
let _ = lhs.not()?; let _ = lhs.not();
} }
And => lhs.and(rhs)?, And => lhs.and(rhs)?,
Or => lhs.or(rhs)?, Or => lhs.or(rhs)?,
Impl => { Impl => {
let _ = lhs.not(); lhs.not()?;
lhs.or(rhs)?; lhs.or(rhs)?;
} }
Con => lhs.concat(rhs)?, Con => lhs.concat(rhs)?,
@@ -226,12 +224,11 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Select {
use ir::Attr::*; use ir::Attr::*;
let mut val = self.expr.eval(ctx)?; let mut val = self.expr.eval(ctx)?;
for attr in self.attrpath.iter() { for attr in self.attrpath.iter() {
let name_val;
let name = match attr { let name = match attr {
Str(name) => name, &Str(name) => name,
Dynamic(expr) => { Dynamic(expr) => {
name_val = expr.eval(ctx)?; let sym = expr.eval(ctx)?.force_string_no_ctx()?;
&*name_val.force_string_no_ctx()? ctx.new_sym(sym)
} }
}; };
if let Some(default) = self.default { if let Some(default) = self.default {
@@ -338,11 +335,9 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Var {
/// Evaluates a `Var` by looking it up in the `with` scope chain. /// Evaluates a `Var` by looking it up in the `with` scope chain.
/// This is for variables that could not be resolved statically. /// This is for variables that could not be resolved statically.
fn eval(&self, ctx: &mut Ctx) -> Result<Value> { fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
ctx.lookup_with(&self.sym) ctx.lookup_with(self.sym).cloned().ok_or_else(|| {
.ok_or_else(|| { Error::eval_error(format!("undefined variable '{}'", format_symbol(ctx.get_sym(self.sym))))
Error::eval_error(format!("undefined variable '{}'", format_symbol(&self.sym)))
}) })
.map(|val| val.clone())
} }
} }

View File

@@ -10,7 +10,7 @@ use hashbrown::hash_map::Entry;
use itertools::Itertools; use itertools::Itertools;
use nixjit_error::{Error, Result}; use nixjit_error::{Error, Result};
use nixjit_ir::ExprId; use nixjit_ir::{ExprId, SymId};
use nixjit_value::{self as p, format_symbol}; use nixjit_value::{self as p, format_symbol};
use crate::EvalContext; use crate::EvalContext;
@@ -24,7 +24,7 @@ use super::Value;
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Constructor)] #[derive(Clone, Constructor)]
pub struct AttrSet { pub struct AttrSet {
data: HashMap<String, Value>, data: HashMap<SymId, Value>,
} }
impl Debug for AttrSet { impl Debug for AttrSet {
@@ -33,23 +33,23 @@ impl Debug for AttrSet {
write!(f, "{{ ")?; write!(f, "{{ ")?;
for (k, v) in self.data.iter() { for (k, v) in self.data.iter() {
match v { match v {
List(_) => write!(f, "{} = [ ... ]; ", format_symbol(k))?, List(_) => write!(f, "{:?} = [ ... ]; ", k)?,
AttrSet(_) => write!(f, "{} = {{ ... }}; ", format_symbol(k))?, AttrSet(_) => write!(f, "{:?} = {{ ... }}; ", k)?,
v => write!(f, "{} = {v:?}; ", format_symbol(k))?, v => write!(f, "{:?} = {v:?}; ", k)?,
} }
} }
write!(f, "}}") write!(f, "}}")
} }
} }
impl From<HashMap<String, Value>> for AttrSet { impl From<HashMap<SymId, Value>> for AttrSet {
fn from(data: HashMap<String, Value>) -> Self { fn from(data: HashMap<SymId, Value>) -> Self {
Self { data } Self { data }
} }
} }
impl Deref for AttrSet { impl Deref for AttrSet {
type Target = HashMap<String, Value>; type Target = HashMap<SymId, Value>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.data &self.data
} }
@@ -64,16 +64,16 @@ impl AttrSet {
} }
/// Inserts an attribute, overwriting any existing attribute with the same name. /// Inserts an attribute, overwriting any existing attribute with the same name.
pub fn push_attr_force(&mut self, sym: String, val: Value) { pub fn push_attr_force(&mut self, sym: SymId, val: Value) {
self.data.insert(sym, val); self.data.insert(sym, val);
} }
/// Inserts an attribute, returns an error if the attribute is already defined. /// Inserts an attribute, returns an error if the attribute is already defined.
pub fn push_attr(&mut self, sym: String, val: Value) -> Result<()> { pub fn push_attr(&mut self, sym: SymId, val: Value, ctx: &mut impl EvalContext) -> Result<()> {
match self.data.entry(sym) { match self.data.entry(sym) {
Entry::Occupied(occupied) => Err(Error::eval_error(format!( Entry::Occupied(occupied) => Err(Error::eval_error(format!(
"attribute '{}' already defined", "attribute '{}' already defined",
format_symbol(occupied.key()) format_symbol(ctx.get_sym(*occupied.key()))
))), ))),
Entry::Vacant(vacant) => { Entry::Vacant(vacant) => {
vacant.insert(val); vacant.insert(val);
@@ -82,29 +82,29 @@ impl AttrSet {
} }
} }
pub fn select(&self, name: &str, ctx: &mut impl EvalContext) -> Result<Value> { pub fn select(&self, name: SymId, ctx: &mut impl EvalContext) -> Result<Value> {
self.data self.data
.get(name) .get(&name)
.cloned() .cloned()
.map(|attr| match attr { .map(|attr| match attr {
Value::Thunk(id) => ctx.eval(id), Value::Thunk(id) => ctx.force(id),
val => Ok(val), val => Ok(val),
}) })
.ok_or_else(|| { .ok_or_else(|| {
Error::eval_error(format!("attribute '{}' not found", format_symbol(name))) Error::eval_error(format!("attribute '{}' not found", format_symbol(ctx.get_sym(name))))
})? })?
} }
pub fn select_or( pub fn select_or(
&self, &self,
name: &str, name: SymId,
default: ExprId, default: ExprId,
ctx: &mut impl EvalContext, ctx: &mut impl EvalContext,
) -> Result<Value> { ) -> Result<Value> {
self.data self.data
.get(name) .get(&name)
.map(|attr| match attr { .map(|attr| match attr {
&Value::Thunk(id) => ctx.eval(id), &Value::Thunk(id) => ctx.force(id),
val => Ok(val.clone()), val => Ok(val.clone()),
}) })
.unwrap_or_else(|| ctx.eval(default)) .unwrap_or_else(|| ctx.eval(default))
@@ -113,19 +113,19 @@ impl AttrSet {
/// Checks if an attribute path exists within the set. /// Checks if an attribute path exists within the set.
pub fn has_attr( pub fn has_attr(
&self, &self,
mut path: impl DoubleEndedIterator<Item = Result<Value>>, mut path: impl DoubleEndedIterator<Item = Result<SymId>>,
) -> Result<Value> { ) -> Result<Value> {
let mut data = &self.data; let mut data = &self.data;
let last = path.nth_back(0).unwrap(); let last = path.nth_back(0).unwrap();
for item in path { for item in path {
let Some(Value::AttrSet(attrs)) = data.get(&item.unwrap().force_string_no_ctx()?) let Some(Value::AttrSet(attrs)) = data.get(&item?)
else { else {
return Ok(Value::Bool(false)); return Ok(Value::Bool(false));
}; };
data = attrs.as_inner(); data = attrs.as_inner();
} }
Ok(Value::Bool( Ok(Value::Bool(
data.get(&last.unwrap().force_string_no_ctx()?).is_some(), data.get(&last?).is_some(),
)) ))
} }
@@ -138,24 +138,18 @@ impl AttrSet {
} }
/// Returns a reference to the inner `HashMap`. /// Returns a reference to the inner `HashMap`.
pub fn as_inner(&self) -> &HashMap<String, Value> { pub fn as_inner(&self) -> &HashMap<SymId, Value> {
&self.data &self.data
} }
/// Converts an `Rc<AttrSet>` to an `Rc<HashMap<String, Value>>` without allocation. /// Converts an `Rc<AttrSet>` to an `Rc<HashMap<String, Value>>` without allocation.
///
/// # Safety
///
/// This is safe because `AttrSet` is `#[repr(transparent)]`.
pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<String, Value>> { pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<String, Value>> {
// SAFETY: This is safe because `AttrSet` is `#[repr(transparent)]` over
// `HashMap<String, Value>`, so `Rc<Self>` has the same layout as
// `Rc<HashMap<String, Value>>`.
unsafe { core::mem::transmute(self) } unsafe { core::mem::transmute(self) }
} }
/// Creates an `AttrSet` from a `HashMap`.
pub fn from_inner(data: HashMap<String, Value>) -> Self {
Self { data }
}
/// Performs a deep equality comparison between two `AttrSet`s. /// Performs a deep equality comparison between two `AttrSet`s.
/// ///
/// It recursively compares the contents of both sets, ensuring that both keys /// It recursively compares the contents of both sets, ensuring that both keys
@@ -171,11 +165,11 @@ impl AttrSet {
} }
/// Converts the `AttrSet` to its public-facing representation. /// Converts the `AttrSet` to its public-facing representation.
pub fn to_public(self) -> p::Value { pub fn to_public(self, ctx: &mut impl EvalContext) -> p::Value {
p::Value::AttrSet(p::AttrSet::new( p::Value::AttrSet(p::AttrSet::new(
self.data self.data
.into_iter() .into_iter()
.map(|(sym, value)| (sym.into(), value.to_public())) .map(|(sym, value)| (ctx.get_sym(sym).into(), value.to_public(ctx)))
.collect(), .collect(),
)) ))
} }

View File

@@ -1,29 +0,0 @@
//! Defines the runtime representation of a partially applied function.
use std::rc::Rc;
use derive_more::Constructor;
use nixjit_error::Result;
use nixjit_ir::ExprId;
use super::Value;
use crate::EvalContext;
pub type StackFrame = smallvec::SmallVec<[Value; 5]>;
#[derive(Debug, Clone, Constructor)]
pub struct Closure {
pub body: ExprId,
pub frame: StackFrame,
}
impl Closure {
pub fn call<Ctx: EvalContext>(
self: Rc<Self>,
arg: Option<Value>,
ctx: &mut Ctx,
) -> Result<Value> {
let Self { body: func, frame } = Rc::unwrap_or_clone(self);
ctx.call(func, arg, frame)
}
}

View File

@@ -70,7 +70,7 @@ impl List {
self.data self.data
.get(idx) .get(idx)
.map(|elem| match elem { .map(|elem| match elem {
&Value::Thunk(id) => ctx.eval(id), &Value::Thunk(id) => ctx.force(id),
val => Ok(val.clone()), val => Ok(val.clone()),
}) })
.ok_or_else(|| { .ok_or_else(|| {
@@ -93,11 +93,11 @@ impl List {
} }
/// Converts the `List` to its public-facing representation. /// Converts the `List` to its public-facing representation.
pub fn to_public(&self) -> PubValue { pub fn to_public(&self, ctx: &mut impl EvalContext) -> PubValue {
PubValue::List(PubList::new( PubValue::List(PubList::new(
self.data self.data
.iter() .iter()
.map(|value| value.clone().to_public()) .map(|value| value.clone().to_public(ctx))
.collect(), .collect(),
)) ))
} }

View File

@@ -13,6 +13,7 @@ use nixjit_ir::ExprId;
use nixjit_ir::PrimOpId; use nixjit_ir::PrimOpId;
use nixjit_error::{Error, Result}; use nixjit_error::{Error, Result};
use nixjit_ir::SymId;
use nixjit_value::Const; use nixjit_value::Const;
use nixjit_value::Value as PubValue; use nixjit_value::Value as PubValue;
use replace_with::replace_with_and_return; use replace_with::replace_with_and_return;
@@ -21,13 +22,11 @@ use smallvec::smallvec;
use crate::EvalContext; use crate::EvalContext;
mod attrset; mod attrset;
mod closure;
mod list; mod list;
mod primop; mod primop;
mod string; mod string;
pub use attrset::AttrSet; pub use attrset::AttrSet;
pub use closure::*;
pub use list::List; pub use list::List;
pub use primop::*; pub use primop::*;
@@ -45,14 +44,12 @@ pub enum Value {
Bool(bool) = Self::BOOL, Bool(bool) = Self::BOOL,
String(String) = Self::STRING, String(String) = Self::STRING,
Null = Self::NULL, Null = Self::NULL,
Thunk(ExprId) = Self::THUNK, Thunk(ValueId) = Self::THUNK,
ClosureThunk(Rc<Closure>) = Self::CLOSURE_THUNK,
AttrSet(Rc<AttrSet>) = Self::ATTRSET, AttrSet(Rc<AttrSet>) = Self::ATTRSET,
List(Rc<List>) = Self::LIST, List(Rc<List>) = Self::LIST,
PrimOp(PrimOpId) = Self::PRIMOP, PrimOp(PrimOpId) = Self::PRIMOP,
PrimOpApp(Rc<PrimOpApp>) = Self::PRIMOP_APP, PrimOpApp(Rc<PrimOpApp>) = Self::PRIMOP_APP,
Closure(Rc<Closure>) = Self::CLOSURE, Closure(ValueId) = Self::CLOSURE,
Blackhole,
} }
impl Debug for Value { impl Debug for Value {
@@ -67,11 +64,9 @@ impl Debug for Value {
AttrSet(x) => write!(f, "{x:?}"), AttrSet(x) => write!(f, "{x:?}"),
List(x) => write!(f, "{x:?}"), List(x) => write!(f, "{x:?}"),
Thunk(thunk) => write!(f, "<THUNK {thunk:?}>"), Thunk(thunk) => write!(f, "<THUNK {thunk:?}>"),
ClosureThunk(_) => write!(f, "<THUNK>"), Closure(func) => write!(f, "<LAMBDA-APP {:?}>", func),
Closure(func) => write!(f, "<LAMBDA-APP {:?}>", func.body),
PrimOp(_) => write!(f, "<PRIMOP>"), PrimOp(_) => write!(f, "<PRIMOP>"),
PrimOpApp(_) => write!(f, "<PRIMOP-APP>"), PrimOpApp(_) => write!(f, "<PRIMOP-APP>"),
Blackhole => write!(f, "<BLACKHOLE>"),
} }
} }
} }
@@ -129,13 +124,11 @@ impl Value {
String(_) => "string", String(_) => "string",
Null => "null", Null => "null",
Thunk(_) => "thunk", Thunk(_) => "thunk",
ClosureThunk(_) => "thunk",
AttrSet(_) => "set", AttrSet(_) => "set",
List(_) => "list", List(_) => "list",
PrimOp(_) => "lambda", PrimOp(_) => "lambda",
PrimOpApp(_) => "lambda", PrimOpApp(_) => "lambda",
Closure(..) => "lambda", Closure(..) => "lambda",
Blackhole => unreachable!(),
} }
} }
@@ -148,8 +141,7 @@ impl Value {
self, self,
|| Value::Null, || Value::Null,
|val| match val { |val| match val {
Value::Thunk(id) => map(ctx.eval(id)), Value::Thunk(id) => map(ctx.force(id)),
Value::ClosureThunk(thunk) => map(thunk.call(None, ctx)),
val => (Ok(()), val), val => (Ok(()), val),
}, },
) )
@@ -170,23 +162,9 @@ impl Value {
self, self,
|| Null, || Null,
|func| match func { |func| match func {
PrimOp(id) => { PrimOp(id) => map(ctx.call_primop(id, smallvec![arg])),
let arity = ctx.get_primop_arity(id); PrimOpApp(primop) => map(primop.call(arg, ctx)),
if arity == 1 { Closure(func) => map(ctx.call(func, arg)),
map(ctx.call_primop(id, smallvec![arg]))
} else {
(
Ok(()),
Value::PrimOpApp(Rc::new(self::PrimOpApp::new(
arity - 1,
id,
smallvec![arg],
))),
)
}
}
PrimOpApp(func) => map(func.call(arg, ctx)),
Closure(func) => map(func.call(Some(arg), ctx)),
_ => ( _ => (
Err(Error::eval_error( Err(Error::eval_error(
"attempt to call something which is not a function but ...".to_string(), "attempt to call something which is not a function but ...".to_string(),
@@ -240,10 +218,7 @@ impl Value {
} }
pub fn eq(&mut self, other: Self) { pub fn eq(&mut self, other: Self) {
use Value::Bool; *self = Value::Bool(self.eq_impl(&other));
*self = match (&*self, other) {
(s, other) => Bool(s.eq_impl(&other)),
};
} }
pub fn lt(&mut self, other: Self) -> Result<()> { pub fn lt(&mut self, other: Self) -> Result<()> {
@@ -378,7 +353,7 @@ impl Value {
} }
} }
pub fn select(&mut self, name: &str, ctx: &mut impl EvalContext) -> Result<()> { pub fn select(&mut self, name: SymId, ctx: &mut impl EvalContext) -> Result<()> {
use Value::*; use Value::*;
let val = match self { let val = match self {
AttrSet(attrs) => attrs.select(name, ctx), AttrSet(attrs) => attrs.select(name, ctx),
@@ -393,7 +368,7 @@ impl Value {
pub fn select_or<Ctx: EvalContext>( pub fn select_or<Ctx: EvalContext>(
&mut self, &mut self,
name: &str, name: SymId,
default: ExprId, default: ExprId,
ctx: &mut Ctx, ctx: &mut Ctx,
) -> Result<()> { ) -> Result<()> {
@@ -411,7 +386,7 @@ impl Value {
Ok(()) Ok(())
} }
pub fn has_attr(&mut self, path: impl DoubleEndedIterator<Item = Result<Value>>) -> Result<()> { pub fn has_attr(&mut self, path: impl DoubleEndedIterator<Item = Result<SymId>>) -> Result<()> {
use Value::*; use Value::*;
if let AttrSet(attrs) = self { if let AttrSet(attrs) = self {
let val = attrs.has_attr(path)?; let val = attrs.has_attr(path)?;
@@ -436,22 +411,46 @@ impl Value {
/// Converts the internal `Value` to its public-facing, serializable /// Converts the internal `Value` to its public-facing, serializable
/// representation from the `nixjit_value` crate. /// representation from the `nixjit_value` crate.
pub fn to_public(self) -> PubValue { pub fn to_public(self, ctx: &mut impl EvalContext) -> PubValue {
use Value::*; use Value::*;
match self { match self {
AttrSet(attrs) => Rc::unwrap_or_clone(attrs).to_public(), AttrSet(attrs) => Rc::unwrap_or_clone(attrs).to_public(ctx),
List(list) => Rc::unwrap_or_clone(list.clone()).to_public(), List(list) => Rc::unwrap_or_clone(list.clone()).to_public(ctx),
Int(x) => PubValue::Const(Const::Int(x)), Int(x) => PubValue::Const(Const::Int(x)),
Float(x) => PubValue::Const(Const::Float(x)), Float(x) => PubValue::Const(Const::Float(x)),
Bool(x) => PubValue::Const(Const::Bool(x)), Bool(x) => PubValue::Const(Const::Bool(x)),
String(x) => PubValue::String(x), String(x) => PubValue::String(x),
Null => PubValue::Const(Const::Null), Null => PubValue::Const(Const::Null),
Thunk(_) => PubValue::Thunk, Thunk(_) => PubValue::Thunk,
ClosureThunk(_) => PubValue::Thunk,
PrimOp(_) => PubValue::PrimOp, PrimOp(_) => PubValue::PrimOp,
PrimOpApp(_) => PubValue::PrimOpApp, PrimOpApp(_) => PubValue::PrimOpApp,
Closure(..) => PubValue::Func, Closure(..) => PubValue::Func,
Blackhole => unreachable!(),
} }
} }
} }
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct ValueId(usize);
impl ValueId {
/// Returns the raw `usize` index.
///
/// # Safety
///
/// The caller is responsible for using this index correctly and not causing out-of-bounds access.
#[inline(always)]
pub unsafe fn raw(self) -> usize {
self.0
}
/// Creates an `ExprId` from a raw `usize` index.
///
/// # Safety
///
/// The caller must ensure that the provided index is valid for the expression table.
#[inline(always)]
pub unsafe fn from_raw(id: usize) -> Self {
Self(id)
}
}

View File

@@ -18,8 +18,6 @@ pub type Args = smallvec::SmallVec<[Value; 2]>;
/// all, of its required arguments. /// all, of its required arguments.
#[derive(Debug, Clone, Constructor)] #[derive(Debug, Clone, Constructor)]
pub struct PrimOpApp { pub struct PrimOpApp {
/// The number of remaining arguments the primop expects.
arity: usize,
/// The unique ID of the primop. /// The unique ID of the primop.
id: PrimOpId, id: PrimOpId,
/// The arguments that have already been applied. /// The arguments that have already been applied.
@@ -27,20 +25,9 @@ pub struct PrimOpApp {
} }
impl PrimOpApp { impl PrimOpApp {
/// Applies more arguments to a partially applied primop.
///
/// If enough arguments are provided to satisfy the primop's arity, it is
/// executed. Otherwise, it returns a new `PrimOpApp` with the combined
/// arguments.
pub fn call(self: Rc<Self>, arg: Value, ctx: &mut impl EvalContext) -> Result<Value> { pub fn call(self: Rc<Self>, arg: Value, ctx: &mut impl EvalContext) -> Result<Value> {
let mut primop = Rc::unwrap_or_clone(self); let PrimOpApp { id, mut args } = Rc::unwrap_or_clone(self);
if primop.arity == 1 { args.push(arg);
primop.args.push(arg); ctx.call_primop(id, args)
ctx.call_primop(primop.id, primop.args)
} else {
primop.args.push(arg);
primop.arity -= 1;
Ok(Value::PrimOpApp(primop.into()))
}
} }
} }

View File

@@ -150,6 +150,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident { impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> { fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let sym = self.ident_token().unwrap().to_string(); let sym = self.ident_token().unwrap().to_string();
let sym = ctx.new_sym(sym);
Ok(ctx.new_expr(Var { sym }.to_hir())) Ok(ctx.new_expr(Var { sym }.to_hir()))
} }
} }
@@ -237,8 +238,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
let bindings = attrs.stcs.clone(); let bindings = attrs.stcs.clone();
let body = ctx.new_expr(attrs.to_hir()); let body = ctx.new_expr(attrs.to_hir());
let expr = ctx.new_expr(Let { bindings, body }.to_hir()); let expr = ctx.new_expr(Let { bindings, body }.to_hir());
let sym = ctx.new_sym("body".into());
// The result of a `legacy let` is the `body` attribute of the resulting set. // The result of a `legacy let` is the `body` attribute of the resulting set.
let attrpath = vec![Attr::Str("body".into())]; let attrpath = vec![Attr::Str(sym)];
Ok(ctx.new_expr( Ok(ctx.new_expr(
Select { Select {
expr, expr,
@@ -274,6 +276,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> { fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let param = downgrade_param(self.param().unwrap(), ctx)?; let param = downgrade_param(self.param().unwrap(), ctx)?;
let mut body = self.body().unwrap().downgrade(ctx)?; let mut body = self.body().unwrap().downgrade(ctx)?;
let arg = ctx.new_expr(Hir::Arg(()));
let ident; let ident;
let required; let required;
@@ -281,7 +284,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
match param { match param {
Param::Ident(id) => { Param::Ident(id) => {
// Simple case: `x: body` // Simple case: `x: body`
ident = Some(id); ident = Some(ctx.new_sym(id));
required = None; required = None;
allowed = None; allowed = None;
} }
@@ -291,35 +294,36 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
alias, alias,
} => { } => {
// Complex case: `{ a, b ? 2, ... }@args: body` // Complex case: `{ a, b ? 2, ... }@args: body`
ident = alias.clone(); let alias = alias.map(|sym| ctx.new_sym(sym));
ident = alias;
required = Some( required = Some(
formals formals
.iter() .iter()
.filter(|(_, default)| default.is_none()) .filter(|(_, default)| default.is_none())
.map(|(k, _)| k.clone()) .map(|(k, _)| ctx.new_sym(k.clone()))
.collect(), .collect(),
); );
allowed = if ellipsis { allowed = if ellipsis {
None // `...` means any attribute is allowed. None // `...` means any attribute is allowed.
} else { } else {
Some(formals.iter().map(|(k, _)| k.clone()).collect()) Some(formals.iter().map(|(k, _)| ctx.new_sym(k.clone())).collect())
}; };
// Desugar pattern matching in function arguments into a `let` expression. // Desugar pattern matching in function arguments into a `let` expression.
// For example, `({ a, b ? 2 }): a + b` is desugared into: // For example, `({ a, b ? 2 }): a + b` is desugared into:
// `arg: let a = arg.a; b = arg.b or 2; in a + b` // `arg: let a = arg.a; b = arg.b or 2; in a + b`
let arg = ctx.new_expr(Hir::Arg(Arg));
let mut bindings: HashMap<_, _> = formals let mut bindings: HashMap<_, _> = formals
.into_iter() .into_iter()
.map(|(k, default)| { .map(|(k, default)| {
// For each formal parameter, create a `Select` expression to extract it from the argument set. // For each formal parameter, create a `Select` expression to extract it from the argument set.
// `Arg` represents the raw argument (the attribute set) passed to the function. // `Arg` represents the raw argument (the attribute set) passed to the function.
let k = ctx.new_sym(k);
( (
k.clone(), k,
ctx.new_expr( ctx.new_expr(
Select { Select {
expr: arg, expr: arg,
attrpath: vec![Attr::Str(k.clone())], attrpath: vec![Attr::Str(k)],
default, default,
} }
.to_hir(), .to_hir(),
@@ -329,7 +333,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
.collect(); .collect();
// If there's an alias (`... }@alias`), bind the alias name to the raw argument set. // If there's an alias (`... }@alias`), bind the alias name to the raw argument set.
if let Some(alias) = alias { if let Some(alias) = alias {
bindings.insert(alias.clone(), arg); bindings.insert(alias, arg);
} }
// Wrap the original function body in the new `let` expression. // Wrap the original function body in the new `let` expression.
let let_ = Let { bindings, body }; let let_ = Let { bindings, body };
@@ -343,7 +347,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
allowed, allowed,
}; };
// The function's body and parameters are now stored directly in the `Func` node. // The function's body and parameters are now stored directly in the `Func` node.
Ok(ctx.new_expr(Func { body, param }.to_hir())) Ok(ctx.new_expr(Func { body, param, arg }.to_hir()))
} }
} }

View File

@@ -17,8 +17,7 @@ use hashbrown::HashMap;
use nixjit_error::{Error, Result}; use nixjit_error::{Error, Result};
use nixjit_ir::{ use nixjit_ir::{
Assert, Attr, AttrSet, BinOp, Call, ConcatStrings, Const, ExprId, Func, HasAttr, If, List, Assert, Attr, AttrSet, BinOp, Call, ConcatStrings, Const, ExprId, Func, HasAttr, If, List, Param as IrParam, Path, Select, Str, SymId, UnOp, Var, With
Param as IrParam, Path, Select, Str, UnOp, Var, With,
}; };
use nixjit_macros::ir; use nixjit_macros::ir;
use nixjit_value::format_symbol; use nixjit_value::format_symbol;
@@ -37,6 +36,10 @@ pub trait DowngradeContext {
/// Allocates a new HIR expression in the context and returns its ID. /// Allocates a new HIR expression in the context and returns its ID.
fn new_expr(&mut self, expr: Hir) -> ExprId; fn new_expr(&mut self, expr: Hir) -> ExprId;
fn new_sym(&mut self, sym: String) -> SymId;
fn get_sym(&self, id: SymId) -> &str;
/// Provides temporary mutable access to an expression. /// Provides temporary mutable access to an expression.
fn with_expr_mut<T>(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T; fn with_expr_mut<T>(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T;
@@ -81,17 +84,12 @@ ir! {
// Represents a path expression. // Represents a path expression.
Path, Path,
// Represents a `let ... in ...` binding. // Represents a `let ... in ...` binding.
Let { pub bindings: HashMap<String, ExprId>, pub body: ExprId }, Let { pub bindings: HashMap<SymId, ExprId>, pub body: ExprId },
// Represents a function argument lookup within the body of a function. // Represents a function argument lookup within the body of a function.
Arg, Arg(()),
Thunk(ExprId) Thunk(ExprId)
} }
/// A placeholder struct for the `Arg` HIR variant. It signifies that at this point
/// in the expression tree, we should be looking up a function argument.
#[derive(Debug)]
pub struct Arg;
/// A trait defining operations on attribute sets within the HIR. /// A trait defining operations on attribute sets within the HIR.
trait Attrs { trait Attrs {
/// Inserts a value into the attribute set at a given path. /// Inserts a value into the attribute set at a given path.
@@ -137,7 +135,7 @@ impl Attrs for AttrSet {
// This path segment exists but is not an attrset, which is an error. // This path segment exists but is not an attrset, which is an error.
Error::downgrade_error(format!( Error::downgrade_error(format!(
"attribute '{}' already defined but is not an attribute set", "attribute '{}' already defined but is not an attribute set",
format_symbol(ident) format_symbol(ctx.get_sym(ident))
)) ))
}) })
.and_then(|attrs| attrs._insert(path, name, value, ctx)) .and_then(|attrs| attrs._insert(path, name, value, ctx))
@@ -164,10 +162,10 @@ impl Attrs for AttrSet {
// This is the final attribute in the path, so insert the value here. // This is the final attribute in the path, so insert the value here.
match name { match name {
Attr::Str(ident) => { Attr::Str(ident) => {
if self.stcs.insert(ident.clone(), value).is_some() { if self.stcs.insert(ident, value).is_some() {
return Err(Error::downgrade_error(format!( return Err(Error::downgrade_error(format!(
"attribute '{}' already defined", "attribute '{}' already defined",
format_symbol(ident) format_symbol(ctx.get_sym(ident))
))); )));
} }
} }

View File

@@ -10,7 +10,7 @@ use nixjit_value::format_symbol;
use rnix::ast; use rnix::ast;
use nixjit_error::{Error, Result}; use nixjit_error::{Error, Result};
use nixjit_ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, Var}; use nixjit_ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, SymId, Var};
use crate::Hir; use crate::Hir;
@@ -121,7 +121,7 @@ pub fn downgrade_attrs(
pub fn downgrade_static_attrs( pub fn downgrade_static_attrs(
attrs: impl ast::HasEntry, attrs: impl ast::HasEntry,
ctx: &mut impl DowngradeContext, ctx: &mut impl DowngradeContext,
) -> Result<HashMap<String, ExprId>> { ) -> Result<HashMap<SymId, ExprId>> {
let entries = attrs.entries(); let entries = attrs.entries();
let mut attrs = AttrSet { let mut attrs = AttrSet {
stcs: HashMap::new(), stcs: HashMap::new(),
@@ -145,7 +145,7 @@ pub fn downgrade_static_attrs(
/// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope). /// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope).
pub fn downgrade_inherit( pub fn downgrade_inherit(
inherit: ast::Inherit, inherit: ast::Inherit,
stcs: &mut HashMap<String, ExprId>, stcs: &mut HashMap<SymId, ExprId>,
ctx: &mut impl DowngradeContext, ctx: &mut impl DowngradeContext,
) -> Result<()> { ) -> Result<()> {
// Downgrade the `from` expression if it exists. // Downgrade the `from` expression if it exists.
@@ -181,7 +181,7 @@ pub fn downgrade_inherit(
Entry::Occupied(occupied) => { Entry::Occupied(occupied) => {
return Err(Error::eval_error(format!( return Err(Error::eval_error(format!(
"attribute '{}' already defined", "attribute '{}' already defined",
format_symbol(occupied.key()) format_symbol(ctx.get_sym(*occupied.key()))
))); )));
} }
Entry::Vacant(vacant) => vacant.insert(ctx.new_expr(expr)), Entry::Vacant(vacant) => vacant.insert(ctx.new_expr(expr)),
@@ -196,15 +196,15 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
use ast::Attr::*; use ast::Attr::*;
use ast::InterpolPart::*; use ast::InterpolPart::*;
match attr { match attr {
Ident(ident) => Ok(Attr::Str(ident.to_string())), Ident(ident) => Ok(Attr::Str(ctx.new_sym(ident.to_string()))),
Str(string) => { Str(string) => {
let parts = string.normalized_parts(); let parts = string.normalized_parts();
if parts.is_empty() { if parts.is_empty() {
Ok(Attr::Str("".into())) Ok(Attr::Str(ctx.new_sym("".into())))
} else if parts.len() == 1 { } else if parts.len() == 1 {
// If the string has only one part, it's either a literal or a single interpolation. // If the string has only one part, it's either a literal or a single interpolation.
match parts.into_iter().next().unwrap() { match parts.into_iter().next().unwrap() {
Literal(ident) => Ok(Attr::Str(ident)), Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident))),
Interpolation(interpol) => { Interpolation(interpol) => {
Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(ctx)?)) Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(ctx)?))
} }

View File

@@ -27,6 +27,7 @@ impl ExprId {
/// Returns the raw `usize` index. /// Returns the raw `usize` index.
/// ///
/// # Safety /// # Safety
///
/// The caller is responsible for using this index correctly and not causing out-of-bounds access. /// The caller is responsible for using this index correctly and not causing out-of-bounds access.
#[inline(always)] #[inline(always)]
pub unsafe fn raw(self) -> usize { pub unsafe fn raw(self) -> usize {
@@ -36,6 +37,7 @@ impl ExprId {
/// Creates an `ExprId` from a raw `usize` index. /// Creates an `ExprId` from a raw `usize` index.
/// ///
/// # Safety /// # Safety
///
/// The caller must ensure that the provided index is valid for the expression table. /// The caller must ensure that the provided index is valid for the expression table.
#[inline(always)] #[inline(always)]
pub unsafe fn from_raw(id: usize) -> Self { pub unsafe fn from_raw(id: usize) -> Self {
@@ -43,6 +45,33 @@ impl ExprId {
} }
} }
/// A type-safe wrapper for an index into an symbol table.
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SymId(usize);
impl SymId {
/// Returns the raw `usize` index.
///
/// # Safety
///
/// The caller is responsible for using this index correctly and not causing out-of-bounds access.
#[inline(always)]
pub unsafe fn raw(self) -> usize {
self.0
}
/// Creates an `SymId` from a raw `usize` index.
///
/// # Safety
///
/// The caller must ensure that the provided index is valid for the symbol table.
#[inline(always)]
pub unsafe fn from_raw(id: usize) -> Self {
Self(id)
}
}
/// A type-safe wrapper for an index into a primop (builtin function) table. /// A type-safe wrapper for an index into a primop (builtin function) table.
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -52,6 +81,7 @@ impl PrimOpId {
/// Returns the raw `usize` index. /// Returns the raw `usize` index.
/// ///
/// # Safety /// # Safety
///
/// The caller is responsible for using this index correctly. /// The caller is responsible for using this index correctly.
#[inline(always)] #[inline(always)]
pub unsafe fn raw(self) -> usize { pub unsafe fn raw(self) -> usize {
@@ -61,6 +91,7 @@ impl PrimOpId {
/// Creates a `PrimOpId` from a raw `usize` index. /// Creates a `PrimOpId` from a raw `usize` index.
/// ///
/// # Safety /// # Safety
///
/// The caller must ensure that the provided index is valid. /// The caller must ensure that the provided index is valid.
#[inline(always)] #[inline(always)]
pub unsafe fn from_raw(id: usize) -> Self { pub unsafe fn from_raw(id: usize) -> Self {
@@ -77,6 +108,7 @@ impl StackIdx {
/// Returns the raw `usize` index. /// Returns the raw `usize` index.
/// ///
/// # Safety /// # Safety
///
/// The caller is responsible for using this index correctly. /// The caller is responsible for using this index correctly.
#[inline(always)] #[inline(always)]
pub unsafe fn raw(self) -> usize { pub unsafe fn raw(self) -> usize {
@@ -86,6 +118,7 @@ impl StackIdx {
/// Creates an `StackIdx` from a raw `usize` index. /// Creates an `StackIdx` from a raw `usize` index.
/// ///
/// # Safety /// # Safety
///
/// The caller must ensure that the provided index is valid. /// The caller must ensure that the provided index is valid.
#[inline(always)] #[inline(always)]
pub unsafe fn from_raw(idx: usize) -> Self { pub unsafe fn from_raw(idx: usize) -> Self {
@@ -100,7 +133,7 @@ pub struct Arg;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct AttrSet { pub struct AttrSet {
/// Statically known attributes (key is a string). /// Statically known attributes (key is a string).
pub stcs: HashMap<String, ExprId>, pub stcs: HashMap<SymId, ExprId>,
/// Dynamically computed attributes, where both the key and value are expressions. /// Dynamically computed attributes, where both the key and value are expressions.
pub dyns: Vec<(ExprId, ExprId)>, pub dyns: Vec<(ExprId, ExprId)>,
} }
@@ -113,7 +146,7 @@ pub enum Attr {
Dynamic(ExprId), Dynamic(ExprId),
/// A static attribute key. /// A static attribute key.
/// Example: `attrs.key` /// Example: `attrs.key`
Str(String), Str(SymId),
} }
/// Represents a Nix list. /// Represents a Nix list.
@@ -246,6 +279,8 @@ pub struct Func {
pub body: ExprId, pub body: ExprId,
/// The parameter specification for the function. /// The parameter specification for the function.
pub param: Param, pub param: Param,
pub arg: ExprId,
} }
/// Describes the parameters of a function. /// Describes the parameters of a function.
@@ -253,12 +288,12 @@ pub struct Func {
pub struct Param { pub struct Param {
/// The name of the argument if it's a simple identifier (e.g., `x: ...`). /// The name of the argument if it's a simple identifier (e.g., `x: ...`).
/// Also used for the alias in a pattern (e.g., `args @ { ... }`). /// Also used for the alias in a pattern (e.g., `args @ { ... }`).
pub ident: Option<String>, pub ident: Option<SymId>,
/// The set of required parameter names for a pattern-matching function. /// The set of required parameter names for a pattern-matching function.
pub required: Option<Vec<String>>, pub required: Option<Vec<SymId>>,
/// The set of all allowed parameter names for a non-ellipsis pattern-matching function. /// The set of all allowed parameter names for a non-ellipsis pattern-matching function.
/// If `None`, any attribute is allowed (ellipsis `...` is present). /// If `None`, any attribute is allowed (ellipsis `...` is present).
pub allowed: Option<HashSet<String>>, pub allowed: Option<HashSet<SymId>>,
} }
/// Represents a function call. /// Represents a function call.
@@ -315,7 +350,7 @@ pub struct Str {
/// Represents a variable lookup by its name. /// Represents a variable lookup by its name.
#[derive(Debug)] #[derive(Debug)]
pub struct Var { pub struct Var {
pub sym: String, pub sym: SymId,
} }
/// Represents a path literal. /// Represents a path literal.

View File

@@ -45,7 +45,7 @@ impl<Ctx: JITContext> JITCompile<Ctx> for AttrSet {
/// This creates a new attribute set and compiles all static attributes into it. /// This creates a new attribute set and compiles all static attributes into it.
fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot { fn compile(&self, ctx: &mut Context<Ctx>, rt_ctx: ir::Value) -> StackSlot {
let attrs = ctx.create_attrs(); let attrs = ctx.create_attrs();
for (k, v) in self.stcs.iter() { for (&k, v) in self.stcs.iter() {
let v = v.compile(ctx, rt_ctx); let v = v.compile(ctx, rt_ctx);
ctx.push_attr(attrs, k, v); ctx.push_attr(attrs, k, v);
} }

View File

@@ -13,7 +13,7 @@ use hashbrown::HashMap;
use nixjit_eval::{AttrSet, EvalContext, List, Value}; use nixjit_eval::{AttrSet, EvalContext, List, Value};
use nixjit_ir::ExprId; use nixjit_ir::ExprId;
use nixjit_ir::StackIdx; use nixjit_ir::SymId;
use super::JITContext; use super::JITContext;
@@ -23,20 +23,12 @@ pub extern "C" fn helper_call<Ctx: JITContext>(
arg: NonNull<Value>, arg: NonNull<Value>,
ctx: &mut Ctx, ctx: &mut Ctx,
) { ) {
// SAFETY: The `arg` pointer is guaranteed to be valid and non-null by the JIT compiler,
// which allocates it on the stack. The JIT code ensures that the pointer points to a
// valid `Value` and that its lifetime is managed correctly within the compiled function.
func.call(unsafe { arg.read() }, ctx).unwrap(); func.call(unsafe { arg.read() }, ctx).unwrap();
} }
/// Helper function to look up a value in the evaluation stack.
///
/// This function is called from JIT-compiled code to access values in the evaluation stack.
pub extern "C" fn helper_lookup_stack<Ctx: JITContext + EvalContext>(
ctx: &Ctx,
idx: StackIdx,
ret: &mut MaybeUninit<Value>,
) {
ret.write(ctx.lookup_stack(idx).clone());
}
/// Helper function to look up a function argument. /// Helper function to look up a function argument.
/// ///
/// This function is called from JIT-compiled code to access function arguments. /// This function is called from JIT-compiled code to access function arguments.
@@ -56,6 +48,9 @@ pub extern "C" fn helper_lookup<Ctx: JITContext>(
ret: &mut MaybeUninit<Value>, ret: &mut MaybeUninit<Value>,
) { ) {
// TODO: Error Handling // TODO: Error Handling
// SAFETY: The `sym_ptr` and `sym_len` are provided by the JIT compiler and are
// guaranteed to form a valid UTF-8 string slice. The string data is embedded
// in the compiled code and has a static lifetime, ensuring the pointer is always valid.
unsafe { unsafe {
ret.write( ret.write(
ctx.lookup_with(str::from_utf8_unchecked(slice::from_raw_parts( ctx.lookup_with(str::from_utf8_unchecked(slice::from_raw_parts(
@@ -78,6 +73,9 @@ pub extern "C" fn helper_select<Ctx: JITContext>(
ctx: &mut Ctx, ctx: &mut Ctx,
) { ) {
let path = core::ptr::slice_from_raw_parts_mut(path_ptr, path_len); let path = core::ptr::slice_from_raw_parts_mut(path_ptr, path_len);
// SAFETY: The `path_ptr` is allocated by the JIT compiler using `helper_alloc_array`
// and is guaranteed to be valid for the given length. The `Box::from_raw` call
// correctly takes ownership of the allocated slice, ensuring it is properly deallocated.
let path = unsafe { Box::from_raw(path) }; let path = unsafe { Box::from_raw(path) };
for attr in path { for attr in path {
val.select(&attr.force_string_no_ctx().unwrap(), ctx) val.select(&attr.force_string_no_ctx().unwrap(), ctx)
@@ -97,6 +95,9 @@ pub extern "C" fn helper_select_with_default<Ctx: JITContext>(
ctx: &mut Ctx, ctx: &mut Ctx,
) { ) {
let path = core::ptr::slice_from_raw_parts_mut(path_ptr, path_len); let path = core::ptr::slice_from_raw_parts_mut(path_ptr, path_len);
// SAFETY: The `path_ptr` is allocated by the JIT compiler using `helper_alloc_array`
// and is guaranteed to be valid for the given length. The `Box::from_raw` call
// correctly takes ownership of the allocated slice, ensuring it is properly deallocated.
let path = unsafe { Box::from_raw(path) }; let path = unsafe { Box::from_raw(path) };
for attr in path { for attr in path {
val.select_or(&attr.force_string_no_ctx().unwrap(), default, ctx) val.select_or(&attr.force_string_no_ctx().unwrap(), default, ctx)
@@ -107,7 +108,10 @@ pub extern "C" fn helper_select_with_default<Ctx: JITContext>(
/// Helper function to check equality between two values. /// Helper function to check equality between two values.
/// ///
/// This function is called from JIT-compiled code to perform equality comparisons. /// This function is called from JIT-compiled code to perform equality comparisons.
pub extern "C" fn helper_eq<Ctx: JITContext>(lhs: &mut Value, rhs: NonNull<Value>) { pub extern "C" fn helper_eq(lhs: &mut Value, rhs: NonNull<Value>) {
// SAFETY: The `rhs` pointer is guaranteed to be valid and non-null by the JIT compiler,
// which allocates it on the stack. The JIT code ensures that the pointer points to a
// valid `Value` and that its lifetime is managed correctly within the compiled function.
lhs.eq(unsafe { rhs.read() }); lhs.eq(unsafe { rhs.read() });
} }
@@ -115,11 +119,14 @@ pub extern "C" fn helper_eq<Ctx: JITContext>(lhs: &mut Value, rhs: NonNull<Value
/// ///
/// This function is called from JIT-compiled code to create string values /// This function is called from JIT-compiled code to create string values
/// from raw byte arrays. /// from raw byte arrays.
pub unsafe extern "C" fn helper_create_string<Ctx: JITContext>( pub unsafe extern "C" fn helper_create_string(
ptr: *const u8, ptr: *const u8,
len: usize, len: usize,
ret: &mut MaybeUninit<Value>, ret: &mut MaybeUninit<Value>,
) { ) {
// SAFETY: The `ptr` and `len` are provided by the JIT compiler and are guaranteed
// to form a valid UTF-8 string slice. The string data is embedded in the compiled
// code and has a static lifetime, ensuring the pointer is always valid.
unsafe { unsafe {
ret.write(Value::String( ret.write(Value::String(
str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)).to_owned(), str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)).to_owned(),
@@ -131,11 +138,14 @@ pub unsafe extern "C" fn helper_create_string<Ctx: JITContext>(
/// ///
/// This function is called from JIT-compiled code to create list values /// This function is called from JIT-compiled code to create list values
/// from arrays of values. /// from arrays of values.
pub unsafe extern "C" fn helper_create_list<Ctx: JITContext>( pub unsafe extern "C" fn helper_create_list(
ptr: *mut Value, ptr: *mut Value,
len: usize, len: usize,
ret: &mut MaybeUninit<Value>, ret: &mut MaybeUninit<Value>,
) { ) {
// SAFETY: The `ptr` is allocated by the JIT compiler using `helper_alloc_array` and
// is guaranteed to be valid for `len` elements. The `Vec::from_raw_parts` call
// correctly takes ownership of the allocated memory, ensuring it is properly managed.
unsafe { unsafe {
ret.write(Value::List( ret.write(Value::List(
List::from(Vec::from_raw_parts(ptr, len, len)).into(), List::from(Vec::from_raw_parts(ptr, len, len)).into(),
@@ -146,7 +156,7 @@ pub unsafe extern "C" fn helper_create_list<Ctx: JITContext>(
/// Helper function to create an attribute set. /// Helper function to create an attribute set.
/// ///
/// This function is called from JIT-compiled code to create a new, empty attribute set. /// This function is called from JIT-compiled code to create a new, empty attribute set.
pub unsafe extern "C" fn helper_create_attrs<Ctx: JITContext>( pub unsafe extern "C" fn helper_create_attrs(
ret: &mut MaybeUninit<HashMap<String, Value>>, ret: &mut MaybeUninit<HashMap<String, Value>>,
) { ) {
ret.write(HashMap::new()); ret.write(HashMap::new());
@@ -156,15 +166,17 @@ pub unsafe extern "C" fn helper_create_attrs<Ctx: JITContext>(
/// ///
/// This function is called from JIT-compiled code to insert a key-value pair /// This function is called from JIT-compiled code to insert a key-value pair
/// into an attribute set. /// into an attribute set.
pub unsafe extern "C" fn helper_push_attr<Ctx: JITContext>( pub unsafe extern "C" fn helper_push_attr(
attrs: &mut HashMap<String, Value>, attrs: &mut HashMap<SymId, Value>,
sym_ptr: *const u8, sym: SymId,
sym_len: usize,
val: NonNull<Value>, val: NonNull<Value>,
) { ) {
// SAFETY: The `sym_ptr` and `sym_len` are provided by the JIT compiler and are
// guaranteed to form a valid UTF-8 string slice. The `val` pointer is also
// guaranteed to be valid and non-null by the JIT compiler.
unsafe { unsafe {
attrs.insert( attrs.insert(
str::from_utf8_unchecked(slice::from_raw_parts(sym_ptr, sym_len)).to_owned(), sym,
val.read(), val.read(),
); );
} }
@@ -174,10 +186,13 @@ pub unsafe extern "C" fn helper_push_attr<Ctx: JITContext>(
/// ///
/// This function is called from JIT-compiled code to convert a HashMap into /// This function is called from JIT-compiled code to convert a HashMap into
/// a proper attribute set value. /// a proper attribute set value.
pub unsafe extern "C" fn helper_finalize_attrs<Ctx: JITContext>( pub unsafe extern "C" fn helper_finalize_attrs(
attrs: NonNull<HashMap<String, Value>>, attrs: NonNull<HashMap<String, Value>>,
ret: &mut MaybeUninit<Value>, ret: &mut MaybeUninit<Value>,
) { ) {
// SAFETY: The `attrs` pointer is guaranteed to be valid and non-null by the JIT
// compiler, which allocates it on the stack. The `read` operation correctly
// takes ownership of the HashMap.
ret.write(Value::AttrSet( ret.write(Value::AttrSet(
AttrSet::from(unsafe { attrs.read() }).into(), AttrSet::from(unsafe { attrs.read() }).into(),
)); ));
@@ -191,6 +206,8 @@ pub unsafe extern "C" fn helper_enter_with<Ctx: JITContext>(
ctx: &mut Ctx, ctx: &mut Ctx,
namespace: NonNull<Value>, namespace: NonNull<Value>,
) { ) {
// SAFETY: The `namespace` pointer is guaranteed to be valid and non-null by the JIT
// compiler. The `read` operation correctly takes ownership of the `Value`.
ctx.enter_with(unsafe { namespace.read() }.unwrap_attr_set().into_inner()); ctx.enter_with(unsafe { namespace.read() }.unwrap_attr_set().into_inner());
} }
@@ -205,13 +222,16 @@ pub unsafe extern "C" fn helper_exit_with<Ctx: JITContext>(ctx: &mut Ctx) {
/// ///
/// This function is called from JIT-compiled code to allocate memory for /// This function is called from JIT-compiled code to allocate memory for
/// arrays of values, such as function arguments or list elements. /// arrays of values, such as function arguments or list elements.
pub unsafe extern "C" fn helper_alloc_array<Ctx: JITContext>(len: usize) -> *mut u8 { pub unsafe extern "C" fn helper_alloc_array(len: usize) -> *mut u8 {
// SAFETY: The `Layout` is guaranteed to be valid for non-zero `len`. The caller
// is responsible for deallocating the memory, which is typically done by
// `Vec::from_raw_parts` or `Box::from_raw` in other helpers.
unsafe { alloc(Layout::array::<Value>(len).unwrap()) } unsafe { alloc(Layout::array::<Value>(len).unwrap()) }
} }
/// Helper function for debugging. /// Helper function for debugging.
/// ///
/// This function is called from JIT-compiled code to print a value for debugging purposes. /// This function is called from JIT-compiled code to print a value for debugging purposes.
pub extern "C" fn helper_dbg<Ctx: JITContext>(value: &Value) { pub extern "C" fn helper_dbg(value: &Value) {
println!("{value:?}") println!("{value:?}")
} }

View File

@@ -21,6 +21,7 @@ use cranelift_module::{FuncId, Linkage, Module};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use nixjit_eval::{EvalContext, Value}; use nixjit_eval::{EvalContext, Value};
use nixjit_ir::SymId;
use nixjit_lir::Lir; use nixjit_lir::Lir;
mod compile; mod compile;
@@ -174,25 +175,20 @@ impl<'comp, 'ctx, Ctx: JITContext> Context<'comp, 'ctx, Ctx> {
slot slot
} }
fn push_attr(&mut self, attrs: StackSlot, sym: &str, val: StackSlot) { fn push_attr(&mut self, attrs: StackSlot, sym: SymId, val: StackSlot) {
self.free_slot(attrs); self.free_slot(attrs);
self.free_slot(val); self.free_slot(val);
let attrs = self.builder.ins().stack_addr(types::I64, attrs, 0); let attrs = self.builder.ins().stack_addr(types::I64, attrs, 0);
let val = self.builder.ins().stack_addr(types::I64, val, 0); let val = self.builder.ins().stack_addr(types::I64, val, 0);
let sym = self.strings.get_or_insert_with(sym, |_| sym.to_owned()); let sym = self
let ptr = self
.builder .builder
.ins() .ins()
.iconst(self.compiler.ptr_type, sym.as_ptr() as i64); .iconst(self.compiler.ptr_type, unsafe { sym.raw() } as i64);
let len = self
.builder
.ins()
.iconst(self.compiler.ptr_type, sym.len() as i64);
let push_attr = self let push_attr = self
.compiler .compiler
.module .module
.declare_func_in_func(self.compiler.push_attr, self.builder.func); .declare_func_in_func(self.compiler.push_attr, self.builder.func);
self.builder.ins().call(push_attr, &[attrs, ptr, len, val]); self.builder.ins().call(push_attr, &[attrs, sym, val]);
} }
fn finalize_attrs(&mut self, attrs: StackSlot) -> StackSlot { fn finalize_attrs(&mut self, attrs: StackSlot) -> StackSlot {
@@ -281,24 +277,6 @@ impl<'comp, 'ctx, Ctx: JITContext> Context<'comp, 'ctx, Ctx> {
slot slot
} }
fn lookup_stack(&mut self, ctx: ir::Value, idx: usize) -> StackSlot {
let slot = self.alloca();
let lookup_stack = self
.compiler
.module
.declare_func_in_func(self.compiler.lookup_stack, self.builder.func);
let idx = self
.builder
.ins()
.iconst(self.compiler.ptr_type, idx as i64);
let ptr = self
.builder
.ins()
.stack_addr(self.compiler.ptr_type, slot, 0);
self.builder.ins().call(lookup_stack, &[ctx, idx, ptr]);
slot
}
fn lookup_arg(&mut self, ctx: ir::Value, idx: usize) -> StackSlot { fn lookup_arg(&mut self, ctx: ir::Value, idx: usize) -> StackSlot {
let slot = self.alloca(); let slot = self.alloca();
let lookup_arg = self let lookup_arg = self
@@ -406,7 +384,6 @@ pub struct JITCompiler<Ctx: JITContext> {
func_sig: Signature, func_sig: Signature,
call: FuncId, call: FuncId,
lookup_stack: FuncId,
lookup_arg: FuncId, lookup_arg: FuncId,
lookup: FuncId, lookup: FuncId,
select: FuncId, select: FuncId,
@@ -445,7 +422,6 @@ impl<Ctx: JITContext> JITCompiler<Ctx> {
let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names()); let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
builder.symbol("helper_call", helper_call::<Ctx> as _); builder.symbol("helper_call", helper_call::<Ctx> as _);
builder.symbol("helper_lookup_stack", helper_lookup_stack::<Ctx> as _);
builder.symbol("helper_lookup_arg", helper_lookup_arg::<Ctx> as _); builder.symbol("helper_lookup_arg", helper_lookup_arg::<Ctx> as _);
builder.symbol("helper_lookup", helper_lookup::<Ctx> as _); builder.symbol("helper_lookup", helper_lookup::<Ctx> as _);
builder.symbol("helper_select", helper_select::<Ctx> as _); builder.symbol("helper_select", helper_select::<Ctx> as _);
@@ -453,17 +429,17 @@ impl<Ctx: JITContext> JITCompiler<Ctx> {
"helper_select_with_default", "helper_select_with_default",
helper_select_with_default::<Ctx> as _, helper_select_with_default::<Ctx> as _,
); );
builder.symbol("helper_eq", helper_eq::<Ctx> as _); builder.symbol("helper_eq", helper_eq as _);
builder.symbol("helper_alloc_array", helper_alloc_array::<Ctx> as _); builder.symbol("helper_alloc_array", helper_alloc_array as _);
builder.symbol("helper_create_string", helper_create_string::<Ctx> as _); builder.symbol("helper_create_string", helper_create_string as _);
builder.symbol("helper_create_list", helper_create_list::<Ctx> as _); builder.symbol("helper_create_list", helper_create_list as _);
builder.symbol("helper_create_attrs", helper_create_attrs::<Ctx> as _); builder.symbol("helper_create_attrs", helper_create_attrs as _);
builder.symbol("helper_push_attr", helper_push_attr::<Ctx> as _); builder.symbol("helper_push_attr", helper_push_attr as _);
builder.symbol("helper_finalize_attrs", helper_finalize_attrs::<Ctx> as _); builder.symbol("helper_finalize_attrs", helper_finalize_attrs as _);
builder.symbol("helper_enter_with", helper_enter_with::<Ctx> as _); builder.symbol("helper_enter_with", helper_enter_with::<Ctx> as _);
builder.symbol("helper_exit_with", helper_exit_with::<Ctx> as _); builder.symbol("helper_exit_with", helper_exit_with::<Ctx> as _);
builder.symbol("helper_dbg", helper_dbg::<Ctx> as _); builder.symbol("helper_dbg", helper_dbg as _);
let mut module = JITModule::new(builder); let mut module = JITModule::new(builder);
let ctx = module.make_context(); let ctx = module.make_context();
@@ -495,18 +471,6 @@ impl<Ctx: JITContext> JITCompiler<Ctx> {
.declare_function("helper_call", Linkage::Import, &call_sig) .declare_function("helper_call", Linkage::Import, &call_sig)
.unwrap(); .unwrap();
let mut lookup_stack_sig = module.make_signature();
lookup_stack_sig.params.extend(
[AbiParam {
value_type: ptr_type,
purpose: ArgumentPurpose::Normal,
extension: ArgumentExtension::None,
}; 3],
);
let lookup_stack = module
.declare_function("helper_lookup_stack", Linkage::Import, &lookup_stack_sig)
.unwrap();
let mut lookup_arg_sig = module.make_signature(); let mut lookup_arg_sig = module.make_signature();
lookup_arg_sig.params.extend( lookup_arg_sig.params.extend(
[AbiParam { [AbiParam {
@@ -626,7 +590,7 @@ impl<Ctx: JITContext> JITCompiler<Ctx> {
value_type: ptr_type, value_type: ptr_type,
purpose: ArgumentPurpose::Normal, purpose: ArgumentPurpose::Normal,
extension: ArgumentExtension::None, extension: ArgumentExtension::None,
}; 4], }; 3],
); );
let push_attr = module let push_attr = module
.declare_function("helper_push_attr", Linkage::Import, &push_attr_sig) .declare_function("helper_push_attr", Linkage::Import, &push_attr_sig)
@@ -694,7 +658,6 @@ impl<Ctx: JITContext> JITCompiler<Ctx> {
func_sig, func_sig,
call, call,
lookup_stack,
lookup_arg, lookup_arg,
lookup, lookup,
select, select,
@@ -760,6 +723,11 @@ impl<Ctx: JITContext> JITCompiler<Ctx> {
self.ctx.clear(); self.ctx.clear();
let _ = self.builder_ctx.insert(builder_ctx); let _ = self.builder_ctx.insert(builder_ctx);
// SAFETY: The `get_finalized_function` method returns a raw pointer to the
// compiled machine code. We transmute it to the correct function pointer type `F<Ctx>`.
// This is safe because the function was compiled with the signature defined in `self.func_sig`,
// which matches the signature of `F<Ctx>`. The lifetime of the compiled code is managed
// by the `JITModule`, ensuring the pointer remains valid.
unsafe { unsafe {
JITFunc { JITFunc {
func: std::mem::transmute::<*const u8, F<Ctx>>( func: std::mem::transmute::<*const u8, F<Ctx>>(

View File

@@ -39,10 +39,10 @@ ir! {
Str, Str,
Var, Var,
Path, Path,
Arg, Arg(()),
PrimOp(PrimOpId), PrimOp(PrimOpId),
StackRef(StackIdx),
ExprRef(ExprId), ExprRef(ExprId),
StackRef(StackIdx),
FuncRef(ExprId), FuncRef(ExprId),
Thunk(ExprId), Thunk(ExprId),
} }
@@ -52,7 +52,7 @@ ir! {
pub enum LookupResult { pub enum LookupResult {
Stack(StackIdx), Stack(StackIdx),
/// The variable was found and resolved to a specific expression. /// The variable was found and resolved to a specific expression.
PrimOp(ExprId), Expr(ExprId),
/// The variable could not be resolved statically, likely due to a `with` expression. /// The variable could not be resolved statically, likely due to a `with` expression.
/// The lookup must be performed dynamically at evaluation time. /// The lookup must be performed dynamically at evaluation time.
Unknown, Unknown,
@@ -69,20 +69,22 @@ pub trait ResolveContext {
fn new_func(&mut self, body: ExprId, param: Param); fn new_func(&mut self, body: ExprId, param: Param);
/// Triggers the resolution of a given expression. /// Triggers the resolution of a given expression.
fn resolve(&mut self, expr: ExprId) -> Result<()>; fn resolve(&mut self, expr: ExprId) -> Result<ExprId>;
/// Looks up a variable by name in the current scope. /// Looks up a variable by name in the current scope.
fn lookup(&mut self, name: &str) -> LookupResult; fn lookup(&mut self, name: SymId) -> LookupResult;
fn lookup_arg(&mut self) -> StackIdx; fn get_sym(&self, id: SymId) -> &str;
fn lookup_arg(&mut self) -> ExprId;
/// Enters a `with` scope for the duration of a closure. /// Enters a `with` scope for the duration of a closure.
fn with_with_env<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T); fn with_with_env(&mut self, f: impl FnOnce(&mut Self) -> Result<()>) -> Result<bool>;
/// Enters a `let` scope with a given set of bindings for the duration of a closure. /// Enters a `let` scope with a given set of bindings for the duration of a closure.
fn with_let_env<T>( fn with_let_env<T>(
&mut self, &mut self,
bindings: HashMap<String, ExprId>, bindings: HashMap<SymId, ExprId>,
f: impl FnOnce(&mut Self) -> T, f: impl FnOnce(&mut Self) -> T,
) -> T; ) -> T;
@@ -90,7 +92,8 @@ pub trait ResolveContext {
fn with_closure_env<T>( fn with_closure_env<T>(
&mut self, &mut self,
func: ExprId, func: ExprId,
ident: Option<String>, arg: ExprId,
ident: Option<SymId>,
f: impl FnOnce(&mut Self) -> T, f: impl FnOnce(&mut Self) -> T,
) -> T; ) -> T;
} }
@@ -123,26 +126,23 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Hir {
Var(x) => x.resolve(ctx), Var(x) => x.resolve(ctx),
Path(x) => x.resolve(ctx), Path(x) => x.resolve(ctx),
Let(x) => x.resolve(ctx), Let(x) => x.resolve(ctx),
Thunk(x) => { Thunk(x) => ctx.resolve(x).map(Lir::Thunk),
ctx.resolve(x)?; Arg(_) => Ok(Lir::ExprRef(ctx.lookup_arg())),
Ok(Lir::Thunk(x))
}
Arg(_) => Ok(Lir::StackRef(ctx.lookup_arg())),
} }
} }
} }
/// Resolves an `AttrSet` by resolving all key and value expressions. /// Resolves an `AttrSet` by resolving all key and value expressions.
impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet { impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
for (_, &v) in self.stcs.iter() { for (_, v) in self.stcs.iter_mut() {
ctx.resolve(v)?; *v = ctx.resolve(*v)?;
} }
for &(k, _) in self.dyns.iter() { for (k, _) in self.dyns.iter_mut() {
ctx.resolve(k)?; *k = ctx.resolve(*k)?;
} }
for &(_, v) in self.dyns.iter() { for (_, v) in self.dyns.iter_mut() {
ctx.resolve(v)?; *v = ctx.resolve(*v)?;
} }
Ok(self.to_lir()) Ok(self.to_lir())
} }
@@ -150,9 +150,9 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
/// Resolves a `List` by resolving each of its items. /// Resolves a `List` by resolving each of its items.
impl<Ctx: ResolveContext> Resolve<Ctx> for List { impl<Ctx: ResolveContext> Resolve<Ctx> for List {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
for &item in self.items.iter() { for item in self.items.iter_mut() {
ctx.resolve(item)?; *item = ctx.resolve(*item)?;
} }
Ok(self.to_lir()) Ok(self.to_lir())
} }
@@ -160,11 +160,11 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for List {
/// Resolves a `HasAttr` expression by resolving the LHS and any dynamic attributes in the path. /// Resolves a `HasAttr` expression by resolving the LHS and any dynamic attributes in the path.
impl<Ctx: ResolveContext> Resolve<Ctx> for HasAttr { impl<Ctx: ResolveContext> Resolve<Ctx> for HasAttr {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.lhs)?; self.lhs = ctx.resolve(self.lhs)?;
for attr in self.rhs.iter() { for attr in self.rhs.iter_mut() {
if let &Attr::Dynamic(expr) = attr { if let &mut Attr::Dynamic(expr) = attr {
ctx.resolve(expr)?; *attr = ctx.resolve(expr).map(Attr::Dynamic)?
} }
} }
Ok(self.to_lir()) Ok(self.to_lir())
@@ -173,17 +173,17 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for HasAttr {
/// Resolves a `BinOp` by resolving its left and right hand sides. /// Resolves a `BinOp` by resolving its left and right hand sides.
impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp { impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.lhs)?; self.lhs = ctx.resolve(self.lhs)?;
ctx.resolve(self.rhs)?; self.rhs = ctx.resolve(self.rhs)?;
Ok(self.to_lir()) Ok(self.to_lir())
} }
} }
/// Resolves a `UnOp` by resolving its right hand side. /// Resolves a `UnOp` by resolving its right hand side.
impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp { impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.rhs)?; self.rhs = ctx.resolve(self.rhs)?;
Ok(self.to_lir()) Ok(self.to_lir())
} }
} }
@@ -191,15 +191,15 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
/// Resolves a `Select` by resolving the expression being selected from, any dynamic /// Resolves a `Select` by resolving the expression being selected from, any dynamic
/// attributes in the path, and the default value if it exists. /// attributes in the path, and the default value if it exists.
impl<Ctx: ResolveContext> Resolve<Ctx> for Select { impl<Ctx: ResolveContext> Resolve<Ctx> for Select {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.expr)?; self.expr = ctx.resolve(self.expr)?;
for attr in self.attrpath.iter() { for attr in self.attrpath.iter_mut() {
if let &Attr::Dynamic(expr) = attr { if let &mut Attr::Dynamic(expr) = attr {
ctx.resolve(expr)?; *attr = ctx.resolve(expr).map(Attr::Dynamic)?
} }
} }
if let Some(expr) = self.default { if let Some(expr) = &mut self.default {
ctx.resolve(expr)?; *expr = ctx.resolve(*expr)?;
} }
Ok(self.to_lir()) Ok(self.to_lir())
} }
@@ -207,10 +207,10 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Select {
/// Resolves an `If` expression by resolving the condition, consequence, and alternative. /// Resolves an `If` expression by resolving the condition, consequence, and alternative.
impl<Ctx: ResolveContext> Resolve<Ctx> for If { impl<Ctx: ResolveContext> Resolve<Ctx> for If {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.cond)?; self.cond = ctx.resolve(self.cond)?;
ctx.resolve(self.consq)?; self.consq = ctx.resolve(self.consq)?;
ctx.resolve(self.alter)?; self.alter = ctx.resolve(self.alter)?;
Ok(self.to_lir()) Ok(self.to_lir())
} }
} }
@@ -218,9 +218,10 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for If {
/// Resolves a `Func` by resolving its body within a new parameter scope. /// Resolves a `Func` by resolving its body within a new parameter scope.
/// It then registers the function with the context. /// It then registers the function with the context.
impl<Ctx: ResolveContext> Resolve<Ctx> for Func { impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.with_closure_env(self.body, self.param.ident.clone(), |ctx| { ctx.with_closure_env(self.body, self.arg, self.param.ident, |ctx| {
ctx.resolve(self.body) self.body = ctx.resolve(self.body)?;
Ok(())
})?; })?;
ctx.new_func(self.body, self.param); ctx.new_func(self.body, self.param);
Ok(Lir::FuncRef(self.body)) Ok(Lir::FuncRef(self.body))
@@ -228,9 +229,9 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
} }
impl<Ctx: ResolveContext> Resolve<Ctx> for Call { impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.func)?; self.func = ctx.resolve(self.func)?;
ctx.resolve(self.arg)?; self.func = ctx.resolve(self.arg)?;
Ok(self.to_lir()) Ok(self.to_lir())
} }
} }
@@ -238,10 +239,12 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
/// Resolves a `With` expression by resolving the namespace and the body. /// Resolves a `With` expression by resolving the namespace and the body.
/// The body is resolved within a special "with" scope. /// The body is resolved within a special "with" scope.
impl<Ctx: ResolveContext> Resolve<Ctx> for With { impl<Ctx: ResolveContext> Resolve<Ctx> for With {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.namespace)?; self.namespace = ctx.resolve(self.namespace)?;
let (env_used, res) = ctx.with_with_env(|ctx| ctx.resolve(self.expr)); let env_used = ctx.with_with_env(|ctx| {
res?; self.expr = ctx.resolve(self.expr)?;
Ok(())
})?;
// Optimization: if the `with` environment was not actually used by any variable // Optimization: if the `with` environment was not actually used by any variable
// lookup in the body, we can elide the `With` node entirely. // lookup in the body, we can elide the `With` node entirely.
if env_used { if env_used {
@@ -254,18 +257,18 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for With {
/// Resolves an `Assert` by resolving the assertion condition and the body. /// Resolves an `Assert` by resolving the assertion condition and the body.
impl<Ctx: ResolveContext> Resolve<Ctx> for Assert { impl<Ctx: ResolveContext> Resolve<Ctx> for Assert {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.assertion)?; self.assertion = ctx.resolve(self.assertion)?;
ctx.resolve(self.expr)?; self.expr = ctx.resolve(self.expr)?;
Ok(self.to_lir()) Ok(self.to_lir())
} }
} }
/// Resolves a `ConcatStrings` by resolving each part. /// Resolves a `ConcatStrings` by resolving each part.
impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings { impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
for &part in self.parts.iter() { for part in self.parts.iter_mut() {
ctx.resolve(part)?; *part = ctx.resolve(*part)?;
} }
Ok(self.to_lir()) Ok(self.to_lir())
} }
@@ -275,13 +278,13 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
impl<Ctx: ResolveContext> Resolve<Ctx> for Var { impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
use LookupResult::*; use LookupResult::*;
match ctx.lookup(&self.sym) { match ctx.lookup(self.sym) {
Expr(id) => Ok(Lir::ExprRef(id)),
Stack(idx) => Ok(Lir::StackRef(idx)), Stack(idx) => Ok(Lir::StackRef(idx)),
PrimOp(id) => Ok(Lir::ExprRef(id)),
Unknown => Ok(self.to_lir()), Unknown => Ok(self.to_lir()),
NotFound => Err(Error::resolution_error(format!( NotFound => Err(Error::resolution_error(format!(
"undefined variable '{}'", "undefined variable '{}'",
format_symbol(&self.sym) format_symbol(ctx.get_sym(self.sym))
))), ))),
} }
} }
@@ -289,8 +292,8 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
/// Resolves a `Path` by resolving the underlying expression that defines the path's content. /// Resolves a `Path` by resolving the underlying expression that defines the path's content.
impl<Ctx: ResolveContext> Resolve<Ctx> for Path { impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(self.expr)?; self.expr = ctx.resolve(self.expr)?;
Ok(self.to_lir()) Ok(self.to_lir())
} }
} }
@@ -298,12 +301,13 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
/// Resolves a `Let` expression by creating a new scope for the bindings, resolving /// Resolves a `Let` expression by creating a new scope for the bindings, resolving
/// the bindings and the body, and then returning a reference to the body. /// the bindings and the body, and then returning a reference to the body.
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let { impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> { fn resolve(mut self, ctx: &mut Ctx) -> Result<Lir> {
ctx.with_let_env(self.bindings.clone(), |ctx| { ctx.with_let_env(self.bindings.clone(), |ctx| {
for &id in self.bindings.values() { for id in self.bindings.values_mut() {
ctx.resolve(id)?; *id = ctx.resolve(*id)?;
} }
ctx.resolve(self.body) self.body = ctx.resolve(self.body)?;
Ok(())
})?; })?;
// The `let` expression itself evaluates to its body. // The `let` expression itself evaluates to its body.
Ok(Lir::ExprRef(self.body)) Ok(Lir::ExprRef(self.body))

View File

@@ -18,7 +18,8 @@ use proc_macro::TokenStream;
use proc_macro2::Span; use proc_macro2::Span;
use quote::{ToTokens, format_ident, quote}; use quote::{ToTokens, format_ident, quote};
use syn::{ use syn::{
parse_macro_input, FnArg, Item, ItemConst, ItemFn, ItemMod, Pat, PatIdent, PatType, Type, Visibility FnArg, Item, ItemConst, ItemFn, ItemMod, Pat, PatIdent, PatType, Type, Visibility,
parse_macro_input,
}; };
/// The implementation of the `#[builtins]` macro. /// The implementation of the `#[builtins]` macro.
@@ -69,14 +70,13 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
// Public functions are added to the global scope, private ones to a scoped set. // Public functions are added to the global scope, private ones to a scoped set.
if matches!(item_fn.vis, Visibility::Public(_)) { if matches!(item_fn.vis, Visibility::Public(_)) {
global.push(primop); global.push(primop);
pub_item_mod.push(quote! { #item_fn }.into()); pub_item_mod.push(quote! { #item_fn });
} else { } else {
scoped.push(primop); scoped.push(primop);
pub_item_mod.push( pub_item_mod.push(
quote! { quote! {
pub #item_fn pub #item_fn
} }
.into(),
); );
} }
wrappers.push(wrapper); wrappers.push(wrapper);