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

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
bumpalo = { version = "3.19", features = ["boxed"] }
bumpalo = { version = "3.19", features = ["boxed", "collections"] }
derive_more = { version = "2.0", features = ["full"] }
hashbrown = "0.15"
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 {
// 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 {
let self_mut = &mut *(self as *mut Self);
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() {
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()) },
&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 hashbrown::HashMap;
use itertools::Itertools;
use petgraph::prelude::DiGraph;
use nixjit_error::Result;
use nixjit_eval::{Args, EvalContext, Evaluate, StackFrame, Value};
use nixjit_ir::ExprId;
use nixjit_error::{Error, Result};
use nixjit_eval::{Args, EvalContext, Evaluate, PrimOpApp, Value, ValueId};
use nixjit_ir::{ExprId, PrimOpId};
use nixjit_jit::JITContext;
use nixjit_lir::Lir;
use petgraph::prelude::DiGraph;
use super::Context;
struct ValueCache(
#[cfg(debug_assertions)]
Option<Value>,
#[cfg(not(debug_assertions))]
MaybeUninit<Value>
);
impl Default for ValueCache {
fn default() -> Self {
#[cfg(debug_assertions)]
{
Self(None)
}
#[cfg(not(debug_assertions))]
{
Self(MaybeUninit::uninit())
}
}
enum ValueCache {
Expr(ExprId),
BlackHole,
Value(Value),
}
impl ValueCache {
fn insert(&mut self, val: Value) {
#[cfg(debug_assertions)]
{
assert!(self.0.is_none());
let _ = self.0.insert(val);
}
#[cfg(not(debug_assertions))]
self.0.write(val);
}
}
impl Drop for ValueCache {
fn drop(&mut self) {
#[cfg(not(debug_assertions))]
unsafe {
self.0.assume_init_drop();
fn get_or_eval(&mut self, eval: impl FnOnce(ExprId) -> Result<Value>) -> Result<&Value> {
match self {
&mut Self::Expr(id) => {
*self = Self::BlackHole;
match eval(id) {
Ok(value) => {
*self = Self::Value(value);
let Self::Value(value) = self else {
unreachable!()
};
Ok(value)
}
Err(err) => Err(err),
}
}
Self::Value(value) => Ok(value),
Self::BlackHole => Err(Error::eval_error(format!("infinite recursion encountered"))),
}
}
}
pub struct EvalCtx<'ctx, 'bump> {
ctx: &'ctx mut Context<'bump>,
graph: DiGraph<ValueCache, ()>,
stack: Vec<StackFrame>,
graph: DiGraph<Vec<ValueId>, ()>,
caches: Vec<ValueCache>,
with_scopes: Vec<Rc<HashMap<String, Value>>>,
}
impl<'ctx, 'bump> EvalCtx<'ctx, 'bump> {
pub fn new(ctx: &'ctx mut Context<'bump>) -> Self {
Self {
ctx,
graph: DiGraph::new(),
stack: Vec::new(),
graph: DiGraph::with_capacity(ctx.graph.node_count(), ctx.graph.edge_count()),
caches: 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<'_, '_> {
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> {
// 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 lir = unsafe { &*(&**self.ctx.lirs.get_unchecked(idx) as *const Lir) };
lir.eval(self)
}
fn call(&mut self, func: ExprId, arg: Option<Value>, frame: StackFrame) -> Result<Value> {
self.stack.push(frame);
if let Err(err) = self.eval_deps(func, arg) {
self.stack.pop();
return Err(err);
}
let ret = self.eval(func);
self.stack.pop();
ret
fn resolve(&mut self, id: ExprId) -> Result<ValueId> {
let mut deps = Vec::new();
self.caches.push(ValueCache::Expr(id));
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()) })
}
fn call(&mut self, func: ValueId, arg: Value) -> Result<Value> {
todo!()
}
fn force(&mut self, id: ValueId) -> Result<Value> {
todo!()
}
fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a Value> {
@@ -137,35 +101,18 @@ impl EvalContext for EvalCtx<'_, '_> {
None
}
fn lookup_stack(&self, idx: nixjit_ir::StackIdx) -> &Value {
if cfg!(debug_assertions) {
self.stack
.last()
.unwrap()
.get(unsafe { idx.raw() })
.unwrap()
fn call_primop(&mut self, id: PrimOpId, args: Args) -> Result<Value> {
// SAFETY: The `PrimOpId` is created and managed by the `Context` and is
// guaranteed to be a valid index into the `primops` array. The `get_unchecked`
// is safe under this invariant, avoiding a bounds check for performance.
let &(arity, primop) = unsafe { self.ctx.primops.get_unchecked(id.raw()) };
if args.len() == arity {
primop(self.ctx, args)
} else {
unsafe {
self.stack
.last()
.unwrap_unchecked()
.get_unchecked(idx.raw())
}
Ok(Value::PrimOpApp(PrimOpApp::new(id, args).into()))
}
}
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>(
&mut self,
namespace: Rc<HashMap<String, Value>>,

View File

@@ -1,4 +1,3 @@
use std::cell::Cell;
use std::ptr::NonNull;
use bumpalo::{Bump, boxed::Box};
@@ -7,13 +6,12 @@ use itertools::Itertools;
use petgraph::graphmap::DiGraphMap;
use nixjit_builtins::{
Builtins, BuiltinsContext,
builtins::{GLOBAL_LEN, SCOPED_LEN},
builtins::{GLOBAL_LEN, SCOPED_LEN}, BuiltinFn, Builtins, BuiltinsContext
};
use nixjit_error::{Error, Result};
use nixjit_eval::{Args, EvalContext, Value};
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 crate::downgrade::DowngradeCtx;
@@ -39,16 +37,23 @@ pub struct Context<'bump> {
global_scope: NonNull<HashMap<&'static str, ExprId>>,
/// A dependency graph between expressions.
graph: DiGraphMap<ExprId, StackIdx>,
graph: DiGraphMap<ExprId, ()>,
/// 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,
}
impl Drop for Context<'_> {
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 {
self.repl_scope.drop_in_place();
self.global_scope.drop_in_place();
@@ -62,10 +67,18 @@ impl<'bump> Context<'bump> {
let global_scope = global
.iter()
.enumerate()
.map(|(idx, (k, _, _))| (*k, unsafe { ExprId::from_raw(idx) }))
.chain(core::iter::once(("builtins", unsafe {
ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN)
})))
.map(|(idx, (k, _, _))| {
// SAFETY: The index `idx` comes from `enumerate()` on the `global` array,
// 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();
let primops = global
.iter()
@@ -74,30 +87,48 @@ impl<'bump> Context<'bump> {
.collect_array()
.unwrap();
let lirs = (0..global.len())
.map(|idx| Lir::PrimOp(unsafe { PrimOpId::from_raw(idx) }))
.chain(
(0..scoped.len())
.map(|idx| Lir::PrimOp(unsafe { PrimOpId::from_raw(idx + GLOBAL_LEN) })),
)
.map(|idx| {
// SAFETY: The index `idx` is guaranteed to be within the bounds of the
// `global` primops array, making it a valid raw ID for a `PrimOpId`.
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 {
stcs: global
.into_iter()
.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, ..))| {
// SAFETY: `idx + GLOBAL_LEN` is a valid index for the LIR
// corresponding to this scoped primop.
(name.to_string(), unsafe {
ExprId::from_raw(idx + GLOBAL_LEN)
})
}))
.chain(core::iter::once(("builtins".to_string(), unsafe {
ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN + 1)
})))
.chain(core::iter::once((
"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(),
..AttrSet::default()
})))
.chain(core::iter::once(Lir::Thunk(unsafe {
ExprId::from_raw(GLOBAL_LEN + SCOPED_LEN)
})))
.chain(core::iter::once(Lir::Thunk(
// 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))
.collect_vec();
Self {
@@ -144,8 +175,9 @@ impl<'bump> Context<'bump> {
let root = self
.downgrade_ctx()
.downgrade_root(root.tree().expr().unwrap())?;
self.resolve_ctx(root).resolve_root()?;
Ok(self.eval_ctx().eval_root(root)?.to_public())
let ctx = self.resolve_ctx(root);
ctx.resolve_root()?;
Ok(self.eval_ctx().eval(root)?.to_public())
}
pub fn add_binding(&mut self, ident: &str, expr: &str) -> Result<()> {
@@ -157,6 +189,10 @@ impl<'bump> Context<'bump> {
.unwrap();
let expr_id = self.downgrade_ctx().downgrade_root(root_expr)?;
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);
Ok(())
}
@@ -165,20 +201,15 @@ impl<'bump> Context<'bump> {
impl Context<'_> {
fn alloc_id(&mut self) -> ExprId {
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) }
}
fn add_dep(&mut self, from: ExprId, to: ExprId, count: &Cell<usize>) -> StackIdx {
if let Some(&idx) = self.graph.edge_weight(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
}
fn add_dep(&mut self, from: ExprId, to: ExprId) {
self.graph.add_edge(from, to, ());
}
}

View File

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