From 64f650b6953abb72ef1ea58e66cd414c311344ab Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Tue, 5 Aug 2025 21:51:03 +0800 Subject: [PATCH] fix: WIP --- Cargo.lock | 30 ++++ evaluator/nixjit/Cargo.toml | 10 ++ evaluator/nixjit/src/lib.rs | 13 +- evaluator/nixjit/src/test.rs | 18 ++- evaluator/nixjit_context/Cargo.toml | 1 + evaluator/nixjit_context/src/lib.rs | 197 ++++++++++++++++++------- evaluator/nixjit_eval/src/lib.rs | 45 +++++- evaluator/nixjit_eval/src/value/mod.rs | 2 +- evaluator/nixjit_hir/src/downgrade.rs | 8 +- evaluator/nixjit_ir/src/lib.rs | 47 +++++- evaluator/nixjit_jit/src/helpers.rs | 2 +- evaluator/nixjit_lir/src/lib.rs | 1 + 12 files changed, 288 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0fe4b1..7b5d207 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,6 +291,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "foldhash" version = "0.1.5" @@ -389,6 +395,17 @@ dependencies = [ [[package]] name = "nixjit" version = "0.1.0" +dependencies = [ + "hashbrown 0.15.4", + "nixjit_context", + "nixjit_error", + "nixjit_eval", + "nixjit_hir", + "nixjit_ir", + "nixjit_lir", + "nixjit_value", + "rnix", +] [[package]] name = "nixjit_context" @@ -407,6 +424,7 @@ dependencies = [ "nixjit_jit", "nixjit_lir", "nixjit_value", + "petgraph", ] [[package]] @@ -505,6 +523,18 @@ dependencies = [ "regex", ] +[[package]] +name = "petgraph" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.4", + "indexmap", + "serde", +] + [[package]] name = "proc-macro2" version = "1.0.95" diff --git a/evaluator/nixjit/Cargo.toml b/evaluator/nixjit/Cargo.toml index 291d821..4e0db2e 100644 --- a/evaluator/nixjit/Cargo.toml +++ b/evaluator/nixjit/Cargo.toml @@ -4,3 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] +rnix = "0.12" +hashbrown = "0.15" + +nixjit_context = { path = "../nixjit_context" } +nixjit_error = { path = "../nixjit_error" } +nixjit_eval = { path = "../nixjit_eval" } +nixjit_hir = { path = "../nixjit_hir" } +nixjit_ir = { path = "../nixjit_ir" } +nixjit_lir = { path = "../nixjit_lir" } +nixjit_value = { path = "../nixjit_value" } diff --git a/evaluator/nixjit/src/lib.rs b/evaluator/nixjit/src/lib.rs index b93cf3f..c7b7f76 100644 --- a/evaluator/nixjit/src/lib.rs +++ b/evaluator/nixjit/src/lib.rs @@ -1,14 +1,3 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - #[cfg(test)] -mod tests { - use super::*; +mod test; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/evaluator/nixjit/src/test.rs b/evaluator/nixjit/src/test.rs index d04dcb3..eb8a851 100644 --- a/evaluator/nixjit/src/test.rs +++ b/evaluator/nixjit/src/test.rs @@ -3,18 +3,20 @@ use std::collections::BTreeMap; -use crate::ir::downgrade; -use crate::ty::common::Const; -use crate::ty::public::*; - -use super::eval; +use hashbrown::HashSet; +use nixjit_context::Context; +use nixjit_eval::EvalContext; +use nixjit_hir::Downgrade; +use nixjit_lir::ResolveContext; +use nixjit_value::{AttrSet, Const, List, Symbol, Value}; #[inline] fn test_expr(expr: &str, expected: Value) { println!("{expr}"); - let downgraded = downgrade(rnix::Root::parse(expr).tree().expr().unwrap()).unwrap(); - println!("{downgraded:#?}"); - assert_eq!(eval(downgraded).unwrap(), expected); + let mut ctx = Context::new(); + let expr = rnix::Root::parse(expr).tree().expr().unwrap().downgrade(&mut ctx).unwrap(); + ctx.resolve(expr).unwrap(); + assert_eq!(ctx.eval(expr).unwrap().to_public(&ctx, &mut HashSet::new()), expected); } macro_rules! map { diff --git a/evaluator/nixjit_context/Cargo.toml b/evaluator/nixjit_context/Cargo.toml index 273e259..f25844b 100644 --- a/evaluator/nixjit_context/Cargo.toml +++ b/evaluator/nixjit_context/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] hashbrown = "0.15" itertools = "0.14" +petgraph = "0.8" cranelift = "0.122" cranelift-module = "0.122" diff --git a/evaluator/nixjit_context/src/lib.rs b/evaluator/nixjit_context/src/lib.rs index 634ca31..196079c 100644 --- a/evaluator/nixjit_context/src/lib.rs +++ b/evaluator/nixjit_context/src/lib.rs @@ -1,30 +1,50 @@ use core::mem::MaybeUninit; use std::cell::{OnceCell, RefCell}; +use std::rc::Rc; use hashbrown::{HashMap, HashSet}; -use itertools::Itertools; +use petgraph::algo::toposort; +use petgraph::graph::{DiGraph, NodeIndex}; -use nixjit_error::Result; -use nixjit_eval::EvalContext; +use nixjit_error::{Error, Result}; +use nixjit_eval::{EvalContext, Evaluate, Value}; use nixjit_hir::{DowngradeContext, Hir}; -use nixjit_ir::{ExprId, Param}; -use nixjit_lir::{Lir, ResolveContext}; -use nixjit_value::Value; +use nixjit_ir::{ArgIdx, ExprId, Param}; +use nixjit_lir::{Lir, LookupResult, Resolve, ResolveContext}; use nixjit_jit::{JITCompiler, JITContext, JITFunc}; +#[derive(Debug)] +struct Frame { + values: Vec>, + left: usize, +} + #[derive(Default)] pub struct Context { hirs: Vec>, lirs: Vec>, + resolved: Vec, + scopes: Vec>, + funcs: HashMap, + graph: DiGraph, + nodes: Vec, + + stack: Vec, + with_scopes: Vec>>>, + jit: JITCompiler, compiled: Vec>>, } impl Drop for Context { fn drop(&mut self) { - unsafe { - self.lirs.iter_mut().for_each(|lir| lir.assume_init_drop()); + for (i, lir) in self.lirs.iter_mut().enumerate() { + if self.resolved[i] { + unsafe { + lir.assume_init_drop(); + } + } } } } @@ -37,20 +57,22 @@ impl Context { impl DowngradeContext for Context { fn new_expr(&mut self, expr: Hir) -> ExprId { - let id = unsafe { core::mem::transmute(self.hirs.len() + self.lirs.len()) }; + let id = ExprId::from(self.hirs.len()); self.hirs.push(expr.into()); + self.lirs.push(MaybeUninit::uninit()); + self.nodes.push(self.graph.add_node(id)); + self.resolved.push(false); + self.compiled.push(OnceCell::new()); id } fn with_expr(&self, id: ExprId, f: impl FnOnce(&Hir, &Self) -> T) -> T { - unsafe { - let idx: usize = core::mem::transmute(id); - f(&self.hirs.get_unchecked(idx).borrow(), self) - } + let idx = usize::from(id); + f(&self.hirs[idx].borrow(), self) } fn with_expr_mut(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T { + let idx = usize::from(id); unsafe { - let self_mut = &mut *(self as *mut Context); - let idx: usize = core::mem::transmute(id); + let self_mut = &mut *(self as *mut Self); f(&mut self.hirs.get_unchecked_mut(idx).borrow_mut(), self_mut) } } @@ -58,88 +80,163 @@ impl DowngradeContext for Context { impl ResolveContext for Context { fn lookup(&self, name: &str) -> nixjit_lir::LookupResult { - todo!() + for scope in self.scopes.iter().rev() { + if let Some(val) = scope.get(name) { + return val.clone(); + } + } + LookupResult::NotFound } + fn new_dep(&mut self, expr: ExprId, dep: ExprId) { - todo!() + let expr = *self.nodes.get(usize::from(expr)).unwrap(); + let dep = *self.nodes.get(usize::from(dep)).unwrap(); + self.graph.add_edge(expr, dep, ()); } + fn resolve(&mut self, expr: ExprId) -> Result<()> { - todo!() + let idx = usize::from(expr); + if self.resolved[idx] { + return Ok(()); + } + + let hir = self.hirs[idx].replace(nixjit_hir::Hir::Const(nixjit_ir::Const::from(false))); + let lir = hir.resolve(self)?; + self.lirs[idx].write(lir); + self.resolved[idx] = true; + Ok(()) } + fn new_func(&mut self, body: ExprId, param: Param) { - todo!() + self.funcs.insert(body, param); } + fn with_let_env<'a, T>( &mut self, bindings: impl IntoIterator, f: impl FnOnce(&mut Self) -> T, ) -> T { - todo!() + let mut scope = HashMap::new(); + for (name, expr) in bindings { + scope.insert(name.clone(), LookupResult::Expr(*expr)); + } + self.scopes.push(scope); + let res = f(self); + self.scopes.pop(); + res } + fn with_with_env(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T) { - todo!() + self.scopes.push(HashMap::new()); + let res = f(self); + self.scopes.pop(); + (true, res) } + fn with_param_env<'a, T>( &mut self, ident: Option<&'a str>, f: impl FnOnce(&mut Self) -> T, ) -> T { - todo!() + let mut scope = HashMap::new(); + if let Some(ident) = ident { + scope.insert(ident.to_string(), LookupResult::Arg(ArgIdx::from(0))); + } + self.scopes.push(scope); + let res = f(self); + self.scopes.pop(); + res } } impl EvalContext for Context { - fn eval(&mut self, expr: ExprId) -> Result> - where - Self: Sized, - { - todo!() + fn eval(&mut self, expr: ExprId) -> Result> { + let idx = usize::from(expr); + let lir = unsafe { &*(self.lirs[idx].assume_init_ref() as *const Lir) }; + lir.eval(self) } - fn pop_frame(&mut self) -> Vec> - where - Self: Sized, - { - todo!() + + fn pop_frame(&mut self) -> Vec> { + self.stack.pop().unwrap().values } - fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a nixjit_eval::Value> - where - Self: Sized, - { - todo!() + + fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a nixjit_eval::Value> { + for scope in self.with_scopes.iter().rev() { + if let Some(val) = scope.get(ident) { + return Some(val); + } + } + None } + + fn lookup_arg<'a>(&'a self, offset: usize) -> Option<&'a Value> { + self.stack.last().and_then(|frame| frame.values.get(offset)) + } + fn with_with_env( &mut self, namespace: std::rc::Rc>>, f: impl FnOnce(&mut Self) -> T, - ) -> T - where - Self: Sized, - { - todo!() + ) -> T { + self.with_scopes.push(namespace); + let res = f(self); + self.with_scopes.pop(); + res } + fn with_args_env( &mut self, args: Vec>, f: impl FnOnce(&mut Self) -> T, - ) -> (Vec>, T) - where - Self: Sized, - { - todo!() + ) -> (Vec>, T) { + self.stack.push(Frame { + left: args.len(), + values: args, + }); + let res = f(self); + (self.stack.pop().unwrap().values, res) + } + + fn consume_arg(&mut self, func: ExprId) -> Result { + let Some(frame) = self.stack.last_mut() else { + return Ok(false); + }; + if frame.left == 0 { + return Ok(false); + } + frame.left -= 1; + let param = self.funcs.get(&func).unwrap(); + if let Some(required) = ¶m.required { + let attrs = frame.values[frame.values.len() - frame.left - 1] + .as_ref() + .try_unwrap_attr_set() + .map_err(|_| Error::EvalError(format!("expected a set but found ...")))?; + if required.iter().any(|attr| attrs.get(attr).is_none()) + || param.allowed.as_ref().map_or(false, |allowed| { + attrs.iter().any(|(attr, _)| allowed.get(attr).is_none()) + }) + { + return Err(Error::EvalError(format!("TODO"))); + } + } + Ok(true) } } impl JITContext for Context { fn lookup_arg(&self, offset: usize) -> &nixjit_eval::Value { - todo!() + &self.stack.last().unwrap().values[offset] } + fn lookup_stack(&self, offset: usize) -> &nixjit_eval::Value { todo!() } + fn enter_with(&mut self, namespace: std::rc::Rc>>) { - todo!() + self.with_scopes.push(namespace); } + fn exit_with(&mut self) { - todo!() + self.with_scopes.pop(); } } diff --git a/evaluator/nixjit_eval/src/lib.rs b/evaluator/nixjit_eval/src/lib.rs index 8e04f1f..d804a44 100644 --- a/evaluator/nixjit_eval/src/lib.rs +++ b/evaluator/nixjit_eval/src/lib.rs @@ -24,7 +24,9 @@ pub trait EvalContext: Sized { f: impl FnOnce(&mut Self) -> T, ) -> (Vec>, T); fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a Value>; + fn lookup_arg<'a>(&'a self, offset: usize) -> Option<&'a Value>; fn pop_frame(&mut self) -> Vec>; + fn consume_arg(&mut self, func: ExprId) -> Result; } pub trait Evaluate { @@ -39,7 +41,38 @@ impl Evaluate for ExprId { impl Evaluate for lir::Lir { fn eval(&self, ctx: &mut Ctx) -> Result> { - todo!() + use lir::Lir::*; + match self { + AttrSet(x) => x.eval(ctx), + List(x) => x.eval(ctx), + HasAttr(x) => x.eval(ctx), + BinOp(x) => x.eval(ctx), + UnOp(x) => x.eval(ctx), + Select(x) => x.eval(ctx), + If(x) => x.eval(ctx), + Call(x) => x.eval(ctx), + With(x) => x.eval(ctx), + Assert(x) => x.eval(ctx), + ConcatStrings(x) => x.eval(ctx), + Const(x) => x.eval(ctx), + Str(x) => x.eval(ctx), + Var(x) => x.eval(ctx), + Path(x) => x.eval(ctx), + ExprRef(expr) => expr.eval(ctx), + &FuncRef(func) => { + if ctx.consume_arg(func)? { + ctx.eval(func) + } else { + Ok(Value::Func(func)) + } + }, + &ArgRef(arg) => { + let idx: usize = unsafe { core::mem::transmute(arg) }; + ctx.lookup_arg(idx) + .cloned() + .ok_or_else(|| Error::EvalError("argument not found".to_string())) + } + } } } @@ -243,7 +276,15 @@ impl Evaluate for ir::With { impl Evaluate for ir::Assert { fn eval(&self, ctx: &mut Ctx) -> Result> { - todo!() + let cond = self.assertion.eval(ctx)?; + let cond = cond + .try_unwrap_bool() + .map_err(|_| Error::EvalError(format!("expected a boolean but found ...")))?; + if cond { + self.expr.eval(ctx) + } else { + Err(Error::EvalError("assertion failed".to_string())) + } } } diff --git a/evaluator/nixjit_eval/src/value/mod.rs b/evaluator/nixjit_eval/src/value/mod.rs index 7f5ded6..91f1b8b 100644 --- a/evaluator/nixjit_eval/src/value/mod.rs +++ b/evaluator/nixjit_eval/src/value/mod.rs @@ -142,7 +142,7 @@ impl PartialEq for Value { impl Eq for Value {} -#[derive(IsVariant, Unwrap, Clone)] +#[derive(IsVariant, TryUnwrap, Unwrap, Clone)] pub enum ValueAsRef<'v, Ctx: EvalContext> { Int(i64), Float(f64), diff --git a/evaluator/nixjit_hir/src/downgrade.rs b/evaluator/nixjit_hir/src/downgrade.rs index f3b14a4..ab92ef3 100644 --- a/evaluator/nixjit_hir/src/downgrade.rs +++ b/evaluator/nixjit_hir/src/downgrade.rs @@ -316,13 +316,7 @@ impl Downgrade for ast::Lambda { allowed = if ellipsis { None } else { - Some( - formals - .into_iter() - .filter(|(_, default)| default.is_some()) - .map(|(k, _)| k) - .collect(), - ) + Some(formals.into_iter().map(|(k, _)| k).collect()) }; } } diff --git a/evaluator/nixjit_ir/src/lib.rs b/evaluator/nixjit_ir/src/lib.rs index 0684f1c..dfc45c4 100644 --- a/evaluator/nixjit_ir/src/lib.rs +++ b/evaluator/nixjit_ir/src/lib.rs @@ -12,24 +12,61 @@ use rnix::ast; use derive_more::TryUnwrap; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use nixjit_value::Const as PubConst; /// A type-safe wrapper for an index into an expression table. #[repr(transparent)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ExprId(usize); +impl From for ExprId { + fn from(id: usize) -> Self { + ExprId(id) + } +} + +impl From for usize { + fn from(id: ExprId) -> Self { + id.0 + } +} + /// A type-safe wrapper for an index into a function table. #[repr(transparent)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct FuncId(usize); +impl From for FuncId { + fn from(id: usize) -> Self { + FuncId(id) + } +} + +impl From for usize { + fn from(id: FuncId) -> Self { + id.0 + } +} + +/// A type-safe wrapper for an index into a function's argument list. #[repr(transparent)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ArgIdx(usize); +impl From for ArgIdx { + fn from(id: usize) -> Self { + ArgIdx(id) + } +} + +impl From for usize { + fn from(id: ArgIdx) -> Self { + id.0 + } +} + /// Represents a Nix attribute set. #[derive(Debug, Default)] pub struct AttrSet { @@ -185,7 +222,7 @@ pub struct Func { pub struct Param { pub ident: Option, pub required: Option>, - pub allowed: Option>, + pub allowed: Option>, } /// Represents a function call. diff --git a/evaluator/nixjit_jit/src/helpers.rs b/evaluator/nixjit_jit/src/helpers.rs index 88c0bc3..d628fc1 100644 --- a/evaluator/nixjit_jit/src/helpers.rs +++ b/evaluator/nixjit_jit/src/helpers.rs @@ -35,7 +35,7 @@ pub extern "C" fn helper_lookup_arg( offset: usize, ret: &mut MaybeUninit>, ) { - ret.write(ctx.lookup_arg(offset).clone()); + ret.write(JITContext::lookup_arg(ctx, offset).clone()); } pub extern "C" fn helper_lookup( diff --git a/evaluator/nixjit_lir/src/lib.rs b/evaluator/nixjit_lir/src/lib.rs index a5ddeef..534cfa0 100644 --- a/evaluator/nixjit_lir/src/lib.rs +++ b/evaluator/nixjit_lir/src/lib.rs @@ -29,6 +29,7 @@ ir! { ArgRef(ArgIdx), } +#[derive(Debug, Clone, Copy)] pub enum LookupResult { Expr(ExprId), Arg(ArgIdx),