feat: TODO
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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, ());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user