refactor: reduce coupling
This commit is contained in:
6
evaluator/nixjit/Cargo.toml
Normal file
6
evaluator/nixjit/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "nixjit"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
14
evaluator/nixjit/src/lib.rs
Normal file
14
evaluator/nixjit/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
217
evaluator/nixjit/src/test.rs
Normal file
217
evaluator/nixjit/src/test.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
#![allow(unused_macros)]
|
||||
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::ir::downgrade;
|
||||
use crate::ty::common::Const;
|
||||
use crate::ty::public::*;
|
||||
|
||||
use super::eval;
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
macro_rules! map {
|
||||
($($k:expr => $v:expr),*) => {
|
||||
{
|
||||
BTreeMap::from([$(($k, $v),)*])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! thunk {
|
||||
() => {
|
||||
Value::Thunk
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! int {
|
||||
($e:expr) => {
|
||||
Value::Const(Const::Int($e))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! float {
|
||||
($e:expr) => {
|
||||
Value::Const(Const::Float($e as f64))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! boolean {
|
||||
($e:expr) => {
|
||||
Value::Const(Const::Bool($e))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! string {
|
||||
($e:expr) => {
|
||||
Value::String(String::from($e))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! symbol {
|
||||
($e:expr) => {
|
||||
Symbol::from($e.to_string())
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! list {
|
||||
($($x:tt)*) => (
|
||||
Value::List(List::new(vec![$($x)*]))
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! attrs {
|
||||
($($x:tt)*) => (
|
||||
Value::AttrSet(AttrSet::new(map!{$($x)*}))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arith() {
|
||||
test_expr("1", int!(1));
|
||||
test_expr("1.", float!(1));
|
||||
test_expr("-1", int!(-1));
|
||||
test_expr("-1.", float!(-1));
|
||||
test_expr("1 + 1", int!(2));
|
||||
test_expr("1 + 1.", float!(2));
|
||||
test_expr("1. + 1", float!(2));
|
||||
test_expr("1. + 1.", float!(2));
|
||||
test_expr("1 - 1", int!(0));
|
||||
test_expr("1 - 1.", float!(0));
|
||||
test_expr("1. - 1", float!(0));
|
||||
test_expr("1. - 1.", float!(0));
|
||||
test_expr("1 * 1", int!(1));
|
||||
test_expr("1 * 1.", float!(1));
|
||||
test_expr("1. * 1", float!(1));
|
||||
test_expr("1. * 1.", float!(1));
|
||||
test_expr("1 / 1", int!(1));
|
||||
test_expr("1 / 1.", float!(1));
|
||||
test_expr("1. / 1", float!(1));
|
||||
test_expr("1. / 1.", float!(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp() {
|
||||
test_expr("1 < 2", boolean!(true));
|
||||
test_expr("1 < 1", boolean!(false));
|
||||
test_expr("1 > 0", boolean!(true));
|
||||
test_expr("1 > 1", boolean!(false));
|
||||
test_expr("1 <= 1", boolean!(true));
|
||||
test_expr("1 <= 0", boolean!(false));
|
||||
test_expr("1 >= 1", boolean!(true));
|
||||
test_expr("1 >= 2", boolean!(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
test_expr(r#""test""#, string!("test"));
|
||||
test_expr(r#""hello" + " world""#, string!("hello world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bool() {
|
||||
test_expr("true", boolean!(true));
|
||||
test_expr("false", boolean!(false));
|
||||
test_expr("!false", boolean!(true));
|
||||
test_expr("true && false", boolean!(false));
|
||||
test_expr("true || false", boolean!(true));
|
||||
test_expr("true -> false", boolean!(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list() {
|
||||
test_expr(
|
||||
"[ 1 2 3 true ]",
|
||||
list![int!(1), int!(2), int!(3), boolean!(true)],
|
||||
);
|
||||
test_expr(
|
||||
"[ 1 2 ] ++ [ 3 4 ]",
|
||||
list![int!(1), int!(2), int!(3), int!(4)],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attrs() {
|
||||
test_expr(
|
||||
"{ a = 1; }",
|
||||
attrs! {
|
||||
symbol!("a") => int!(1)
|
||||
},
|
||||
);
|
||||
test_expr(
|
||||
"rec { a = 1; b = a; }",
|
||||
attrs! {
|
||||
symbol!("a") => int!(1),
|
||||
// symbol!("b") => int!(1)
|
||||
symbol!("b") => thunk!()
|
||||
},
|
||||
);
|
||||
test_expr("{ a = 1; }.a", int!(1));
|
||||
test_expr("{ a = 1; }.b or 1", int!(1));
|
||||
test_expr(
|
||||
"{ a = { a = 1; }; }.a",
|
||||
attrs! {
|
||||
symbol!("a") => int!(1)
|
||||
},
|
||||
);
|
||||
test_expr("{ a.b = 1; }.a.b", int!(1));
|
||||
test_expr(
|
||||
"{ a.b = 1; a.c = 2; }",
|
||||
attrs! { symbol!("a") => attrs!{ symbol!("b") => int!(1), symbol!("c") => int!(2) } },
|
||||
);
|
||||
test_expr("{ a.b = 1; } ? a.b", boolean!(true));
|
||||
test_expr(
|
||||
"{ a.b = 1; } // { a.c = 2; }",
|
||||
attrs! { symbol!("a") => attrs!{ symbol!("c") => int!(2) } },
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if() {
|
||||
test_expr("if true || false then 1 else 2", int!(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_let() {
|
||||
test_expr(r#"let a = 1; in a"#, int!(1));
|
||||
test_expr(r#"let a = 1; b = a; in b"#, int!(1));
|
||||
test_expr(r#"let a = { a = 1; }; b = "a"; in a.${b}"#, int!(1));
|
||||
test_expr(
|
||||
r#"let b = "c"; in { a.b = 1; } // { a."a${b}" = 2; }"#,
|
||||
attrs! { symbol!("a") => attrs!{ symbol!("ac") => int!(2) } },
|
||||
);
|
||||
test_expr(
|
||||
"let f = n: let a = n; f = x: a + x; in f; in f 0 1",
|
||||
int!(1),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_func() {
|
||||
test_expr("(x: x) 1", int!(1));
|
||||
test_expr("(x: x) (x: x) 1", int!(1));
|
||||
test_expr("(x: y: x / y) 1 2", int!(0));
|
||||
test_expr("({ x, y }: x + y) { x = 1; y = 2; }", int!(3));
|
||||
test_expr("({ x, y, ... }: x + y) { x = 1; y = 2; z = 3; }", int!(3));
|
||||
test_expr(
|
||||
"(inputs@{ x, y, ... }: x + inputs.y) { x = 1; y = 2; z = 3; }",
|
||||
int!(3),
|
||||
);
|
||||
test_expr("let fix = f: let x = f x; in x; in (fix (self: { x = 1; y = self.x + 1; })).y", int!(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_fib() {
|
||||
test_expr(
|
||||
"let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30",
|
||||
int!(832040),
|
||||
);
|
||||
}
|
||||
21
evaluator/nixjit_context/Cargo.toml
Normal file
21
evaluator/nixjit_context/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "nixjit_context"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
hashbrown = "0.15"
|
||||
itertools = "0.14"
|
||||
|
||||
cranelift = "0.122"
|
||||
cranelift-module = "0.122"
|
||||
cranelift-jit = "0.122"
|
||||
cranelift-native = "0.122"
|
||||
|
||||
nixjit_error = { path = "../nixjit_error" }
|
||||
nixjit_eval = { path = "../nixjit_eval" }
|
||||
nixjit_hir = { path = "../nixjit_hir" }
|
||||
nixjit_ir = { path = "../nixjit_ir" }
|
||||
nixjit_jit = { path = "../nixjit_jit" }
|
||||
nixjit_lir = { path = "../nixjit_lir" }
|
||||
nixjit_value = { path = "../nixjit_value" }
|
||||
145
evaluator/nixjit_context/src/lib.rs
Normal file
145
evaluator/nixjit_context/src/lib.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use core::mem::MaybeUninit;
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use itertools::Itertools;
|
||||
|
||||
use nixjit_error::Result;
|
||||
use nixjit_eval::EvalContext;
|
||||
use nixjit_hir::{DowngradeContext, Hir};
|
||||
use nixjit_ir::{ExprId, Param};
|
||||
use nixjit_lir::{Lir, ResolveContext};
|
||||
use nixjit_value::Value;
|
||||
|
||||
use nixjit_jit::{JITCompiler, JITContext, JITFunc};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Context {
|
||||
hirs: Vec<RefCell<Hir>>,
|
||||
lirs: Vec<MaybeUninit<Lir>>,
|
||||
jit: JITCompiler<Self>,
|
||||
compiled: Vec<OnceCell<JITFunc<Self>>>,
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.lirs.iter_mut().for_each(|lir| lir.assume_init_drop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl DowngradeContext for Context {
|
||||
fn new_expr(&mut self, expr: Hir) -> ExprId {
|
||||
let id = unsafe { core::mem::transmute(self.hirs.len() + self.lirs.len()) };
|
||||
self.hirs.push(expr.into());
|
||||
id
|
||||
}
|
||||
fn with_expr<T>(&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)
|
||||
}
|
||||
}
|
||||
fn with_expr_mut<T>(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T {
|
||||
unsafe {
|
||||
let self_mut = &mut *(self as *mut Context);
|
||||
let idx: usize = core::mem::transmute(id);
|
||||
f(&mut self.hirs.get_unchecked_mut(idx).borrow_mut(), self_mut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolveContext for Context {
|
||||
fn lookup(&self, name: &str) -> nixjit_lir::LookupResult {
|
||||
todo!()
|
||||
}
|
||||
fn new_dep(&mut self, expr: ExprId, dep: ExprId) {
|
||||
todo!()
|
||||
}
|
||||
fn resolve(&mut self, expr: ExprId) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
fn new_func(&mut self, body: ExprId, param: Param) {
|
||||
todo!()
|
||||
}
|
||||
fn with_let_env<'a, T>(
|
||||
&mut self,
|
||||
bindings: impl IntoIterator<Item = (&'a String, &'a ExprId)>,
|
||||
f: impl FnOnce(&mut Self) -> T,
|
||||
) -> T {
|
||||
todo!()
|
||||
}
|
||||
fn with_with_env<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T) {
|
||||
todo!()
|
||||
}
|
||||
fn with_param_env<'a, T>(
|
||||
&mut self,
|
||||
ident: Option<&'a str>,
|
||||
f: impl FnOnce(&mut Self) -> T,
|
||||
) -> T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl EvalContext for Context {
|
||||
fn eval(&mut self, expr: ExprId) -> Result<nixjit_eval::Value<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
fn pop_frame(&mut self) -> Vec<nixjit_eval::Value<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a nixjit_eval::Value<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
fn with_with_env<T>(
|
||||
&mut self,
|
||||
namespace: std::rc::Rc<HashMap<String, nixjit_eval::Value<Self>>>,
|
||||
f: impl FnOnce(&mut Self) -> T,
|
||||
) -> T
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
fn with_args_env<T>(
|
||||
&mut self,
|
||||
args: Vec<nixjit_eval::Value<Self>>,
|
||||
f: impl FnOnce(&mut Self) -> T,
|
||||
) -> (Vec<nixjit_eval::Value<Self>>, T)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl JITContext for Context {
|
||||
fn lookup_arg(&self, offset: usize) -> &nixjit_eval::Value<Self> {
|
||||
todo!()
|
||||
}
|
||||
fn lookup_stack(&self, offset: usize) -> &nixjit_eval::Value<Self> {
|
||||
todo!()
|
||||
}
|
||||
fn enter_with(&mut self, namespace: std::rc::Rc<HashMap<String, nixjit_eval::Value<Self>>>) {
|
||||
todo!()
|
||||
}
|
||||
fn exit_with(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
7
evaluator/nixjit_error/Cargo.toml
Normal file
7
evaluator/nixjit_error/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "nixjit_error"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "2.0"
|
||||
17
evaluator/nixjit_error/src/lib.rs
Normal file
17
evaluator/nixjit_error/src/lib.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("error occurred during parse stage: {0}")]
|
||||
ParseError(String),
|
||||
#[error("error occurred during downgrade stage: {0}")]
|
||||
DowngradeError(String),
|
||||
#[error("error occurred during variable resolve stage: {0}")]
|
||||
ResolutionError(String),
|
||||
#[error("error occurred during evaluation stage: {0}")]
|
||||
EvalError(String),
|
||||
#[error("unknown error")]
|
||||
Unknown,
|
||||
}
|
||||
15
evaluator/nixjit_eval/Cargo.toml
Normal file
15
evaluator/nixjit_eval/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "nixjit_eval"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
derive_more = { version = "2.0", features = ["full"] }
|
||||
hashbrown = "0.15"
|
||||
itertools = "0.14"
|
||||
replace_with = "0.1"
|
||||
|
||||
nixjit_error = { path = "../nixjit_error" }
|
||||
nixjit_ir = { path = "../nixjit_ir" }
|
||||
nixjit_lir = { path = "../nixjit_lir" }
|
||||
nixjit_value = { path = "../nixjit_value" }
|
||||
308
evaluator/nixjit_eval/src/lib.rs
Normal file
308
evaluator/nixjit_eval/src/lib.rs
Normal file
@@ -0,0 +1,308 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use nixjit_error::{Error, Result};
|
||||
use nixjit_ir::{self as ir, ExprId};
|
||||
use nixjit_lir as lir;
|
||||
use nixjit_value::{Const, Symbol};
|
||||
|
||||
pub use crate::value::*;
|
||||
|
||||
mod value;
|
||||
|
||||
pub trait EvalContext: Sized {
|
||||
fn eval(&mut self, expr: ExprId) -> Result<Value<Self>>;
|
||||
fn with_with_env<T>(
|
||||
&mut self,
|
||||
namespace: Rc<HashMap<String, Value<Self>>>,
|
||||
f: impl FnOnce(&mut Self) -> T,
|
||||
) -> T;
|
||||
fn with_args_env<T>(
|
||||
&mut self,
|
||||
args: Vec<Value<Self>>,
|
||||
f: impl FnOnce(&mut Self) -> T,
|
||||
) -> (Vec<Value<Self>>, T);
|
||||
fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a Value<Self>>;
|
||||
fn pop_frame(&mut self) -> Vec<Value<Self>>;
|
||||
}
|
||||
|
||||
pub trait Evaluate<Ctx: EvalContext> {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>>;
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ExprId {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
ctx.eval(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for lir::Lir {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::AttrSet {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
let mut attrs = AttrSet::new(
|
||||
self.stcs
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let eval_result = v.eval(ctx);
|
||||
Ok((k.clone(), eval_result?))
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
);
|
||||
for (k, v) in self.dyns.iter() {
|
||||
let mut k = k.eval(ctx)?;
|
||||
k.coerce_to_string();
|
||||
let v_eval_result = v.eval(ctx)?;
|
||||
attrs.push_attr(k.unwrap_string(), v_eval_result);
|
||||
}
|
||||
let result = Value::AttrSet(attrs.into()).ok();
|
||||
Ok(result.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::List {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
let items = self
|
||||
.items
|
||||
.iter()
|
||||
.map(|val| val.eval(ctx))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let result = Value::List(List::from(items).into()).ok();
|
||||
Ok(result.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::HasAttr {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
use ir::Attr::*;
|
||||
let mut val = self.lhs.eval(ctx)?;
|
||||
val.has_attr(self.rhs.iter().map(|attr| {
|
||||
Ok(match attr {
|
||||
Str(ident) => ident.clone(),
|
||||
Dynamic(expr) => {
|
||||
let mut val = expr.eval(ctx)?;
|
||||
val.coerce_to_string();
|
||||
val.unwrap_string()
|
||||
}
|
||||
})
|
||||
}))?;
|
||||
let result = val.ok();
|
||||
Ok(result.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
use ir::BinOpKind::*;
|
||||
let mut lhs = self.lhs.eval(ctx)?;
|
||||
let mut rhs = self.rhs.eval(ctx)?;
|
||||
match self.kind {
|
||||
Add => lhs.add(rhs),
|
||||
Sub => {
|
||||
rhs.neg();
|
||||
lhs.add(rhs);
|
||||
}
|
||||
Mul => lhs.mul(rhs),
|
||||
Div => lhs.div(rhs)?,
|
||||
Eq => Value::eq(&mut lhs, &rhs),
|
||||
Neq => {
|
||||
Value::eq(&mut lhs, &rhs);
|
||||
lhs.not();
|
||||
}
|
||||
Lt => lhs.lt(rhs),
|
||||
Gt => {
|
||||
rhs.lt(lhs);
|
||||
lhs = rhs;
|
||||
}
|
||||
Leq => {
|
||||
rhs.lt(lhs);
|
||||
rhs.not();
|
||||
lhs = rhs;
|
||||
}
|
||||
Geq => {
|
||||
lhs.lt(rhs);
|
||||
lhs.not();
|
||||
}
|
||||
And => lhs.and(rhs),
|
||||
Or => lhs.or(rhs),
|
||||
Impl => {
|
||||
lhs.not();
|
||||
lhs.or(rhs);
|
||||
}
|
||||
Con => lhs.concat(rhs),
|
||||
Upd => lhs.update(rhs),
|
||||
PipeL => lhs.call(vec![rhs], ctx)?,
|
||||
PipeR => {
|
||||
rhs.call(vec![lhs], ctx)?;
|
||||
lhs = rhs;
|
||||
}
|
||||
}
|
||||
Ok(lhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::UnOp {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
use ir::UnOpKind::*;
|
||||
let mut rhs = self.rhs.eval(ctx)?;
|
||||
match self.kind {
|
||||
Neg => {
|
||||
rhs.neg();
|
||||
}
|
||||
Not => {
|
||||
rhs.not();
|
||||
}
|
||||
};
|
||||
Ok(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Select {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
use ir::Attr::*;
|
||||
let mut val = self.expr.eval(ctx)?;
|
||||
if let Some(default) = &self.default {
|
||||
let default = default.eval(ctx)?;
|
||||
val.select_with_default(
|
||||
self.attrpath.iter().map(|attr| {
|
||||
Ok(match attr {
|
||||
Str(ident) => ident.clone(),
|
||||
Dynamic(expr) => {
|
||||
let mut val = expr.eval(ctx)?;
|
||||
val.coerce_to_string();
|
||||
val.unwrap_string()
|
||||
}
|
||||
})
|
||||
}),
|
||||
default,
|
||||
)?;
|
||||
} else {
|
||||
val.select(self.attrpath.iter().map(|attr| {
|
||||
Ok(match attr {
|
||||
Str(ident) => ident.clone(),
|
||||
Dynamic(expr) => {
|
||||
let mut val = expr.eval(ctx)?;
|
||||
val.coerce_to_string();
|
||||
val.unwrap_string()
|
||||
}
|
||||
})
|
||||
}))?;
|
||||
}
|
||||
let result = val.ok();
|
||||
Ok(result.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::If {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
// TODO: Error Handling
|
||||
let cond = self.cond.eval(ctx)?;
|
||||
let cond = cond
|
||||
.try_unwrap_bool()
|
||||
.map_err(|_| Error::EvalError(format!("expected a boolean but found ...")))?;
|
||||
|
||||
if cond {
|
||||
self.consq.eval(ctx)
|
||||
} else {
|
||||
self.alter.eval(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Call {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
let mut func = self.func.eval(ctx)?;
|
||||
func.call(
|
||||
self.args
|
||||
.iter()
|
||||
.map(|arg| arg.eval(ctx))
|
||||
.collect::<Result<_>>()?,
|
||||
ctx,
|
||||
)?;
|
||||
Ok(func.ok().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::With {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
let namespace = self.namespace.eval(ctx)?;
|
||||
ctx.with_with_env(
|
||||
namespace
|
||||
.try_unwrap_attr_set()
|
||||
.map_err(|_| Error::EvalError(format!("expected a set but found ...")))?
|
||||
.into_inner(),
|
||||
|ctx| self.expr.eval(ctx),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Assert {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::ConcatStrings {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
let mut parts = self
|
||||
.parts
|
||||
.iter()
|
||||
.map(|part| {
|
||||
let mut part = part.eval(ctx)?;
|
||||
part.coerce_to_string();
|
||||
part.ok()
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter();
|
||||
let init = parts.next().unwrap();
|
||||
let result = parts.fold(init, |mut a, b| {
|
||||
a.concat_string(b);
|
||||
a
|
||||
});
|
||||
Ok(result.ok().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Str {
|
||||
fn eval(&self, _: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
let result = Value::String(self.val.clone()).ok();
|
||||
Ok(result.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Const {
|
||||
fn eval(&self, _: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
let result = match self.val {
|
||||
Const::Null => Value::Null,
|
||||
Const::Int(x) => Value::Int(x),
|
||||
Const::Float(x) => Value::Float(x),
|
||||
Const::Bool(x) => Value::Bool(x),
|
||||
}
|
||||
.ok();
|
||||
Ok(result.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Var {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
ctx.lookup_with(&self.sym)
|
||||
.ok_or_else(|| {
|
||||
Error::EvalError(format!(
|
||||
"variable {} not found",
|
||||
Symbol::from(self.sym.clone())
|
||||
))
|
||||
})
|
||||
.map(|val| val.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Path {
|
||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value<Ctx>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
148
evaluator/nixjit_eval/src/value/attrset.rs
Normal file
148
evaluator/nixjit_eval/src/value/attrset.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use core::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use derive_more::Constructor;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use itertools::Itertools;
|
||||
|
||||
use nixjit_error::{Error, Result};
|
||||
use nixjit_value as p;
|
||||
use nixjit_value::Symbol;
|
||||
|
||||
use super::Value;
|
||||
use crate::EvalContext;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Constructor, PartialEq)]
|
||||
pub struct AttrSet<Ctx: EvalContext> {
|
||||
data: HashMap<String, Value<Ctx>>,
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Debug for AttrSet<Ctx> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use Value::*;
|
||||
write!(f, "{{ ")?;
|
||||
for (k, v) in self.data.iter() {
|
||||
match v {
|
||||
List(_) => write!(f, "{k:?} = [ ... ]; ")?,
|
||||
AttrSet(_) => write!(f, "{k:?} = {{ ... }}; ")?,
|
||||
v => write!(f, "{k:?} = {v:?}; ")?,
|
||||
}
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Clone for AttrSet<Ctx> {
|
||||
fn clone(&self) -> Self {
|
||||
AttrSet {
|
||||
data: self.data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> From<HashMap<String, Value<Ctx>>> for AttrSet<Ctx> {
|
||||
fn from(data: HashMap<String, Value<Ctx>>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Deref for AttrSet<Ctx> {
|
||||
type Target = HashMap<String, Value<Ctx>>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> AttrSet<Ctx> {
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
AttrSet {
|
||||
data: HashMap::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_attr_force(&mut self, sym: String, val: Value<Ctx>) {
|
||||
self.data.insert(sym, val);
|
||||
}
|
||||
|
||||
pub fn push_attr(&mut self, sym: String, val: Value<Ctx>) {
|
||||
if self.data.get(&sym).is_some() {
|
||||
todo!()
|
||||
}
|
||||
self.data.insert(sym, val);
|
||||
}
|
||||
|
||||
pub fn select(
|
||||
&self,
|
||||
mut path: impl DoubleEndedIterator<Item = Result<String>>,
|
||||
) -> Result<Value<Ctx>> {
|
||||
let mut data = &self.data;
|
||||
let last = path.nth_back(0).unwrap();
|
||||
for item in path {
|
||||
let item = item?;
|
||||
let Some(Value::AttrSet(attrs)) = data.get(&item) else {
|
||||
return Err(Error::EvalError(format!(
|
||||
"attribute '{}' not found",
|
||||
Symbol::from(item)
|
||||
)));
|
||||
};
|
||||
data = attrs.as_inner();
|
||||
}
|
||||
let last = last?;
|
||||
data.get(&last).cloned().ok_or_else(|| {
|
||||
Error::EvalError(format!("attribute '{}' not found", Symbol::from(last)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_attr(
|
||||
&self,
|
||||
mut path: impl DoubleEndedIterator<Item = Result<String>>,
|
||||
) -> Result<bool> {
|
||||
let mut data = &self.data;
|
||||
let last = path.nth_back(0).unwrap();
|
||||
for item in path {
|
||||
let Some(Value::AttrSet(attrs)) = data.get(&item?) else {
|
||||
return Ok(false);
|
||||
};
|
||||
data = attrs.as_inner();
|
||||
}
|
||||
Ok(data.get(&last?).is_some())
|
||||
}
|
||||
|
||||
pub fn update(&mut self, other: &Self) {
|
||||
for (k, v) in other.data.iter() {
|
||||
self.push_attr_force(k.clone(), v.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_inner(&self) -> &HashMap<String, Value<Ctx>> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<String, Value<Ctx>>> {
|
||||
unsafe { core::mem::transmute(self) }
|
||||
}
|
||||
|
||||
pub fn from_inner(data: HashMap<String, Value<Ctx>>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
|
||||
pub fn eq_impl(&self, other: &Self) -> bool {
|
||||
self.data.iter().len() == other.data.iter().len()
|
||||
&& std::iter::zip(
|
||||
self.data.iter().sorted_by(|(a, _), (b, _)| a.cmp(b)),
|
||||
other.data.iter().sorted_by(|(a, _), (b, _)| a.cmp(b)),
|
||||
)
|
||||
.all(|((k1, v1), (k2, v2))| k1 == k2 && v1.eq_impl(v2))
|
||||
}
|
||||
|
||||
pub fn to_public(&self, ctx: &Ctx, seen: &mut HashSet<Value<Ctx>>) -> p::Value {
|
||||
p::Value::AttrSet(p::AttrSet::new(
|
||||
self.data
|
||||
.iter()
|
||||
.map(|(sym, value)| (sym.as_str().into(), value.to_public(ctx, seen)))
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
47
evaluator/nixjit_eval/src/value/func.rs
Normal file
47
evaluator/nixjit_eval/src/value/func.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use derive_more::Constructor;
|
||||
|
||||
use nixjit_error::Result;
|
||||
use nixjit_ir::ExprId;
|
||||
|
||||
use super::Value;
|
||||
use crate::EvalContext;
|
||||
|
||||
#[derive(Debug, Constructor)]
|
||||
pub struct FuncApp<Ctx: EvalContext> {
|
||||
pub body: ExprId,
|
||||
pub args: Vec<Value<Ctx>>,
|
||||
pub frame: Vec<Value<Ctx>>,
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Clone for FuncApp<Ctx> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
body: self.body,
|
||||
args: self.args.clone(),
|
||||
frame: self.frame.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> FuncApp<Ctx> {
|
||||
pub fn call(
|
||||
self: &mut Rc<Self>,
|
||||
new_args: Vec<Value<Ctx>>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<Value<Ctx>> {
|
||||
let FuncApp { body: expr, args, frame } = Rc::make_mut(self);
|
||||
args.extend(new_args);
|
||||
let (args, ret) = ctx.with_args_env(core::mem::take(args), |ctx| ctx.eval(*expr));
|
||||
let mut ret = ret?;
|
||||
if let Value::Func(expr) = ret {
|
||||
let frame = ctx.pop_frame();
|
||||
ret = Value::FuncApp(FuncApp::new(expr, args, frame).into());
|
||||
} else if let Value::FuncApp(func) = &mut ret {
|
||||
todo!();
|
||||
let func = Rc::make_mut(func);
|
||||
}
|
||||
ret.ok()
|
||||
}
|
||||
}
|
||||
86
evaluator/nixjit_eval/src/value/list.rs
Normal file
86
evaluator/nixjit_eval/src/value/list.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use std::ops::Deref;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use hashbrown::HashSet;
|
||||
|
||||
use nixjit_value::List as PubList;
|
||||
use nixjit_value::Value as PubValue;
|
||||
|
||||
use super::Value;
|
||||
use crate::EvalContext;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct List<Ctx: EvalContext> {
|
||||
data: Vec<Value<Ctx>>,
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Debug for List<Ctx> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[ ")?;
|
||||
for v in self.data.iter() {
|
||||
write!(f, "{v:?} ")?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Clone for List<Ctx> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data: self.data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext, T: Into<Vec<Value<Ctx>>>> From<T> for List<Ctx> {
|
||||
fn from(value: T) -> Self {
|
||||
Self { data: value.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Deref for List<Ctx> {
|
||||
type Target = [Value<Ctx>];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> List<Ctx> {
|
||||
pub fn new() -> Self {
|
||||
List { data: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
List {
|
||||
data: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, elem: Value<Ctx>) {
|
||||
self.data.push(elem);
|
||||
}
|
||||
|
||||
pub fn concat(&mut self, other: &Self) {
|
||||
for elem in other.data.iter() {
|
||||
self.data.push(elem.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Vec<Value<Ctx>> {
|
||||
self.data
|
||||
}
|
||||
|
||||
pub fn eq_impl(&self, other: &Self) -> bool {
|
||||
self.len() == other.len()
|
||||
&& core::iter::zip(self.iter(), other.iter()).all(|(a, b)| a.eq_impl(b))
|
||||
}
|
||||
|
||||
pub fn to_public(&self, engine: &Ctx, seen: &mut HashSet<Value<Ctx>>) -> PubValue {
|
||||
PubValue::List(PubList::new(
|
||||
self.data
|
||||
.iter()
|
||||
.map(|value| value.clone().to_public(engine, seen))
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
516
evaluator/nixjit_eval/src/value/mod.rs
Normal file
516
evaluator/nixjit_eval/src/value/mod.rs
Normal file
@@ -0,0 +1,516 @@
|
||||
use std::hash::Hash;
|
||||
use std::rc::Rc;
|
||||
use std::fmt::{write, Debug};
|
||||
|
||||
use derive_more::TryUnwrap;
|
||||
use derive_more::{IsVariant, Unwrap};
|
||||
use func::FuncApp;
|
||||
use hashbrown::HashSet;
|
||||
use nixjit_ir::ExprId;
|
||||
use replace_with::replace_with_or_abort;
|
||||
|
||||
use nixjit_error::{Error, Result};
|
||||
use nixjit_value::Const;
|
||||
use nixjit_value::Value as PubValue;
|
||||
|
||||
use crate::EvalContext;
|
||||
|
||||
mod attrset;
|
||||
mod func;
|
||||
mod list;
|
||||
mod primop;
|
||||
mod string;
|
||||
|
||||
pub use attrset::*;
|
||||
pub use list::List;
|
||||
pub use primop::*;
|
||||
|
||||
#[repr(C, u64)]
|
||||
#[derive(IsVariant, TryUnwrap, Unwrap)]
|
||||
pub enum Value<Ctx: EvalContext> {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
Null,
|
||||
Thunk(usize),
|
||||
AttrSet(Rc<AttrSet<Ctx>>),
|
||||
List(Rc<List<Ctx>>),
|
||||
Catchable(String),
|
||||
PrimOp(Rc<PrimOp<Ctx>>),
|
||||
PrimOpApp(Rc<PrimOpApp<Ctx>>),
|
||||
Func(ExprId),
|
||||
FuncApp(Rc<FuncApp<Ctx>>),
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Debug for Value<Ctx> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use Value::*;
|
||||
match self {
|
||||
Int(x) => write!(f, "{x}"),
|
||||
Float(x) => write!(f, "{x}"),
|
||||
Bool(x) => write!(f, "{x}"),
|
||||
Null => write!(f, "null"),
|
||||
String(x) => write!(f, "{x}"),
|
||||
AttrSet(x) => write!(f, "{x:?}"),
|
||||
List(x) => write!(f, "{x:?}"),
|
||||
Catchable(x) => write!(f, "{x}"),
|
||||
Thunk(thunk) => write!(f, "<THUNK {thunk:?}>"),
|
||||
Func(func) => write!(f, "<LAMBDA {func:?}>"),
|
||||
FuncApp(func) => write!(f, "<LAMBDA-APP {:?}>", func.body),
|
||||
PrimOp(primop) => write!(f, "<PRIMOP {}>", primop.name),
|
||||
PrimOpApp(primop) => write!(f, "<PRIMOP-APP {}>", primop.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Clone for Value<Ctx> {
|
||||
fn clone(&self) -> Self {
|
||||
use Value::*;
|
||||
match self {
|
||||
AttrSet(attrs) => AttrSet(attrs.clone()),
|
||||
List(list) => List(list.clone()),
|
||||
Catchable(catchable) => Catchable(catchable.clone()),
|
||||
Int(x) => Int(*x),
|
||||
Float(x) => Float(*x),
|
||||
Bool(x) => Bool(*x),
|
||||
String(x) => String(x.clone()),
|
||||
Null => Null,
|
||||
Thunk(expr) => Thunk(*expr),
|
||||
PrimOp(primop) => PrimOp(primop.clone()),
|
||||
PrimOpApp(primop) => PrimOpApp(primop.clone()),
|
||||
Func(expr) => Func(*expr),
|
||||
FuncApp(func) => FuncApp(func.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Hash for Value<Ctx> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
use Value::*;
|
||||
std::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
AttrSet(x) => Rc::as_ptr(x).hash(state),
|
||||
List(x) => x.as_ptr().hash(state),
|
||||
_ => 0.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Value<Ctx> {
|
||||
pub const INT: u64 = 0;
|
||||
pub const FLOAT: u64 = 1;
|
||||
pub const BOOL: u64 = 2;
|
||||
pub const STRING: u64 = 3;
|
||||
pub const NULL: u64 = 4;
|
||||
pub const THUNK: u64 = 5;
|
||||
pub const ATTRSET: u64 = 6;
|
||||
pub const LIST: u64 = 7;
|
||||
pub const CATCHABLE: u64 = 8;
|
||||
pub const PRIMOP: u64 = 9;
|
||||
pub const PARTIAL_PRIMOP: u64 = 10;
|
||||
pub const FUNC: u64 = 11;
|
||||
pub const PARTIAL_FUNC: u64 = 12;
|
||||
|
||||
fn eq_impl(&self, other: &Self) -> bool {
|
||||
use Value::*;
|
||||
match (self, other) {
|
||||
(Bool(a), Bool(b)) => a == b,
|
||||
(Int(a), Int(b)) => a == b,
|
||||
(Float(a), Float(b)) => a == b,
|
||||
(Int(a), Float(b)) => *a as f64 == *b,
|
||||
(Float(a), Int(b)) => *b as f64 == *a,
|
||||
(String(a), String(b)) => a.as_str().eq(b.as_str()),
|
||||
(Null, Null) => true,
|
||||
(AttrSet(a), AttrSet(b)) => a.eq_impl(b),
|
||||
(List(a), List(b)) => a.eq_impl(b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> PartialEq for Value<Ctx> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use Value::*;
|
||||
match (self, other) {
|
||||
(AttrSet(a), AttrSet(b)) => Rc::as_ptr(a).eq(&Rc::as_ptr(b)),
|
||||
(List(a), List(b)) => a.as_ptr().eq(&b.as_ptr()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Eq for Value<Ctx> {}
|
||||
|
||||
#[derive(IsVariant, Unwrap, Clone)]
|
||||
pub enum ValueAsRef<'v, Ctx: EvalContext> {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(&'v String),
|
||||
Null,
|
||||
Thunk(usize),
|
||||
AttrSet(&'v AttrSet<Ctx>),
|
||||
List(&'v List<Ctx>),
|
||||
Catchable(&'v str),
|
||||
PrimOp(&'v PrimOp<Ctx>),
|
||||
PartialPrimOp(&'v PrimOpApp<Ctx>),
|
||||
Func(ExprId),
|
||||
PartialFunc(&'v FuncApp<Ctx>),
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Value<Ctx> {
|
||||
pub fn as_ref(&self) -> ValueAsRef<'_, Ctx> {
|
||||
use Value::*;
|
||||
use ValueAsRef as R;
|
||||
match self {
|
||||
Int(x) => R::Int(*x),
|
||||
Float(x) => R::Float(*x),
|
||||
Bool(x) => R::Bool(*x),
|
||||
String(x) => R::String(x),
|
||||
Null => R::Null,
|
||||
Thunk(x) => R::Thunk(*x),
|
||||
AttrSet(x) => R::AttrSet(x),
|
||||
List(x) => R::List(x),
|
||||
Catchable(x) => R::Catchable(x),
|
||||
PrimOp(x) => R::PrimOp(x),
|
||||
PrimOpApp(x) => R::PartialPrimOp(x),
|
||||
Func(x) => R::Func(*x),
|
||||
FuncApp(x) => R::PartialFunc(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Ctx: EvalContext> Value<Ctx> {
|
||||
pub fn ok(self) -> Result<Self> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn typename(&self) -> &'static str {
|
||||
use Value::*;
|
||||
match self {
|
||||
Int(_) => "int",
|
||||
Float(_) => "float",
|
||||
Bool(_) => "bool",
|
||||
String(_) => "string",
|
||||
Null => "null",
|
||||
Thunk(_) => "thunk",
|
||||
AttrSet(_) => "set",
|
||||
List(_) => "list",
|
||||
Catchable(_) => unreachable!(),
|
||||
PrimOp(_) => "lambda",
|
||||
PrimOpApp(_) => "lambda",
|
||||
Func(_) => "lambda",
|
||||
FuncApp(..) => "lambda",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn callable(&self) -> bool {
|
||||
match self {
|
||||
Value::PrimOp(_) | Value::PrimOpApp(_) | Value::Func(_) => true,
|
||||
Value::AttrSet(_) => todo!(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&mut self, args: Vec<Self>, ctx: &mut Ctx) -> Result<()> {
|
||||
use Value::*;
|
||||
for arg in args.iter() {
|
||||
if matches!(arg, Value::Catchable(_)) {
|
||||
*self = arg.clone();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
*self = match self {
|
||||
PrimOp(func) => func.call(args, ctx),
|
||||
PrimOpApp(func) => func.call(args, ctx),
|
||||
FuncApp(func) => func.call(args, ctx),
|
||||
&mut Func(expr) => {
|
||||
let (args, ret) = ctx.with_args_env(args, |ctx| ctx.eval(expr));
|
||||
let mut ret = ret?;
|
||||
if let Value::Func(expr) = ret {
|
||||
let frame = ctx.pop_frame();
|
||||
ret = Value::FuncApp(self::FuncApp::new(expr, args, frame).into());
|
||||
} else if let Value::FuncApp(func) = &mut ret {
|
||||
todo!();
|
||||
let func = Rc::make_mut(func);
|
||||
}
|
||||
ret.ok()
|
||||
}
|
||||
Catchable(_) => return Ok(()),
|
||||
other => todo!("{}", other.typename()),
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn not(&mut self) {
|
||||
use Value::*;
|
||||
*self = match &*self {
|
||||
Bool(bool) => Bool(!bool),
|
||||
Value::Catchable(_) => return,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and(&mut self, other: Self) {
|
||||
use Value::*;
|
||||
*self = match (&*self, other) {
|
||||
(Bool(a), Bool(b)) => Bool(*a && b),
|
||||
(Value::Catchable(_), _) => return,
|
||||
(_, x @ Value::Catchable(_)) => x,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn or(&mut self, other: Self) {
|
||||
use Value::*;
|
||||
*self = match (&*self, other) {
|
||||
(Bool(a), Bool(b)) => Bool(*a || b),
|
||||
(Value::Catchable(_), _) => return,
|
||||
(_, x @ Value::Catchable(_)) => x,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq(&mut self, other: &Self) {
|
||||
use Value::Bool;
|
||||
*self = match (&*self, other) {
|
||||
(Value::Catchable(_), _) => return,
|
||||
(_, x @ Value::Catchable(_)) => x.clone(),
|
||||
(s, other) => Bool(s.eq_impl(other)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn lt(&mut self, other: Self) {
|
||||
use Value::*;
|
||||
*self = Bool(match (&*self, other) {
|
||||
(Int(a), Int(b)) => *a < b,
|
||||
(Int(a), Float(b)) => (*a as f64) < b,
|
||||
(Float(a), Int(b)) => *a < b as f64,
|
||||
(Float(a), Float(b)) => *a < b,
|
||||
(String(a), String(b)) => a.as_str() < b.as_str(),
|
||||
(Value::Catchable(_), _) => return,
|
||||
(_, x @ Value::Catchable(_)) => {
|
||||
*self = x;
|
||||
return;
|
||||
}
|
||||
_ => todo!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn neg(&mut self) {
|
||||
use Value::*;
|
||||
*self = match &*self {
|
||||
Int(int) => Int(-int),
|
||||
Float(float) => Float(-float),
|
||||
Value::Catchable(_) => return,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, other: Self) {
|
||||
use Value::*;
|
||||
replace_with_or_abort(self, |a| match (a, other) {
|
||||
(Int(a), Int(b)) => Int(a + b),
|
||||
(Int(a), Float(b)) => Float(a as f64 + b),
|
||||
(Float(a), Int(b)) => Float(a + b as f64),
|
||||
(Float(a), Float(b)) => Float(a + b),
|
||||
(String(mut a), String(b)) => {
|
||||
a.push_str(&b);
|
||||
String(a)
|
||||
}
|
||||
(a @ Value::Catchable(_), _) => a,
|
||||
(_, x @ Value::Catchable(_)) => x,
|
||||
_ => todo!(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn mul(&mut self, other: Self) {
|
||||
use Value::*;
|
||||
*self = match (&*self, other) {
|
||||
(Int(a), Int(b)) => Int(a * b),
|
||||
(Int(a), Float(b)) => Float(*a as f64 * b),
|
||||
(Float(a), Int(b)) => Float(a * b as f64),
|
||||
(Float(a), Float(b)) => Float(a * b),
|
||||
(Value::Catchable(_), _) => return,
|
||||
(_, x @ Value::Catchable(_)) => x,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div(&mut self, other: Self) -> Result<()> {
|
||||
use Value::*;
|
||||
*self = match (&*self, other) {
|
||||
(_, Int(0)) => return Err(Error::EvalError("division by zero".to_string())),
|
||||
(_, Float(0.)) => {
|
||||
return Err(Error::EvalError("division by zero".to_string()));
|
||||
}
|
||||
(Int(a), Int(b)) => Int(a / b),
|
||||
(Int(a), Float(b)) => Float(*a as f64 / b),
|
||||
(Float(a), Int(b)) => Float(a / b as f64),
|
||||
(Float(a), Float(b)) => Float(a / b),
|
||||
(Catchable(_), _) => return Ok(()),
|
||||
(_, x @ Catchable(_)) => x,
|
||||
_ => todo!(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn concat_string(&mut self, mut other: Self) -> &mut Self {
|
||||
use Value::*;
|
||||
match (self.coerce_to_string(), other.coerce_to_string()) {
|
||||
(String(a), String(b)) => {
|
||||
a.push_str(b.as_str());
|
||||
}
|
||||
(_, Catchable(_)) => *self = other,
|
||||
(Catchable(_), _) => (),
|
||||
_ => todo!(),
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push(&mut self, elem: Self) -> &mut Self {
|
||||
use Value::*;
|
||||
if let List(list) = self {
|
||||
Rc::make_mut(list).push(elem);
|
||||
} else if let Catchable(_) = self {
|
||||
} else if let Catchable(_) = elem {
|
||||
*self = elem;
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn concat(&mut self, other: Self) {
|
||||
use Value::*;
|
||||
if let x @ Catchable(_) = other {
|
||||
*self = x;
|
||||
return;
|
||||
}
|
||||
match (self, other) {
|
||||
(List(a), List(b)) => {
|
||||
Rc::make_mut(a).concat(&b);
|
||||
}
|
||||
(Catchable(_), _) => (),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_attr(&mut self, sym: String, val: Self) -> &mut Self {
|
||||
use Value::*;
|
||||
if let AttrSet(attrs) = self {
|
||||
Rc::make_mut(attrs).push_attr(sym, val);
|
||||
} else if let Catchable(_) = self {
|
||||
} else if let Catchable(_) = val {
|
||||
*self = val
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn update(&mut self, other: Self) {
|
||||
use Value::*;
|
||||
if let x @ Catchable(_) = other {
|
||||
*self = x;
|
||||
return;
|
||||
}
|
||||
match (self, other) {
|
||||
(AttrSet(a), AttrSet(b)) => {
|
||||
Rc::make_mut(a).update(&b);
|
||||
}
|
||||
(Catchable(_), _) => (),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select(
|
||||
&mut self,
|
||||
path: impl DoubleEndedIterator<Item = Result<String>>,
|
||||
) -> Result<&mut Self> {
|
||||
use Value::*;
|
||||
let val = match self {
|
||||
AttrSet(attrs) => attrs.select(path),
|
||||
Catchable(_) => return Ok(self),
|
||||
_ => Err(Error::EvalError(format!(
|
||||
"can not select from {:?}",
|
||||
self.typename()
|
||||
))),
|
||||
}?;
|
||||
*self = val;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn select_with_default(
|
||||
&mut self,
|
||||
path: impl DoubleEndedIterator<Item = Result<String>>,
|
||||
default: Self,
|
||||
) -> Result<&mut Self> {
|
||||
use Value::*;
|
||||
let val = match self {
|
||||
AttrSet(attrs) => attrs.select(path).unwrap_or(default),
|
||||
Catchable(_) => return Ok(self),
|
||||
_ => {
|
||||
return Err(Error::EvalError(format!(
|
||||
"can not select from {:?}",
|
||||
self.typename()
|
||||
)));
|
||||
}
|
||||
};
|
||||
*self = val;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn has_attr(
|
||||
&mut self,
|
||||
path: impl DoubleEndedIterator<Item = Result<String>>,
|
||||
) -> Result<()> {
|
||||
use Value::*;
|
||||
if let AttrSet(attrs) = self {
|
||||
let val = Bool(attrs.has_attr(path)?);
|
||||
*self = val;
|
||||
} else if let Catchable(_) = self {
|
||||
} else {
|
||||
*self = Bool(false);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn coerce_to_string(&mut self) -> &mut Self {
|
||||
use Value::*;
|
||||
if let String(_) = self {
|
||||
} else if let Catchable(_) = self {
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_public(&self, ctx: &Ctx, seen: &mut HashSet<Value<Ctx>>) -> PubValue {
|
||||
use Value::*;
|
||||
if seen.contains(self) {
|
||||
return PubValue::Repeated;
|
||||
}
|
||||
match self {
|
||||
AttrSet(attrs) => {
|
||||
seen.insert(self.clone());
|
||||
attrs.to_public(ctx, seen)
|
||||
}
|
||||
List(list) => {
|
||||
seen.insert(self.clone());
|
||||
list.to_public(ctx, seen)
|
||||
}
|
||||
Catchable(catchable) => PubValue::Catchable(catchable.clone().into()),
|
||||
Int(x) => PubValue::Const(Const::Int(*x)),
|
||||
Float(x) => PubValue::Const(Const::Float(*x)),
|
||||
Bool(x) => PubValue::Const(Const::Bool(*x)),
|
||||
String(x) => PubValue::String(x.clone()),
|
||||
Null => PubValue::Const(Const::Null),
|
||||
Thunk(_) => PubValue::Thunk,
|
||||
PrimOp(primop) => PubValue::PrimOp(primop.name),
|
||||
PrimOpApp(primop) => PubValue::PrimOpApp(primop.name),
|
||||
Func(_) => PubValue::Func,
|
||||
FuncApp(..) => PubValue::Func,
|
||||
}
|
||||
}
|
||||
}
|
||||
75
evaluator/nixjit_eval/src/value/primop.rs
Normal file
75
evaluator/nixjit_eval/src/value/primop.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use derive_more::Constructor;
|
||||
|
||||
use nixjit_error::Result;
|
||||
|
||||
use super::Value;
|
||||
use crate::EvalContext;
|
||||
|
||||
#[derive(Debug, Clone, Constructor)]
|
||||
pub struct PrimOp<Ctx: EvalContext> {
|
||||
pub name: &'static str,
|
||||
arity: usize,
|
||||
func: fn(Vec<Value<Ctx>>, &Ctx) -> Result<Value<Ctx>>,
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> PrimOp<Ctx> {
|
||||
pub fn call(&self, args: Vec<Value<Ctx>>, ctx: &Ctx) -> Result<Value<Ctx>> {
|
||||
if args.len() > self.arity {
|
||||
todo!()
|
||||
}
|
||||
if self.arity > args.len() {
|
||||
Value::PrimOpApp(Rc::new(PrimOpApp {
|
||||
name: self.name,
|
||||
arity: self.arity - args.len(),
|
||||
args,
|
||||
func: self.func,
|
||||
}))
|
||||
.ok()
|
||||
} else {
|
||||
(self.func)(args, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrimOpApp<Ctx: EvalContext> {
|
||||
pub name: &'static str,
|
||||
arity: usize,
|
||||
args: Vec<Value<Ctx>>,
|
||||
func: fn(Vec<Value<Ctx>>, &Ctx) -> Result<Value<Ctx>>,
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> Clone for PrimOpApp<Ctx> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
name: self.name,
|
||||
arity: self.arity,
|
||||
args: self.args.clone(),
|
||||
func: self.func,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: EvalContext> PrimOpApp<Ctx> {
|
||||
pub fn call(self: &mut Rc<Self>, args: Vec<Value<Ctx>>, ctx: &Ctx) -> Result<Value<Ctx>> {
|
||||
if self.arity < args.len() {
|
||||
todo!()
|
||||
}
|
||||
let func = self.func;
|
||||
let Some(ret) = ({
|
||||
let self_mut = Rc::make_mut(self);
|
||||
self_mut.arity -= args.len();
|
||||
self_mut.args.extend(args);
|
||||
if self_mut.arity == 0 {
|
||||
Some(func(std::mem::take(&mut self_mut.args), ctx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) else {
|
||||
return Value::PrimOpApp(self.clone()).ok();
|
||||
};
|
||||
ret
|
||||
}
|
||||
}
|
||||
27
evaluator/nixjit_eval/src/value/string.rs
Normal file
27
evaluator/nixjit_eval/src/value/string.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
// TODO: Contextful String
|
||||
|
||||
pub struct StringContext {
|
||||
context: Vec<()>,
|
||||
}
|
||||
|
||||
impl StringContext {
|
||||
pub fn new() -> StringContext {
|
||||
StringContext {
|
||||
context: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContextfulString {
|
||||
string: String,
|
||||
context: StringContext,
|
||||
}
|
||||
|
||||
impl ContextfulString {
|
||||
pub fn new(string: String) -> ContextfulString {
|
||||
ContextfulString {
|
||||
string,
|
||||
context: StringContext::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
16
evaluator/nixjit_hir/Cargo.toml
Normal file
16
evaluator/nixjit_hir/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "nixjit_hir"
|
||||
description = "The high-level intermediate representation (HIR) for nixjit."
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
derive_more = { version = "2.0", features = ["full"] }
|
||||
hashbrown = "0.15"
|
||||
itertools = "0.14"
|
||||
rnix = "0.12"
|
||||
|
||||
nixjit_error = { path = "../nixjit_error" }
|
||||
nixjit_ir = { path = "../nixjit_ir" }
|
||||
nixjit_macros = { path = "../nixjit_macros" }
|
||||
nixjit_value = { path = "../nixjit_value" }
|
||||
357
evaluator/nixjit_hir/src/downgrade.rs
Normal file
357
evaluator/nixjit_hir/src/downgrade.rs
Normal file
@@ -0,0 +1,357 @@
|
||||
//! This module handles the "downgrading" of the `rnix` Abstract Syntax Tree (AST)
|
||||
//! into the High-level Intermediate Representation (HIR). The term "downgrade" is used
|
||||
//! because the process moves from a concrete syntax tree, which is very detailed about
|
||||
//! source code structure (like parentheses and whitespace), to a more abstract,
|
||||
//! semantically-focused representation.
|
||||
//!
|
||||
//! The core of this module is the `Downgrade` trait, which defines a standard way to
|
||||
//! convert different AST node types into their corresponding HIR representations.
|
||||
|
||||
use rnix::ast::{self, Expr};
|
||||
|
||||
use nixjit_error::{Error, Result};
|
||||
use nixjit_ir as ir;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A trait for converting (downgrading) an `rnix` AST node into an HIR expression.
|
||||
pub trait Downgrade<Ctx: DowngradeContext> {
|
||||
/// Performs the downgrade conversion.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `self` - The `rnix` AST node to convert.
|
||||
/// * `ctx` - The context for the conversion, used for allocating new HIR expressions.
|
||||
///
|
||||
/// # Returns
|
||||
/// A `Result` containing the `ExprId` of the newly created HIR expression, or an error.
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId>;
|
||||
}
|
||||
|
||||
/// The main entry point for downgrading any `rnix` expression.
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
use Expr::*;
|
||||
match self {
|
||||
// Dispatch to the specific implementation for each expression type.
|
||||
Apply(apply) => apply.downgrade(ctx),
|
||||
Assert(assert) => assert.downgrade(ctx),
|
||||
Error(error) => Err(self::Error::DowngradeError(error.to_string())),
|
||||
IfElse(ifelse) => ifelse.downgrade(ctx),
|
||||
Select(select) => select.downgrade(ctx),
|
||||
Str(str) => str.downgrade(ctx),
|
||||
Path(path) => path.downgrade(ctx),
|
||||
Literal(lit) => lit.downgrade(ctx),
|
||||
Lambda(lambda) => lambda.downgrade(ctx),
|
||||
LegacyLet(let_) => let_.downgrade(ctx),
|
||||
LetIn(letin) => letin.downgrade(ctx),
|
||||
List(list) => list.downgrade(ctx),
|
||||
BinOp(op) => op.downgrade(ctx),
|
||||
AttrSet(attrs) => attrs.downgrade(ctx),
|
||||
UnaryOp(op) => op.downgrade(ctx),
|
||||
Ident(ident) => ident.downgrade(ctx),
|
||||
With(with) => with.downgrade(ctx),
|
||||
HasAttr(has) => has.downgrade(ctx),
|
||||
// Parentheses and the root node are transparent; we just downgrade their contents.
|
||||
Paren(paren) => paren.expr().unwrap().downgrade(ctx),
|
||||
Root(root) => root.expr().unwrap().downgrade(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let assertion = self.condition().unwrap().downgrade(ctx)?;
|
||||
let expr = self.body().unwrap().downgrade(ctx)?;
|
||||
Ok(ctx.new_expr(Assert { assertion, expr }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let cond = self.condition().unwrap().downgrade(ctx)?;
|
||||
let consq = self.body().unwrap().downgrade(ctx)?;
|
||||
let alter = self.else_body().unwrap().downgrade(ctx)?;
|
||||
Ok(ctx.new_expr(If { cond, consq, alter }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Downgrades a path expression.
|
||||
/// A path can be a simple literal or contain interpolated expressions.
|
||||
/// If it contains interpolations, it's converted into a `ConcatStrings` HIR node
|
||||
/// which is then wrapped in a `Path` node.
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let parts = self
|
||||
.parts()
|
||||
.map(|part| match part {
|
||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
|
||||
Str {
|
||||
val: lit.to_string(),
|
||||
}
|
||||
.to_hir(),
|
||||
)),
|
||||
ast::InterpolPart::Interpolation(interpol) => {
|
||||
interpol.expr().unwrap().downgrade(ctx)
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let expr = if parts.len() == 1 {
|
||||
// If there's only one part, it's a simple string, no concatenation needed.
|
||||
parts.into_iter().next().unwrap()
|
||||
} else {
|
||||
// Multiple parts (e.g., `./${name}.txt`) require string concatenation.
|
||||
ctx.new_expr(ConcatStrings { parts }.to_hir())
|
||||
};
|
||||
Ok(ctx.new_expr(Path { expr }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Downgrades a string expression.
|
||||
/// A string can be a simple literal or contain interpolated expressions.
|
||||
/// If it contains interpolations, it's converted into a `ConcatStrings` HIR node.
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let parts = self
|
||||
.normalized_parts()
|
||||
.into_iter()
|
||||
.map(|part| match part {
|
||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit }.to_hir())),
|
||||
ast::InterpolPart::Interpolation(interpol) => {
|
||||
interpol.expr().unwrap().downgrade(ctx)
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(if parts.len() == 1 {
|
||||
// If there's only one part, it's a simple string, no concatenation needed.
|
||||
parts.into_iter().next().unwrap()
|
||||
} else {
|
||||
// Multiple parts (e.g., "hello ${name}") require string concatenation.
|
||||
ctx.new_expr(ConcatStrings { parts }.to_hir())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
Ok(ctx.new_expr(match self.kind() {
|
||||
ast::LiteralKind::Integer(int) => Const::from(int.value().unwrap()).to_hir(),
|
||||
ast::LiteralKind::Float(float) => Const::from(float.value().unwrap()).to_hir(),
|
||||
ast::LiteralKind::Uri(uri) => Str {
|
||||
val: uri.to_string(),
|
||||
}
|
||||
.to_hir(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let sym = self.ident_token().unwrap().to_string();
|
||||
Ok(ctx.new_expr(Var { sym }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let rec = self.rec_token().is_some();
|
||||
let mut attrs = downgrade_attrs(self, ctx)?;
|
||||
attrs.rec = rec;
|
||||
Ok(ctx.new_expr(attrs.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let mut items = Vec::with_capacity(self.items().size_hint().0);
|
||||
for item in self.items() {
|
||||
items.push(item.downgrade(ctx)?)
|
||||
}
|
||||
Ok(ctx.new_expr(List { items }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let lhs = self.lhs().unwrap().downgrade(ctx)?;
|
||||
let rhs = self.rhs().unwrap().downgrade(ctx)?;
|
||||
let kind = self.operator().unwrap().into();
|
||||
Ok(ctx.new_expr(BinOp { lhs, rhs, kind }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let lhs = self.expr().unwrap().downgrade(ctx)?;
|
||||
let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
||||
Ok(ctx.new_expr(HasAttr { lhs, rhs }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let rhs = self.expr().unwrap().downgrade(ctx)?;
|
||||
let kind = self.operator().unwrap().into();
|
||||
Ok(ctx.new_expr(UnOp { rhs, kind }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let expr = self.expr().unwrap().downgrade(ctx)?;
|
||||
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
||||
let default = if let Some(default) = self.default_expr() {
|
||||
Some(default.downgrade(ctx)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(ctx.new_expr(
|
||||
Select {
|
||||
expr,
|
||||
attrpath,
|
||||
default,
|
||||
}
|
||||
.to_hir(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Downgrades a `legacy let`, which is essentially a recursive attribute set.
|
||||
/// The body of the `let` is accessed via `let.body`.
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let mut attrs = downgrade_attrs(self, ctx)?;
|
||||
attrs.rec = true;
|
||||
let expr = ctx.new_expr(attrs.to_hir());
|
||||
// The result of a `legacy let` is the `body` attribute of the resulting set.
|
||||
let attrpath = vec![Attr::Str("body".into())];
|
||||
Ok(ctx.new_expr(
|
||||
Select {
|
||||
expr,
|
||||
attrpath,
|
||||
default: None,
|
||||
}
|
||||
.to_hir(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let body = self.body().unwrap().downgrade(ctx)?;
|
||||
let bindings = downgrade_static_attrs(self, ctx)?;
|
||||
Ok(ctx.new_expr(Let { bindings, body }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
||||
let expr = self.body().unwrap().downgrade(ctx)?;
|
||||
Ok(ctx.new_expr(With { namespace, expr }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let param = downgrade_param(self.param().unwrap(), ctx)?;
|
||||
let mut body = self.body().unwrap().downgrade(ctx)?;
|
||||
|
||||
// Desugar pattern matching in function arguments into a `let` expression.
|
||||
// For example, `({ a, b ? 2 }): a + b` is desugared into:
|
||||
// `arg: let a = arg.a; b = arg.b or 2; in a + b`
|
||||
if let Param::Formals { formals, alias, .. } = ¶m {
|
||||
// `Arg` represents the raw argument (the attribute set) passed to the function.
|
||||
let arg = ctx.new_expr(Hir::Arg(Arg));
|
||||
let mut bindings: HashMap<_, _> = formals
|
||||
.iter()
|
||||
.map(|&(ref k, default)| {
|
||||
// For each formal parameter, create a `Select` expression to extract it from the argument set.
|
||||
(
|
||||
k.clone(),
|
||||
ctx.new_expr(
|
||||
Select {
|
||||
expr: arg,
|
||||
attrpath: vec![Attr::Str(k.clone())],
|
||||
default,
|
||||
}
|
||||
.to_hir(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
// If there's an alias (`... }@alias`), bind the alias name to the raw argument set.
|
||||
if let Some(alias) = alias {
|
||||
bindings.insert(
|
||||
alias.clone(),
|
||||
ctx.new_expr(Var { sym: alias.clone() }.to_hir()),
|
||||
);
|
||||
}
|
||||
// Wrap the original function body in the new `let` expression.
|
||||
let let_ = Let { bindings, body };
|
||||
body = ctx.new_expr(let_.to_hir());
|
||||
}
|
||||
let ident;
|
||||
let required;
|
||||
let allowed;
|
||||
match param {
|
||||
Param::Ident(id) => {
|
||||
ident = Some(id);
|
||||
required = None;
|
||||
allowed = None;
|
||||
}
|
||||
Param::Formals {
|
||||
formals,
|
||||
ellipsis,
|
||||
alias,
|
||||
} => {
|
||||
ident = alias;
|
||||
required = Some(
|
||||
formals
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|(_, default)| default.is_none())
|
||||
.map(|(k, _)| k)
|
||||
.collect(),
|
||||
);
|
||||
allowed = if ellipsis {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
formals
|
||||
.into_iter()
|
||||
.filter(|(_, default)| default.is_some())
|
||||
.map(|(k, _)| k)
|
||||
.collect(),
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
let param = ir::Param {
|
||||
ident,
|
||||
required,
|
||||
allowed,
|
||||
};
|
||||
// The function's body and parameters are now stored directly in the `Func` node.
|
||||
Ok(ctx.new_expr(Func { body, param }.to_hir()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Downgrades a function application.
|
||||
/// The `rnix` AST represents chained function calls as nested `Apply` nodes,
|
||||
/// e.g., `f a b` is parsed as `(f a) b`. This implementation unnests these
|
||||
/// calls into a single `Call` HIR node with a list of arguments.
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Apply {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
let mut args = vec![self.argument().unwrap().downgrade(ctx)?];
|
||||
let mut func = self.lambda().unwrap();
|
||||
// Traverse the chain of nested `Apply` nodes to collect all arguments.
|
||||
while let ast::Expr::Apply(call) = func {
|
||||
func = call.lambda().unwrap();
|
||||
args.push(call.argument().unwrap().downgrade(ctx)?);
|
||||
}
|
||||
let func = func.downgrade(ctx)?;
|
||||
// The arguments were collected in reverse order, so fix that.
|
||||
args.reverse();
|
||||
Ok(ctx.new_expr(Call { func, args }.to_hir()))
|
||||
}
|
||||
}
|
||||
202
evaluator/nixjit_hir/src/lib.rs
Normal file
202
evaluator/nixjit_hir/src/lib.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
//! The high-level intermediate representation (HIR) for nixjit.
|
||||
//!
|
||||
//! This module defines the data structures for the HIR, which is a more abstract and
|
||||
//! semantically rich representation of the original Nix code compared to the raw AST from `rnix`.
|
||||
//! It's designed to be easily translatable from the AST and serves as a stepping stone
|
||||
//! towards the lower-level IR (`nixjit_lir`).
|
||||
//!
|
||||
//! The key components are:
|
||||
//! - `Hir`: An enum representing all possible expression types in the HIR.
|
||||
//! - `Downgrade`: A trait for converting `rnix::ast` nodes into HIR expressions.
|
||||
//! - `DowngradeContext`: A trait that provides the necessary context for the conversion,
|
||||
//! such as allocating new expressions and functions.
|
||||
|
||||
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use nixjit_error::{Error, Result};
|
||||
use nixjit_ir::{
|
||||
Assert, Attr, AttrSet, BinOp, Call, ConcatStrings, Const, ExprId, Func, HasAttr, If, List,
|
||||
Path, Select, Str, UnOp, Var, With,
|
||||
};
|
||||
use nixjit_macros::ir;
|
||||
use nixjit_value::format_symbol;
|
||||
|
||||
mod downgrade;
|
||||
mod utils;
|
||||
use utils::*;
|
||||
|
||||
pub use downgrade::Downgrade;
|
||||
|
||||
/// A context for the AST-to-HIR downgrading process.
|
||||
///
|
||||
/// This trait abstracts the storage of HIR expressions and functions, allowing the
|
||||
/// `downgrade` implementations to be generic over the specific context implementation.
|
||||
pub trait DowngradeContext {
|
||||
/// Allocates a new HIR expression in the context and returns its ID.
|
||||
fn new_expr(&mut self, expr: Hir) -> ExprId;
|
||||
|
||||
/// Provides temporary access to an immutable expression for inspection or use.
|
||||
fn with_expr<T>(&self, id: ExprId, f: impl FnOnce(&Hir, &Self) -> T) -> T;
|
||||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
ir! {
|
||||
Hir,
|
||||
|
||||
// Represents an attribute set, e.g., `{ a = 1; b = 2; }`.
|
||||
AttrSet,
|
||||
// Represents a list, e.g., `[1 2 3]`.
|
||||
List,
|
||||
// Represents a "has attribute" check, e.g., `attrs ? a`.
|
||||
HasAttr,
|
||||
// Represents a binary operation, e.g., `a + b`.
|
||||
BinOp,
|
||||
// Represents a unary operation, e.g., `-a`.
|
||||
UnOp,
|
||||
// Represents an attribute selection, e.g., `attrs.a` or `attrs.a or defaultValue`.
|
||||
Select,
|
||||
// Represents an if-then-else expression.
|
||||
If,
|
||||
// Represents a function definition (lambda).
|
||||
Func,
|
||||
// Represents a function call.
|
||||
Call,
|
||||
// Represents a `with` expression, e.g., `with pkgs; stdenv.mkDerivation { ... }`.
|
||||
With,
|
||||
// Represents an `assert` expression.
|
||||
Assert,
|
||||
// Represents the concatenation of strings, often from interpolated strings.
|
||||
ConcatStrings,
|
||||
// Represents a constant value (integer, float, boolean, null).
|
||||
Const,
|
||||
// Represents a simple string literal.
|
||||
Str,
|
||||
// Represents a variable lookup by its symbol/name.
|
||||
Var,
|
||||
// Represents a path expression.
|
||||
Path,
|
||||
// Represents a `let ... in ...` binding.
|
||||
Let { pub bindings: HashMap<String, ExprId>, pub body: ExprId },
|
||||
// Represents a function argument lookup.
|
||||
Arg,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Arg;
|
||||
|
||||
/// A trait defining operations on attribute sets within the HIR.
|
||||
trait Attrs {
|
||||
/// Inserts a value into the attribute set at a given path.
|
||||
///
|
||||
/// # Example
|
||||
/// `insert([a, b], value)` corresponds to `a.b = value;`.
|
||||
fn insert(
|
||||
&mut self,
|
||||
path: Vec<Attr>,
|
||||
value: ExprId,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Internal helper for recursively inserting an attribute.
|
||||
fn _insert(
|
||||
&mut self,
|
||||
path: impl Iterator<Item = Attr>,
|
||||
name: Attr,
|
||||
value: ExprId,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl Attrs for AttrSet {
|
||||
fn _insert(
|
||||
&mut self,
|
||||
mut path: impl Iterator<Item = Attr>,
|
||||
name: Attr,
|
||||
value: ExprId,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
if let Some(attr) = path.next() {
|
||||
// If the path is not yet exhausted, we need to recurse deeper.
|
||||
match attr {
|
||||
Attr::Str(ident) => {
|
||||
// If the next attribute is a static string.
|
||||
if let Some(&id) = self.stcs.get(&ident) {
|
||||
// If a sub-attrset already exists, recurse into it.
|
||||
ctx.with_expr_mut(id, |expr, ctx| {
|
||||
expr.as_mut()
|
||||
.try_unwrap_attr_set()
|
||||
.map_err(|_| {
|
||||
// This path segment exists but is not an attrset.
|
||||
Error::DowngradeError(format!(
|
||||
r#"attribute '{}' already defined"#,
|
||||
format_symbol(&ident)
|
||||
))
|
||||
})
|
||||
.and_then(|attrs| attrs._insert(path, name, value, ctx))
|
||||
})?;
|
||||
} else {
|
||||
// Create a new sub-attrset because this path doesn't exist yet.
|
||||
let mut attrs = AttrSet::default();
|
||||
attrs._insert(path, name, value, ctx)?;
|
||||
let attrs = ctx.new_expr(attrs.to_hir());
|
||||
self.stcs.insert(ident, attrs);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Attr::Dynamic(dynamic) => {
|
||||
// If the next attribute is a dynamic expression.
|
||||
let mut attrs = AttrSet::default();
|
||||
attrs._insert(path, name, value, ctx)?;
|
||||
self.dyns.push((dynamic, ctx.new_expr(attrs.to_hir())));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is the final attribute in the path, so insert the value here.
|
||||
match name {
|
||||
Attr::Str(ident) => {
|
||||
if self.stcs.insert(ident.clone(), value).is_some() {
|
||||
return Err(Error::DowngradeError(format!(
|
||||
r#"attribute '{}' already defined"#,
|
||||
format_symbol(&ident)
|
||||
)));
|
||||
}
|
||||
}
|
||||
Attr::Dynamic(dynamic) => {
|
||||
self.dyns.push((dynamic, value));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(
|
||||
&mut self,
|
||||
path: Vec<Attr>,
|
||||
value: ExprId,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
let mut path = path.into_iter();
|
||||
// The last part of the path is the name of the attribute to be inserted.
|
||||
let name = path.next_back().unwrap();
|
||||
self._insert(path, name, value, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Param {
|
||||
/// A simple parameter, e.g., `x: ...`.
|
||||
Ident(String),
|
||||
/// A pattern-matching parameter (formals), e.g., `{ a, b ? 2, ... }@args: ...`.
|
||||
Formals {
|
||||
/// The individual formal parameters, with optional default values.
|
||||
formals: Vec<(String, Option<ExprId>)>,
|
||||
/// Whether an ellipsis (`...`) is present, allowing extra arguments.
|
||||
ellipsis: bool,
|
||||
/// An optional alias for the entire argument set, e.g., `args @ { ... }`.
|
||||
alias: Option<String>,
|
||||
},
|
||||
}
|
||||
223
evaluator/nixjit_hir/src/utils.rs
Normal file
223
evaluator/nixjit_hir/src/utils.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
//! This module provides utility functions for the AST-to-HIR downgrade process.
|
||||
//! These functions handle common, often complex, patterns in the `rnix` AST,
|
||||
//! such as parsing parameters, attribute sets, and `inherit` statements.
|
||||
//! They are helpers to the main `Downgrade` trait implementations.
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use rnix::ast;
|
||||
|
||||
use nixjit_error::{Error, Result};
|
||||
use nixjit_ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, Var};
|
||||
|
||||
use super::ToHir;
|
||||
use super::downgrade::Downgrade;
|
||||
use super::{Attrs, DowngradeContext, Param};
|
||||
|
||||
/// Downgrades a function parameter from the AST.
|
||||
pub fn downgrade_param(param: ast::Param, ctx: &mut impl DowngradeContext) -> Result<Param> {
|
||||
match param {
|
||||
ast::Param::IdentParam(ident) => Ok(Param::Ident(ident.to_string())),
|
||||
ast::Param::Pattern(pattern) => downgrade_pattern(pattern, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
/// Downgrades a parameter pattern (formals) from the AST.
|
||||
/// This handles `{ a, b ? 2, ... }@args` style parameters.
|
||||
pub fn downgrade_pattern(pattern: ast::Pattern, ctx: &mut impl DowngradeContext) -> Result<Param> {
|
||||
// Extract each formal parameter, downgrading its default value if it exists.
|
||||
let formals = pattern
|
||||
.pat_entries()
|
||||
.map(|entry| {
|
||||
let ident = entry.ident().unwrap().to_string();
|
||||
if entry.default().is_none() {
|
||||
Ok((ident, None))
|
||||
} else {
|
||||
entry
|
||||
.default()
|
||||
.unwrap()
|
||||
.downgrade(ctx)
|
||||
.map(|ok| (ident, Some(ok)))
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let ellipsis = pattern.ellipsis_token().is_some();
|
||||
let alias = pattern
|
||||
.pat_bind()
|
||||
.map(|alias| alias.ident().unwrap().to_string());
|
||||
Ok(Param::Formals {
|
||||
formals,
|
||||
ellipsis,
|
||||
alias,
|
||||
})
|
||||
}
|
||||
|
||||
/// Downgrades the entries of an attribute set.
|
||||
/// This handles `inherit` and `attrpath = value;` entries.
|
||||
pub fn downgrade_attrs(
|
||||
attrs: impl ast::HasEntry,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<AttrSet> {
|
||||
let entries = attrs.entries();
|
||||
let mut attrs = AttrSet {
|
||||
stcs: HashMap::new(),
|
||||
dyns: Vec::new(),
|
||||
rec: false,
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
match entry {
|
||||
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
||||
ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(attrs)
|
||||
}
|
||||
|
||||
/// Downgrades attribute set entries for a `let...in` expression.
|
||||
/// This is a stricter version of `downgrade_attrs` that disallows dynamic attributes,
|
||||
/// as `let` bindings must be statically known.
|
||||
pub fn downgrade_static_attrs(
|
||||
attrs: impl ast::HasEntry,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<HashMap<String, ExprId>> {
|
||||
let entries = attrs.entries();
|
||||
let mut attrs = AttrSet {
|
||||
stcs: HashMap::new(),
|
||||
dyns: Vec::new(),
|
||||
rec: false,
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
match entry {
|
||||
ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?,
|
||||
ast::Entry::AttrpathValue(value) => {
|
||||
downgrade_static_attrpathvalue(value, &mut attrs, ctx)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(attrs.stcs)
|
||||
}
|
||||
|
||||
/// Downgrades an `inherit` statement.
|
||||
/// `inherit (from) a b;` is translated into `a = from.a; b = from.b;`.
|
||||
/// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope).
|
||||
pub fn downgrade_inherit(
|
||||
inherit: ast::Inherit,
|
||||
stcs: &mut HashMap<String, ExprId>,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
// Downgrade the `from` expression if it exists.
|
||||
let from = if let Some(from) = inherit.from() {
|
||||
Some(from.expr().unwrap().downgrade(ctx)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for attr in inherit.attrs() {
|
||||
let ident = match downgrade_attr(attr, ctx)? {
|
||||
Attr::Str(ident) => ident,
|
||||
_ => {
|
||||
// `inherit` does not allow dynamic attributes.
|
||||
return Err(Error::DowngradeError(
|
||||
"dynamic attributes not allowed in inherit".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let expr = from.map_or_else(
|
||||
// If `from` is None, `inherit foo;` becomes `foo = foo;`.
|
||||
|| Var { sym: ident.clone() }.to_hir(),
|
||||
// If `from` is Some, `inherit (from) foo;` becomes `foo = from.foo;`.
|
||||
|expr| {
|
||||
Select {
|
||||
expr,
|
||||
attrpath: vec![Attr::Str(ident.clone())],
|
||||
default: None,
|
||||
}
|
||||
.to_hir()
|
||||
},
|
||||
);
|
||||
if stcs.insert(ident, ctx.new_expr(expr)).is_some() {
|
||||
// TODO: Handle or error on duplicate attribute definitions.
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Downgrades a single attribute key (part of an attribute path).
|
||||
/// An attribute can be a static identifier, an interpolated string, or a dynamic expression.
|
||||
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
|
||||
use ast::Attr::*;
|
||||
use ast::InterpolPart::*;
|
||||
match attr {
|
||||
Ident(ident) => Ok(Attr::Str(ident.to_string())),
|
||||
Str(string) => {
|
||||
let parts = string.normalized_parts();
|
||||
if parts.is_empty() {
|
||||
Ok(Attr::Str("".into()))
|
||||
} else if parts.len() == 1 {
|
||||
// If the string has only one part, it's either a literal or a single interpolation.
|
||||
match parts.into_iter().next().unwrap() {
|
||||
Literal(ident) => Ok(Attr::Str(ident)),
|
||||
Interpolation(interpol) => {
|
||||
Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(ctx)?))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the string has multiple parts, it's an interpolated string that must be concatenated.
|
||||
let parts = parts
|
||||
.into_iter()
|
||||
.map(|part| match part {
|
||||
Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit }.to_hir())),
|
||||
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(Attr::Dynamic(
|
||||
ctx.new_expr(ConcatStrings { parts }.to_hir()),
|
||||
))
|
||||
}
|
||||
}
|
||||
Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(ctx)?)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Downgrades an attribute path (e.g., `a.b."${c}".d`) into a `Vec<Attr>`.
|
||||
pub fn downgrade_attrpath(
|
||||
attrpath: ast::Attrpath,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<Vec<Attr>> {
|
||||
attrpath
|
||||
.attrs()
|
||||
.map(|attr| downgrade_attr(attr, ctx))
|
||||
.collect::<Result<Vec<_>>>()
|
||||
}
|
||||
|
||||
/// Downgrades an `attrpath = value;` expression and inserts it into an `AttrSet`.
|
||||
pub fn downgrade_attrpathvalue(
|
||||
value: ast::AttrpathValue,
|
||||
attrs: &mut AttrSet,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
||||
let value = value.value().unwrap().downgrade(ctx)?;
|
||||
attrs.insert(path, value, ctx)
|
||||
}
|
||||
|
||||
/// A stricter version of `downgrade_attrpathvalue` for `let...in` bindings.
|
||||
/// It ensures that the attribute path contains no dynamic parts.
|
||||
pub fn downgrade_static_attrpathvalue(
|
||||
value: ast::AttrpathValue,
|
||||
attrs: &mut AttrSet,
|
||||
ctx: &mut impl DowngradeContext,
|
||||
) -> Result<()> {
|
||||
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
||||
if path.iter().any(|attr| matches!(attr, Attr::Dynamic(_))) {
|
||||
return Err(Error::DowngradeError(
|
||||
"dynamic attributes not allowed".to_string(),
|
||||
));
|
||||
}
|
||||
let value = value.value().unwrap().downgrade(ctx)?;
|
||||
attrs.insert(path, value, ctx)
|
||||
}
|
||||
13
evaluator/nixjit_ir/Cargo.toml
Normal file
13
evaluator/nixjit_ir/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "nixjit_ir"
|
||||
description = "The core data structures for the nixjit intermediate representation (IR)."
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
derive_more = { version = "2.0", features = ["full"] }
|
||||
hashbrown = "0.15"
|
||||
rnix = "0.12"
|
||||
|
||||
nixjit_error = { path = "../nixjit_error" }
|
||||
nixjit_value = { path = "../nixjit_value" }
|
||||
255
evaluator/nixjit_ir/src/lib.rs
Normal file
255
evaluator/nixjit_ir/src/lib.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
//! This crate defines the core data structures for the nixjit Intermediate Representation (IR).
|
||||
//!
|
||||
//! The IR provides a simplified, language-agnostic representation of Nix expressions,
|
||||
//! serving as a bridge between the high-level representation (HIR) and the low-level
|
||||
//! representation (LIR). It defines the fundamental building blocks like expression IDs,
|
||||
//! function IDs, and structures for various expression types (e.g., binary operations,
|
||||
//! attribute sets, function calls).
|
||||
//!
|
||||
//! These structures are designed to be generic and reusable across different stages of
|
||||
//! the compiler.
|
||||
|
||||
use rnix::ast;
|
||||
|
||||
use derive_more::TryUnwrap;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use nixjit_value::Const as PubConst;
|
||||
|
||||
/// A type-safe wrapper for an index into an expression table.
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ExprId(usize);
|
||||
|
||||
/// A type-safe wrapper for an index into a function table.
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FuncId(usize);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ArgIdx(usize);
|
||||
|
||||
/// Represents a Nix attribute set.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AttrSet {
|
||||
/// Statically known attributes (key is a string).
|
||||
pub stcs: HashMap<String, ExprId>,
|
||||
/// Dynamically computed attributes, where both the key and value are expressions.
|
||||
pub dyns: Vec<(ExprId, ExprId)>,
|
||||
/// `true` if this is a recursive attribute set (`rec { ... }`).
|
||||
pub rec: bool,
|
||||
}
|
||||
|
||||
/// Represents a key in an attribute path.
|
||||
#[derive(Clone, Debug, TryUnwrap)]
|
||||
pub enum Attr {
|
||||
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
||||
Dynamic(ExprId),
|
||||
/// A static attribute key.
|
||||
Str(String),
|
||||
}
|
||||
|
||||
/// Represents a Nix list.
|
||||
#[derive(Debug)]
|
||||
pub struct List {
|
||||
/// The expressions that are elements of the list.
|
||||
pub items: Vec<ExprId>,
|
||||
}
|
||||
|
||||
/// Represents a "has attribute" check (`?` operator).
|
||||
#[derive(Debug)]
|
||||
pub struct HasAttr {
|
||||
/// The expression to check for the attribute (the left-hand side).
|
||||
pub lhs: ExprId,
|
||||
/// The attribute path to look for (the right-hand side).
|
||||
pub rhs: Vec<Attr>,
|
||||
}
|
||||
|
||||
/// Represents a binary operation.
|
||||
#[derive(Debug)]
|
||||
pub struct BinOp {
|
||||
pub lhs: ExprId,
|
||||
pub rhs: ExprId,
|
||||
pub kind: BinOpKind,
|
||||
}
|
||||
|
||||
/// The kinds of binary operations supported in Nix.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BinOpKind {
|
||||
// Arithmetic
|
||||
Add,
|
||||
Sub,
|
||||
Div,
|
||||
Mul,
|
||||
|
||||
// Comparison
|
||||
Eq,
|
||||
Neq,
|
||||
Lt,
|
||||
Gt,
|
||||
Leq,
|
||||
Geq,
|
||||
|
||||
// Logical
|
||||
And,
|
||||
Or,
|
||||
Impl,
|
||||
|
||||
// Set/String/Path operations
|
||||
Con, // List concatenation (`++`)
|
||||
Upd, // AttrSet update (`//`)
|
||||
|
||||
// Not standard, but part of rnix AST
|
||||
PipeL,
|
||||
PipeR,
|
||||
}
|
||||
|
||||
impl From<ast::BinOpKind> for BinOpKind {
|
||||
fn from(op: ast::BinOpKind) -> Self {
|
||||
use BinOpKind::*;
|
||||
use ast::BinOpKind as kind;
|
||||
match op {
|
||||
kind::Concat => Con,
|
||||
kind::Update => Upd,
|
||||
kind::Add => Add,
|
||||
kind::Sub => Sub,
|
||||
kind::Mul => Mul,
|
||||
kind::Div => Div,
|
||||
kind::And => And,
|
||||
kind::Equal => Eq,
|
||||
kind::Implication => Impl,
|
||||
kind::Less => Lt,
|
||||
kind::LessOrEq => Leq,
|
||||
kind::More => Gt,
|
||||
kind::MoreOrEq => Geq,
|
||||
kind::NotEqual => Neq,
|
||||
kind::Or => Or,
|
||||
kind::PipeLeft => PipeL,
|
||||
kind::PipeRight => PipeR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a unary operation.
|
||||
#[derive(Debug)]
|
||||
pub struct UnOp {
|
||||
pub rhs: ExprId,
|
||||
pub kind: UnOpKind,
|
||||
}
|
||||
|
||||
/// The kinds of unary operations.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum UnOpKind {
|
||||
Neg, // Negation (`-`)
|
||||
Not, // Logical not (`!`)
|
||||
}
|
||||
|
||||
impl From<ast::UnaryOpKind> for UnOpKind {
|
||||
fn from(value: ast::UnaryOpKind) -> Self {
|
||||
match value {
|
||||
ast::UnaryOpKind::Invert => UnOpKind::Not,
|
||||
ast::UnaryOpKind::Negate => UnOpKind::Neg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an attribute selection from an attribute set.
|
||||
#[derive(Debug)]
|
||||
pub struct Select {
|
||||
/// The expression that should evaluate to an attribute set.
|
||||
pub expr: ExprId,
|
||||
/// The path of attributes to select.
|
||||
pub attrpath: Vec<Attr>,
|
||||
/// An optional default value to return if the selection fails.
|
||||
pub default: Option<ExprId>,
|
||||
}
|
||||
|
||||
/// Represents an `if-then-else` expression.
|
||||
#[derive(Debug)]
|
||||
pub struct If {
|
||||
pub cond: ExprId,
|
||||
pub consq: ExprId, // Consequence (then branch)
|
||||
pub alter: ExprId, // Alternative (else branch)
|
||||
}
|
||||
|
||||
/// Represents a function value (a lambda).
|
||||
#[derive(Debug)]
|
||||
pub struct Func {
|
||||
/// The body of the function
|
||||
pub body: ExprId,
|
||||
pub param: Param,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Param {
|
||||
pub ident: Option<String>,
|
||||
pub required: Option<Vec<String>>,
|
||||
pub allowed: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Represents a function call.
|
||||
#[derive(Debug)]
|
||||
pub struct Call {
|
||||
/// The expression that evaluates to the function to be called.
|
||||
pub func: ExprId,
|
||||
/// The list of arguments to pass to the function.
|
||||
pub args: Vec<ExprId>,
|
||||
}
|
||||
|
||||
/// Represents a `with` expression.
|
||||
#[derive(Debug)]
|
||||
pub struct With {
|
||||
/// The namespace to bring into scope.
|
||||
pub namespace: ExprId,
|
||||
/// The expression to be evaluated within the new scope.
|
||||
pub expr: ExprId,
|
||||
}
|
||||
|
||||
/// Represents an `assert` expression.
|
||||
#[derive(Debug)]
|
||||
pub struct Assert {
|
||||
/// The condition to assert.
|
||||
pub assertion: ExprId,
|
||||
/// The expression to return if the assertion is true.
|
||||
pub expr: ExprId,
|
||||
}
|
||||
|
||||
/// Represents the concatenation of multiple string expressions.
|
||||
/// This is typically the result of downgrading an interpolated string.
|
||||
#[derive(Debug)]
|
||||
pub struct ConcatStrings {
|
||||
pub parts: Vec<ExprId>,
|
||||
}
|
||||
|
||||
/// Represents a constant value (e.g., integer, float, boolean, null).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Const {
|
||||
pub val: PubConst,
|
||||
}
|
||||
|
||||
impl<T: Into<PubConst>> From<T> for Const {
|
||||
fn from(value: T) -> Self {
|
||||
Self { val: value.into() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a simple, non-interpolated string literal.
|
||||
#[derive(Debug)]
|
||||
pub struct Str {
|
||||
pub val: String,
|
||||
}
|
||||
|
||||
/// Represents a variable lookup by its name.
|
||||
#[derive(Debug)]
|
||||
pub struct Var {
|
||||
pub sym: String,
|
||||
}
|
||||
|
||||
/// Represents a path literal.
|
||||
#[derive(Debug)]
|
||||
pub struct Path {
|
||||
/// The expression that evaluates to the string content of the path.
|
||||
/// This can be a simple `Str` or a `ConcatStrings` for interpolated paths.
|
||||
pub expr: ExprId,
|
||||
}
|
||||
19
evaluator/nixjit_jit/Cargo.toml
Normal file
19
evaluator/nixjit_jit/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "nixjit_jit"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
hashbrown = "0.15"
|
||||
|
||||
cranelift = "0.122"
|
||||
cranelift-module = "0.122"
|
||||
cranelift-jit = "0.122"
|
||||
cranelift-native = "0.122"
|
||||
|
||||
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" }
|
||||
537
evaluator/nixjit_jit/src/compile.rs
Normal file
537
evaluator/nixjit_jit/src/compile.rs
Normal file
@@ -0,0 +1,537 @@
|
||||
use cranelift::codegen::ir::{self, StackSlot};
|
||||
use cranelift::prelude::*;
|
||||
|
||||
use nixjit_eval::{EvalContext, Value};
|
||||
use nixjit_ir::*;
|
||||
use nixjit_lir::Lir;
|
||||
|
||||
use super::{Context, JITContext};
|
||||
|
||||
pub trait JITCompile<Ctx: JITContext> {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot;
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for ExprId {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Lir {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for AttrSet {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
let attrs = ctx.create_attrs();
|
||||
for (k, v) in self.stcs.iter() {
|
||||
let v = v.compile(ctx, engine, env);
|
||||
ctx.push_attr(attrs, k, v);
|
||||
}
|
||||
ctx.finalize_attrs(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for List {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
let array = ctx.alloc_array(self.items.len());
|
||||
for (i, item) in self.items.iter().enumerate() {
|
||||
let item = item.compile(ctx, engine, env);
|
||||
let tag = ctx.builder.ins().stack_load(types::I64, item, 0);
|
||||
let val0 = ctx.builder.ins().stack_load(types::I64, item, 8);
|
||||
let val1 = ctx.builder.ins().stack_load(types::I64, item, 16);
|
||||
let val2 = ctx.builder.ins().stack_load(types::I64, item, 24);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), tag, array, i as i32 * 32);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val0, array, i as i32 * 32 + 8);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val1, array, i as i32 * 32 + 16);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val2, array, i as i32 * 32 + 24);
|
||||
}
|
||||
ctx.create_list(array, self.items.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for HasAttr {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for BinOp {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
use BinOpKind::*;
|
||||
let lhs = self.lhs.compile(ctx, engine, env);
|
||||
let rhs = self.rhs.compile(ctx, engine, env);
|
||||
let lhs_tag = ctx.get_tag(lhs);
|
||||
let rhs_tag = ctx.get_tag(rhs);
|
||||
let eq = ctx.builder.ins().icmp(IntCC::Equal, lhs_tag, rhs_tag);
|
||||
|
||||
let eq_block = ctx.builder.create_block();
|
||||
let neq_block = ctx.builder.create_block();
|
||||
let exit_block = ctx.builder.create_block();
|
||||
ctx.builder.ins().brif(eq, eq_block, [], neq_block, []);
|
||||
|
||||
match self.kind {
|
||||
Add => {
|
||||
ctx.builder.switch_to_block(eq_block);
|
||||
let default_block = ctx.builder.create_block();
|
||||
let int_block = ctx.builder.create_block();
|
||||
let float_block = ctx.builder.create_block();
|
||||
let float_check_block = ctx.builder.create_block();
|
||||
|
||||
let is_int =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, lhs_tag, Value::<Ctx>::INT as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_int, int_block, [], float_check_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(int_block);
|
||||
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||
let result = ctx.builder.ins().iadd(lhs_value, rhs_value);
|
||||
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||
ctx.builder.ins().jump(exit_block, &[]);
|
||||
|
||||
// FIXME: Non-float
|
||||
ctx.builder.switch_to_block(float_check_block);
|
||||
let is_float =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, lhs_tag, Value::<Ctx>::FLOAT as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_float, float_block, [], default_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(float_block);
|
||||
let lhs_value = ctx.get_small_value(types::F64, lhs);
|
||||
let rhs_value = ctx.get_small_value(types::F64, rhs);
|
||||
let result = ctx.builder.ins().fadd(lhs_value, rhs_value);
|
||||
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||
ctx.builder.ins().jump(exit_block, &[]);
|
||||
|
||||
// FIXME: finish this
|
||||
ctx.builder.switch_to_block(default_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.switch_to_block(neq_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.seal_block(default_block);
|
||||
ctx.builder.seal_block(int_block);
|
||||
ctx.builder.seal_block(float_check_block);
|
||||
ctx.builder.seal_block(float_block);
|
||||
}
|
||||
Sub => {
|
||||
ctx.builder.switch_to_block(eq_block);
|
||||
let default_block = ctx.builder.create_block();
|
||||
let int_block = ctx.builder.create_block();
|
||||
let float_block = ctx.builder.create_block();
|
||||
let float_check_block = ctx.builder.create_block();
|
||||
|
||||
let is_int =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, lhs_tag, Value::<Ctx>::INT as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_int, int_block, [], float_check_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(int_block);
|
||||
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||
let result = ctx.builder.ins().isub(lhs_value, rhs_value);
|
||||
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||
ctx.builder.ins().jump(exit_block, &[]);
|
||||
|
||||
// FIXME: Non-float
|
||||
ctx.builder.switch_to_block(float_check_block);
|
||||
let is_float =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, lhs_tag, Value::<Ctx>::FLOAT as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_float, float_block, [], default_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(float_block);
|
||||
let lhs_value = ctx.get_small_value(types::F64, lhs);
|
||||
let rhs_value = ctx.get_small_value(types::F64, rhs);
|
||||
let result = ctx.builder.ins().fsub(lhs_value, rhs_value);
|
||||
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||
ctx.builder.ins().jump(exit_block, &[]);
|
||||
|
||||
// FIXME: finish this
|
||||
ctx.builder.switch_to_block(default_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.switch_to_block(neq_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.seal_block(default_block);
|
||||
ctx.builder.seal_block(int_block);
|
||||
ctx.builder.seal_block(float_check_block);
|
||||
ctx.builder.seal_block(float_block);
|
||||
}
|
||||
Div => {
|
||||
ctx.builder.switch_to_block(eq_block);
|
||||
let default_block = ctx.builder.create_block();
|
||||
let int_block = ctx.builder.create_block();
|
||||
let float_block = ctx.builder.create_block();
|
||||
let float_check_block = ctx.builder.create_block();
|
||||
|
||||
let is_int =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, lhs_tag, Value::<Ctx>::INT as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_int, int_block, [], float_check_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(int_block);
|
||||
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||
let result = ctx.builder.ins().sdiv(lhs_value, rhs_value);
|
||||
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||
ctx.builder.ins().jump(exit_block, &[]);
|
||||
|
||||
// FIXME: Non-float
|
||||
ctx.builder.switch_to_block(float_check_block);
|
||||
let is_float =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, lhs_tag, Value::<Ctx>::FLOAT as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_float, float_block, [], default_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(float_block);
|
||||
let lhs_value = ctx.get_small_value(types::F64, lhs);
|
||||
let rhs_value = ctx.get_small_value(types::F64, rhs);
|
||||
let result = ctx.builder.ins().fdiv(lhs_value, rhs_value);
|
||||
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||
ctx.builder.ins().jump(exit_block, &[]);
|
||||
|
||||
// FIXME: finish this
|
||||
ctx.builder.switch_to_block(default_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.switch_to_block(neq_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.seal_block(default_block);
|
||||
ctx.builder.seal_block(int_block);
|
||||
ctx.builder.seal_block(float_check_block);
|
||||
ctx.builder.seal_block(float_block);
|
||||
}
|
||||
And => {
|
||||
ctx.builder.switch_to_block(eq_block);
|
||||
let bool_block = ctx.builder.create_block();
|
||||
let non_bool_block = ctx.builder.create_block();
|
||||
|
||||
let is_bool =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, lhs_tag, Value::<Ctx>::BOOL as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_bool, bool_block, [], non_bool_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(bool_block);
|
||||
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||
let result = ctx.builder.ins().band(lhs_value, rhs_value);
|
||||
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||
ctx.builder.ins().jump(exit_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(non_bool_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.switch_to_block(neq_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.seal_block(bool_block);
|
||||
ctx.builder.seal_block(non_bool_block);
|
||||
}
|
||||
Or => {
|
||||
ctx.builder.switch_to_block(eq_block);
|
||||
let bool_block = ctx.builder.create_block();
|
||||
let non_bool_block = ctx.builder.create_block();
|
||||
|
||||
let is_bool =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, lhs_tag, Value::<Ctx>::BOOL as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_bool, bool_block, [], non_bool_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(bool_block);
|
||||
let lhs_value = ctx.get_small_value(types::I64, lhs);
|
||||
let rhs_value = ctx.get_small_value(types::I64, rhs);
|
||||
let result = ctx.builder.ins().bor(lhs_value, rhs_value);
|
||||
ctx.builder.ins().stack_store(lhs_tag, lhs, 0);
|
||||
ctx.builder.ins().stack_store(result, lhs, 8);
|
||||
ctx.builder.ins().jump(exit_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(non_bool_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.switch_to_block(neq_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.seal_block(bool_block);
|
||||
ctx.builder.seal_block(non_bool_block);
|
||||
}
|
||||
Eq => {
|
||||
ctx.builder.switch_to_block(eq_block);
|
||||
ctx.eq(lhs, rhs);
|
||||
ctx.builder.ins().jump(exit_block, []);
|
||||
ctx.builder.switch_to_block(neq_block);
|
||||
ctx.eq(lhs, rhs);
|
||||
ctx.builder.ins().jump(exit_block, []);
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
|
||||
ctx.builder.seal_block(exit_block);
|
||||
ctx.builder.seal_block(eq_block);
|
||||
ctx.builder.seal_block(neq_block);
|
||||
ctx.builder.switch_to_block(exit_block);
|
||||
ctx.free_slot(rhs);
|
||||
|
||||
lhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for UnOp {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Attr {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
use Attr::*;
|
||||
match self {
|
||||
Str(string) => ctx.create_string(string),
|
||||
Dynamic(ir) => ir.compile(ctx, engine, env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Select {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
let val = self.expr.compile(ctx, engine, env);
|
||||
let attrpath = ctx.alloc_array(self.attrpath.len());
|
||||
for (i, attr) in self.attrpath.iter().enumerate() {
|
||||
let arg = attr.compile(ctx, engine, env);
|
||||
let tag = ctx.builder.ins().stack_load(types::I64, arg, 0);
|
||||
let val0 = ctx.builder.ins().stack_load(types::I64, arg, 8);
|
||||
let val1 = ctx.builder.ins().stack_load(types::I64, arg, 16);
|
||||
let val2 = ctx.builder.ins().stack_load(types::I64, arg, 24);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), tag, attrpath, i as i32 * 32);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val0, attrpath, i as i32 * 32 + 8);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val1, attrpath, i as i32 * 32 + 16);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val2, attrpath, i as i32 * 32 + 24);
|
||||
}
|
||||
ctx.select(val, attrpath, self.attrpath.len(), engine, env);
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for If {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
let cond = self.cond.compile(ctx, engine, env);
|
||||
let cond_type = ctx.builder.ins().stack_load(types::I64, cond, 0);
|
||||
let cond_value = ctx.builder.ins().stack_load(types::I64, cond, 8);
|
||||
|
||||
let true_block = ctx.builder.create_block();
|
||||
let false_block = ctx.builder.create_block();
|
||||
let exit_block = ctx.builder.create_block();
|
||||
let error_block = ctx.builder.create_block();
|
||||
let judge_block = ctx.builder.create_block();
|
||||
let slot = ctx.alloca();
|
||||
|
||||
let is_bool =
|
||||
ctx.builder
|
||||
.ins()
|
||||
.icmp_imm(IntCC::Equal, cond_type, Value::<Ctx>::BOOL as i64);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(is_bool, judge_block, [], error_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(judge_block);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.brif(cond_value, true_block, [], false_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(true_block);
|
||||
let ret = self.consq.compile(ctx, engine, env);
|
||||
let tag = ctx.builder.ins().stack_load(types::I64, ret, 0);
|
||||
let val0 = ctx.builder.ins().stack_load(types::I64, ret, 8);
|
||||
let val1 = ctx.builder.ins().stack_load(types::I64, ret, 16);
|
||||
let val2 = ctx.builder.ins().stack_load(types::I64, ret, 24);
|
||||
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||
ctx.builder.ins().stack_store(val0, slot, 8);
|
||||
ctx.builder.ins().stack_store(val1, slot, 16);
|
||||
ctx.builder.ins().stack_store(val2, slot, 24);
|
||||
ctx.builder.ins().jump(exit_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(false_block);
|
||||
let ret = self.alter.compile(ctx, engine, env);
|
||||
let tag = ctx.builder.ins().stack_load(types::I64, ret, 0);
|
||||
let val0 = ctx.builder.ins().stack_load(types::I64, ret, 8);
|
||||
let val1 = ctx.builder.ins().stack_load(types::I64, ret, 16);
|
||||
let val2 = ctx.builder.ins().stack_load(types::I64, ret, 24);
|
||||
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||
ctx.builder.ins().stack_store(val0, slot, 8);
|
||||
ctx.builder.ins().stack_store(val1, slot, 16);
|
||||
ctx.builder.ins().stack_store(val2, slot, 24);
|
||||
ctx.builder.ins().jump(exit_block, []);
|
||||
|
||||
ctx.builder.switch_to_block(error_block);
|
||||
ctx.builder.ins().trap(TrapCode::unwrap_user(1));
|
||||
|
||||
ctx.builder.switch_to_block(exit_block);
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Call {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
let func = self.func.compile(ctx, engine, env);
|
||||
let args = ctx.alloc_array(self.args.len());
|
||||
for (i, arg) in self.args.iter().enumerate() {
|
||||
let arg = arg.compile(ctx, engine, env);
|
||||
let tag = ctx.builder.ins().stack_load(types::I64, arg, 0);
|
||||
let val0 = ctx.builder.ins().stack_load(types::I64, arg, 8);
|
||||
let val1 = ctx.builder.ins().stack_load(types::I64, arg, 16);
|
||||
let val2 = ctx.builder.ins().stack_load(types::I64, arg, 24);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), tag, args, i as i32 * 32);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val0, args, i as i32 * 32 + 8);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val1, args, i as i32 * 32 + 16);
|
||||
ctx.builder
|
||||
.ins()
|
||||
.store(MemFlags::new(), val2, args, i as i32 * 32 + 24);
|
||||
}
|
||||
ctx.call(func, args, self.args.len(), engine, env);
|
||||
func
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for With {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
let namespace = self.namespace.compile(ctx, engine, env);
|
||||
ctx.enter_with(env, namespace);
|
||||
let ret = self.expr.compile(ctx, engine, env);
|
||||
ctx.exit_with(env);
|
||||
ctx.free_slot(namespace);
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Assert {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for ConcatStrings {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Const {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
use nixjit_value::Const::*;
|
||||
let slot = ctx.alloca();
|
||||
match self.val {
|
||||
Bool(x) => {
|
||||
let tag = ctx
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(types::I64, Value::<Ctx>::BOOL as i64);
|
||||
let val = ctx.builder.ins().iconst(types::I64, x as i64);
|
||||
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||
ctx.builder.ins().stack_store(val, slot, 8);
|
||||
}
|
||||
Int(x) => {
|
||||
let tag = ctx
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(types::I64, Value::<Ctx>::INT as i64);
|
||||
let val = ctx.builder.ins().iconst(types::I64, x);
|
||||
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||
ctx.builder.ins().stack_store(val, slot, 8);
|
||||
}
|
||||
Float(x) => {
|
||||
let tag = ctx
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(types::I64, Value::<Ctx>::FLOAT as i64);
|
||||
let val = ctx.builder.ins().f64const(x);
|
||||
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||
ctx.builder.ins().stack_store(val, slot, 8);
|
||||
}
|
||||
Null => {
|
||||
let tag = ctx
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(types::I64, Value::<Ctx>::NULL as i64);
|
||||
ctx.builder.ins().stack_store(tag, slot, 0);
|
||||
}
|
||||
}
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Str {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
ctx.create_string(&self.val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Var {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
ctx.lookup(env, &self.sym)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompile<Ctx> for Path {
|
||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
165
evaluator/nixjit_jit/src/helpers.rs
Normal file
165
evaluator/nixjit_jit/src/helpers.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
use core::{slice, str};
|
||||
use std::alloc::Layout;
|
||||
use std::alloc::alloc;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use nixjit_eval::{AttrSet, EvalContext, List, Value};
|
||||
|
||||
use super::JITContext;
|
||||
|
||||
pub extern "C" fn helper_call<Ctx: JITContext>(
|
||||
func: &mut Value<Ctx>,
|
||||
args_ptr: *mut Value<Ctx>,
|
||||
args_len: usize,
|
||||
ctx: &mut Ctx,
|
||||
) {
|
||||
// TODO: Error Handling
|
||||
let args = core::ptr::slice_from_raw_parts_mut(args_ptr, args_len);
|
||||
let args = unsafe { Box::from_raw(args) };
|
||||
func.call(args.into_iter().collect(), ctx).unwrap();
|
||||
}
|
||||
|
||||
pub extern "C" fn helper_lookup_stack<Ctx: JITContext>(
|
||||
ctx: &Ctx,
|
||||
offset: usize,
|
||||
ret: &mut MaybeUninit<Value<Ctx>>,
|
||||
) {
|
||||
ret.write(ctx.lookup_stack(offset).clone());
|
||||
}
|
||||
|
||||
pub extern "C" fn helper_lookup_arg<Ctx: JITContext>(
|
||||
ctx: &Ctx,
|
||||
offset: usize,
|
||||
ret: &mut MaybeUninit<Value<Ctx>>,
|
||||
) {
|
||||
ret.write(ctx.lookup_arg(offset).clone());
|
||||
}
|
||||
|
||||
pub extern "C" fn helper_lookup<Ctx: JITContext>(
|
||||
ctx: &Ctx,
|
||||
sym_ptr: *const u8,
|
||||
sym_len: usize,
|
||||
ret: &mut MaybeUninit<Value<Ctx>>,
|
||||
) {
|
||||
// TODO: Error Handling
|
||||
unsafe {
|
||||
ret.write(
|
||||
ctx.lookup_with(str::from_utf8_unchecked(slice::from_raw_parts(
|
||||
sym_ptr, sym_len,
|
||||
)))
|
||||
.unwrap()
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub extern "C" fn helper_select<Ctx: JITContext>(
|
||||
val: &mut Value<Ctx>,
|
||||
path_ptr: *mut Value<Ctx>,
|
||||
path_len: usize,
|
||||
) {
|
||||
let path = core::ptr::slice_from_raw_parts_mut(path_ptr, path_len);
|
||||
let path = unsafe { Box::from_raw(path) };
|
||||
val.select(path.into_iter().map(|mut val| {
|
||||
val.coerce_to_string();
|
||||
Ok(val.unwrap_string())
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub extern "C" fn helper_select_with_default<Ctx: JITContext>(
|
||||
val: &mut Value<Ctx>,
|
||||
path_ptr: *mut Value<Ctx>,
|
||||
path_len: usize,
|
||||
default: NonNull<Value<Ctx>>,
|
||||
) {
|
||||
let path = core::ptr::slice_from_raw_parts_mut(path_ptr, path_len);
|
||||
let path = unsafe { Box::from_raw(path) };
|
||||
val.select_with_default(
|
||||
path.into_iter().map(|mut val| {
|
||||
val.coerce_to_string();
|
||||
Ok(val.unwrap_string())
|
||||
}),
|
||||
unsafe { default.read() },
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub extern "C" fn helper_eq<Ctx: JITContext>(lhs: &mut Value<Ctx>, rhs: &Value<Ctx>) {
|
||||
lhs.eq(rhs);
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn helper_create_string<Ctx: JITContext>(
|
||||
ptr: *const u8,
|
||||
len: usize,
|
||||
ret: &mut MaybeUninit<Value<Ctx>>,
|
||||
) {
|
||||
unsafe {
|
||||
ret.write(Value::String(
|
||||
str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)).to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn helper_create_list<Ctx: JITContext>(
|
||||
ptr: *mut Value<Ctx>,
|
||||
len: usize,
|
||||
ret: &mut MaybeUninit<Value<Ctx>>,
|
||||
) {
|
||||
unsafe {
|
||||
ret.write(Value::List(
|
||||
List::from(Vec::from_raw_parts(ptr, len, len)).into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn helper_create_attrs<Ctx: JITContext>(
|
||||
ret: &mut MaybeUninit<HashMap<String, Value<Ctx>>>,
|
||||
) {
|
||||
ret.write(HashMap::new());
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn helper_push_attr<Ctx: JITContext>(
|
||||
attrs: &mut HashMap<String, Value<Ctx>>,
|
||||
sym_ptr: *const u8,
|
||||
sym_len: usize,
|
||||
val: NonNull<Value<Ctx>>,
|
||||
) {
|
||||
unsafe {
|
||||
attrs.insert(
|
||||
str::from_utf8_unchecked(slice::from_raw_parts(sym_ptr, sym_len)).to_owned(),
|
||||
val.read(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn helper_finalize_attrs<Ctx: JITContext>(
|
||||
attrs: NonNull<HashMap<String, Value<Ctx>>>,
|
||||
ret: &mut MaybeUninit<Value<Ctx>>,
|
||||
) {
|
||||
ret.write(Value::AttrSet(
|
||||
AttrSet::from(unsafe { attrs.read() }).into(),
|
||||
));
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn helper_enter_with<Ctx: JITContext>(
|
||||
ctx: &mut Ctx,
|
||||
namespace: NonNull<Value<Ctx>>,
|
||||
) {
|
||||
ctx.enter_with(unsafe { namespace.read() }.unwrap_attr_set().into_inner());
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn helper_exit_with<Ctx: JITContext>(ctx: &mut Ctx) {
|
||||
ctx.exit_with();
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn helper_alloc_array<Ctx: JITContext>(len: usize) -> *mut u8 {
|
||||
unsafe { alloc(Layout::array::<Value<Ctx>>(len).unwrap()) }
|
||||
}
|
||||
|
||||
pub extern "C" fn helper_dbg<Ctx: JITContext>(value: &Value<Ctx>) {
|
||||
println!("{value:?}")
|
||||
}
|
||||
762
evaluator/nixjit_jit/src/lib.rs
Normal file
762
evaluator/nixjit_jit/src/lib.rs
Normal file
@@ -0,0 +1,762 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use cranelift::codegen::ir::Function;
|
||||
use cranelift::codegen::ir::{self, ArgumentExtension, ArgumentPurpose, StackSlot};
|
||||
use cranelift::prelude::*;
|
||||
use cranelift_jit::{JITBuilder, JITModule};
|
||||
use cranelift_module::{FuncId, Linkage, Module};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
|
||||
use nixjit_eval::{EvalContext, Value};
|
||||
use nixjit_lir::Lir;
|
||||
|
||||
mod compile;
|
||||
mod helpers;
|
||||
|
||||
pub use compile::JITCompile;
|
||||
use helpers::*;
|
||||
|
||||
pub trait JITContext: EvalContext + Sized {
|
||||
fn lookup_stack(&self, offset: usize) -> &Value<Self>;
|
||||
fn lookup_arg(&self, offset: usize) -> &Value<Self>;
|
||||
fn enter_with(&mut self, namespace: Rc<HashMap<String, Value<Self>>>);
|
||||
fn exit_with(&mut self);
|
||||
}
|
||||
|
||||
type F<Ctx: JITContext> = unsafe extern "C" fn(*const Ctx, *mut Value<Ctx>);
|
||||
|
||||
pub struct JITFunc<Ctx: JITContext> {
|
||||
func: F<Ctx>,
|
||||
strings: HashSet<String>,
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> Deref for JITFunc<Ctx> {
|
||||
type Target = F<Ctx>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.func
|
||||
}
|
||||
}
|
||||
|
||||
struct Context<'comp, 'ctx, Ctx: JITContext> {
|
||||
pub compiler: &'comp mut JITCompiler<Ctx>,
|
||||
pub builder: FunctionBuilder<'ctx>,
|
||||
free_slots: Vec<StackSlot>,
|
||||
strings: HashSet<String>,
|
||||
}
|
||||
|
||||
impl<'comp, 'ctx, Ctx: JITContext> Context<'comp, 'ctx, Ctx> {
|
||||
fn new(compiler: &'comp mut JITCompiler<Ctx>, builder: FunctionBuilder<'ctx>) -> Self {
|
||||
Self {
|
||||
compiler,
|
||||
builder,
|
||||
free_slots: Vec::new(),
|
||||
strings: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn alloca(&mut self) -> StackSlot {
|
||||
self.free_slots.pop().map_or_else(
|
||||
|| {
|
||||
let slot = StackSlotData::new(StackSlotKind::ExplicitSlot, 32, 3);
|
||||
self.builder.create_sized_stack_slot(slot)
|
||||
},
|
||||
|x| x,
|
||||
)
|
||||
}
|
||||
|
||||
fn free_slot(&mut self, slot: StackSlot) {
|
||||
self.free_slots.push(slot);
|
||||
}
|
||||
|
||||
fn alloc_array(&mut self, len: usize) -> ir::Value {
|
||||
let len = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, len as i64);
|
||||
let alloc_array = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.alloc_array, self.builder.func);
|
||||
let inst = self.builder.ins().call(alloc_array, &[len]);
|
||||
self.builder.inst_results(inst)[0]
|
||||
}
|
||||
|
||||
fn create_string(&mut self, string: &str) -> StackSlot {
|
||||
let string = self
|
||||
.strings
|
||||
.get_or_insert_with(string, |_| string.to_owned());
|
||||
let ptr = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, string.as_ptr() as i64);
|
||||
let len = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, string.len() as i64);
|
||||
let create_string = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.create_string, self.builder.func);
|
||||
let slot = self.alloca();
|
||||
let ret = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||
self.builder.ins().call(create_string, &[ptr, len, ret]);
|
||||
slot
|
||||
}
|
||||
|
||||
fn create_list(&mut self, ptr: ir::Value, len: usize) -> StackSlot {
|
||||
let len = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, len as i64);
|
||||
let create_list = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.create_list, self.builder.func);
|
||||
let slot = self.alloca();
|
||||
let ret = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||
self.builder.ins().call(create_list, &[ptr, len, ret]);
|
||||
slot
|
||||
}
|
||||
|
||||
fn create_attrs(&mut self) -> StackSlot {
|
||||
let create_attrs = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.create_attrs, self.builder.func);
|
||||
let slot = StackSlotData::new(StackSlotKind::ExplicitSlot, 40, 3);
|
||||
let slot = self.builder.create_sized_stack_slot(slot);
|
||||
let ret = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||
self.builder.ins().call(create_attrs, &[ret]);
|
||||
slot
|
||||
}
|
||||
|
||||
fn push_attr(&mut self, attrs: StackSlot, sym: &str, val: StackSlot) {
|
||||
self.free_slot(attrs);
|
||||
self.free_slot(val);
|
||||
let attrs = self.builder.ins().stack_addr(types::I64, attrs, 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 ptr = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, sym.as_ptr() as i64);
|
||||
let len = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, sym.len() as i64);
|
||||
let push_attr = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.push_attr, self.builder.func);
|
||||
self.builder.ins().call(push_attr, &[attrs, ptr, len, val]);
|
||||
}
|
||||
|
||||
fn finalize_attrs(&mut self, attrs: StackSlot) -> StackSlot {
|
||||
let attrs = self.builder.ins().stack_addr(types::I64, attrs, 0);
|
||||
let finalize_attrs = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.finalize_attrs, self.builder.func);
|
||||
let slot = self.alloca();
|
||||
let ret = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||
self.builder.ins().call(finalize_attrs, &[attrs, ret]);
|
||||
slot
|
||||
}
|
||||
|
||||
fn enter_with(&mut self, env: ir::Value, namespace: StackSlot) {
|
||||
let ptr = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, namespace, 0);
|
||||
let enter_with = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.enter_with, self.builder.func);
|
||||
self.builder.ins().call(enter_with, &[env, ptr]);
|
||||
}
|
||||
|
||||
fn exit_with(&mut self, env: ir::Value) {
|
||||
let exit_with = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.exit_with, self.builder.func);
|
||||
self.builder.ins().call(exit_with, &[env]);
|
||||
}
|
||||
|
||||
fn dbg(&mut self, slot: StackSlot) {
|
||||
let ptr = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||
let dbg = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.dbg, self.builder.func);
|
||||
self.builder.ins().call(dbg, &[ptr]);
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
func: StackSlot,
|
||||
args_ptr: ir::Value,
|
||||
args_len: usize,
|
||||
engine: ir::Value,
|
||||
env: ir::Value,
|
||||
) {
|
||||
let args_len = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, args_len as i64);
|
||||
let call = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.call, self.builder.func);
|
||||
let func = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, func, 0);
|
||||
self.builder
|
||||
.ins()
|
||||
.call(call, &[func, args_ptr, args_len, engine, env]);
|
||||
}
|
||||
|
||||
fn lookup(&mut self, env: ir::Value, sym: &str) -> StackSlot {
|
||||
let sym = self.strings.get_or_insert_with(sym, |_| sym.to_owned());
|
||||
let ptr = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, sym.as_ptr() as i64);
|
||||
let len = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, sym.len() as i64);
|
||||
let lookup = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.lookup, self.builder.func);
|
||||
let slot = self.alloca();
|
||||
let ret = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||
self.builder.ins().call(lookup, &[env, ptr, len, ret]);
|
||||
slot
|
||||
}
|
||||
|
||||
fn lookup_stack(&mut self, env: 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, &[env, idx, ptr]);
|
||||
slot
|
||||
}
|
||||
|
||||
fn lookup_arg(&mut self, env: ir::Value, idx: usize) -> StackSlot {
|
||||
let slot = self.alloca();
|
||||
let lookup_arg = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.lookup_arg, 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_arg, &[env, idx, ptr]);
|
||||
slot
|
||||
}
|
||||
|
||||
fn select(
|
||||
&mut self,
|
||||
slot: StackSlot,
|
||||
path_ptr: ir::Value,
|
||||
path_len: usize,
|
||||
engine: ir::Value,
|
||||
env: ir::Value,
|
||||
) {
|
||||
let select = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.select, self.builder.func);
|
||||
let path_len = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, path_len as i64);
|
||||
let ptr = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||
self.builder
|
||||
.ins()
|
||||
.call(select, &[ptr, path_ptr, path_len, engine, env]);
|
||||
}
|
||||
|
||||
fn select_with_default(
|
||||
&mut self,
|
||||
slot: StackSlot,
|
||||
path_ptr: ir::Value,
|
||||
path_len: usize,
|
||||
default: StackSlot,
|
||||
engine: ir::Value,
|
||||
env: ir::Value,
|
||||
) {
|
||||
let select_with_default = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.select_with_default, self.builder.func);
|
||||
let path_len = self
|
||||
.builder
|
||||
.ins()
|
||||
.iconst(self.compiler.ptr_type, path_len as i64);
|
||||
let ptr = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, slot, 0);
|
||||
let default_ptr = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, default, 0);
|
||||
self.builder.ins().call(
|
||||
select_with_default,
|
||||
&[ptr, path_ptr, path_len, default_ptr, engine, env],
|
||||
);
|
||||
}
|
||||
|
||||
pub fn eq(&mut self, lhs: StackSlot, rhs: StackSlot) {
|
||||
let lhs = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, lhs, 0);
|
||||
let rhs = self
|
||||
.builder
|
||||
.ins()
|
||||
.stack_addr(self.compiler.ptr_type, rhs, 0);
|
||||
let eq = self
|
||||
.compiler
|
||||
.module
|
||||
.declare_func_in_func(self.compiler.eq, self.builder.func);
|
||||
self.builder.ins().call(eq, &[lhs, rhs]);
|
||||
}
|
||||
|
||||
pub fn get_tag(&mut self, slot: StackSlot) -> ir::Value {
|
||||
self.builder.ins().stack_load(types::I64, slot, 0)
|
||||
}
|
||||
|
||||
pub fn get_small_value(&mut self, ty: Type, slot: StackSlot) -> ir::Value {
|
||||
self.builder.ins().stack_load(ty, slot, 8)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JITCompiler<Ctx: JITContext> {
|
||||
ctx: codegen::Context,
|
||||
module: JITModule,
|
||||
builder_ctx: Option<FunctionBuilderContext>,
|
||||
_marker: PhantomData<Ctx>,
|
||||
|
||||
int_type: Type,
|
||||
float_type: Type,
|
||||
bool_type: Type,
|
||||
ptr_type: Type,
|
||||
value_type: Type,
|
||||
func_sig: Signature,
|
||||
|
||||
call: FuncId,
|
||||
lookup_stack: FuncId,
|
||||
lookup_arg: FuncId,
|
||||
lookup: FuncId,
|
||||
select: FuncId,
|
||||
select_with_default: FuncId,
|
||||
|
||||
eq: FuncId,
|
||||
|
||||
alloc_array: FuncId,
|
||||
create_string: FuncId,
|
||||
create_list: FuncId,
|
||||
create_attrs: FuncId,
|
||||
push_attr: FuncId,
|
||||
finalize_attrs: FuncId,
|
||||
enter_with: FuncId,
|
||||
exit_with: FuncId,
|
||||
dbg: FuncId,
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> Default for JITCompiler<Ctx> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: JITContext> JITCompiler<Ctx> {
|
||||
pub fn new() -> Self {
|
||||
let mut flag_builder = settings::builder();
|
||||
flag_builder.set("use_colocated_libcalls", "false").unwrap();
|
||||
flag_builder.set("is_pic", "false").unwrap();
|
||||
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
|
||||
panic!("host machine is not supported: {msg}");
|
||||
});
|
||||
let isa = isa_builder
|
||||
.finish(settings::Flags::new(flag_builder))
|
||||
.unwrap();
|
||||
let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
|
||||
|
||||
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", helper_lookup::<Ctx> as _);
|
||||
builder.symbol("helper_select", helper_select::<Ctx> as _);
|
||||
builder.symbol(
|
||||
"helper_select_with_default",
|
||||
helper_select_with_default::<Ctx> as _,
|
||||
);
|
||||
builder.symbol("helper_eq", helper_eq::<Ctx> as _);
|
||||
|
||||
builder.symbol("helper_alloc_array", helper_alloc_array::<Ctx> as _);
|
||||
builder.symbol("helper_create_string", helper_create_string::<Ctx> as _);
|
||||
builder.symbol("helper_create_list", helper_create_list::<Ctx> as _);
|
||||
builder.symbol("helper_create_attrs", helper_create_attrs::<Ctx> as _);
|
||||
builder.symbol("helper_push_attr", helper_push_attr::<Ctx> as _);
|
||||
builder.symbol("helper_finalize_attrs", helper_finalize_attrs::<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_dbg", helper_dbg::<Ctx> as _);
|
||||
|
||||
let mut module = JITModule::new(builder);
|
||||
let ctx = module.make_context();
|
||||
|
||||
let int_type = types::I64;
|
||||
let float_type = types::F64;
|
||||
let bool_type = types::I8;
|
||||
let ptr_type = module.target_config().pointer_type();
|
||||
let value_type = types::I128;
|
||||
|
||||
// fn(*const Context, *const Env, *mut Value)
|
||||
let mut func_sig = module.make_signature();
|
||||
func_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 3],
|
||||
);
|
||||
|
||||
// fn(func: &mut Value, args_ptr: *mut Value, args_len: usize, engine: &mut Context, env:
|
||||
// &mut Env)
|
||||
let mut call_sig = module.make_signature();
|
||||
call_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 5],
|
||||
);
|
||||
let call = module
|
||||
.declare_function("helper_call", Linkage::Import, &call_sig)
|
||||
.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();
|
||||
|
||||
// fn(env: &Env, level: usize, ret: &mut MaybeUninit<Value>)
|
||||
let mut lookup_arg_sig = module.make_signature();
|
||||
lookup_arg_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 3],
|
||||
);
|
||||
let lookup_arg = module
|
||||
.declare_function("helper_lookup_arg", Linkage::Import, &lookup_arg_sig)
|
||||
.unwrap();
|
||||
|
||||
// fn(env: &Env, sym_ptr: *const u8, sym_len: usize, ret: &mut MaybeUninit<Value>)
|
||||
let mut lookup_sig = module.make_signature();
|
||||
lookup_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 4],
|
||||
);
|
||||
let lookup = module
|
||||
.declare_function("helper_lookup", Linkage::Import, &lookup_sig)
|
||||
.unwrap();
|
||||
|
||||
// fn(val: &mut Value, path_ptr: *mut Value, path_len: usize, engine: &mut Context, env: &mut Env)
|
||||
let mut select_sig = module.make_signature();
|
||||
select_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 5],
|
||||
);
|
||||
let select = module
|
||||
.declare_function("helper_select", Linkage::Import, &select_sig)
|
||||
.unwrap();
|
||||
|
||||
// fn(val: &mut Value, path_ptr: *mut Value, path_len: usize, default: NonNull<Value>, engine: &mut Context, env: &mut Env)
|
||||
let mut select_with_default_sig = module.make_signature();
|
||||
select_with_default_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 6],
|
||||
);
|
||||
let select_with_default = module
|
||||
.declare_function(
|
||||
"helper_select_with_default",
|
||||
Linkage::Import,
|
||||
&select_with_default_sig,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut eq_sig = module.make_signature();
|
||||
eq_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 2],
|
||||
);
|
||||
let eq = module
|
||||
.declare_function("helper_eq", Linkage::Import, &eq_sig)
|
||||
.unwrap();
|
||||
|
||||
let mut alloc_array_sig = module.make_signature();
|
||||
alloc_array_sig.params.push(AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
});
|
||||
alloc_array_sig.returns.push(AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
});
|
||||
let alloc_array = module
|
||||
.declare_function("helper_alloc_array", Linkage::Import, &alloc_array_sig)
|
||||
.unwrap();
|
||||
|
||||
let mut create_string_sig = module.make_signature();
|
||||
create_string_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 3],
|
||||
);
|
||||
let create_string = module
|
||||
.declare_function("helper_create_string", Linkage::Import, &create_string_sig)
|
||||
.unwrap();
|
||||
|
||||
let mut create_list_sig = module.make_signature();
|
||||
create_list_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 3],
|
||||
);
|
||||
let create_list = module
|
||||
.declare_function("helper_create_list", Linkage::Import, &create_list_sig)
|
||||
.unwrap();
|
||||
|
||||
let mut create_attrs_sig = module.make_signature();
|
||||
create_attrs_sig.params.push(AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
});
|
||||
let create_attrs = module
|
||||
.declare_function("helper_create_attrs", Linkage::Import, &create_attrs_sig)
|
||||
.unwrap();
|
||||
|
||||
let mut push_attr_sig = module.make_signature();
|
||||
push_attr_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 4],
|
||||
);
|
||||
let push_attr = module
|
||||
.declare_function("helper_push_attr", Linkage::Import, &push_attr_sig)
|
||||
.unwrap();
|
||||
|
||||
let mut finalize_attrs_sig = module.make_signature();
|
||||
finalize_attrs_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 2],
|
||||
);
|
||||
let finalize_attrs = module
|
||||
.declare_function(
|
||||
"helper_finalize_attrs",
|
||||
Linkage::Import,
|
||||
&finalize_attrs_sig,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut enter_with_sig = module.make_signature();
|
||||
enter_with_sig.params.extend(
|
||||
[AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
}; 2],
|
||||
);
|
||||
let enter_with = module
|
||||
.declare_function("helper_enter_with", Linkage::Import, &enter_with_sig)
|
||||
.unwrap();
|
||||
|
||||
let mut exit_with_sig = module.make_signature();
|
||||
exit_with_sig.params.push(AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
});
|
||||
let exit_with = module
|
||||
.declare_function("helper_exit_with", Linkage::Import, &exit_with_sig)
|
||||
.unwrap();
|
||||
|
||||
let mut dbg_sig = module.make_signature();
|
||||
dbg_sig.params.push(AbiParam {
|
||||
value_type: ptr_type,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
extension: ArgumentExtension::None,
|
||||
});
|
||||
let dbg = module
|
||||
.declare_function("helper_dbg", Linkage::Import, &dbg_sig)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
builder_ctx: None,
|
||||
_marker: PhantomData,
|
||||
ctx,
|
||||
module,
|
||||
|
||||
int_type,
|
||||
float_type,
|
||||
bool_type,
|
||||
ptr_type,
|
||||
value_type,
|
||||
func_sig,
|
||||
|
||||
call,
|
||||
lookup_stack,
|
||||
lookup_arg,
|
||||
lookup,
|
||||
select,
|
||||
select_with_default,
|
||||
|
||||
eq,
|
||||
|
||||
alloc_array,
|
||||
create_string,
|
||||
create_list,
|
||||
create_attrs,
|
||||
push_attr,
|
||||
finalize_attrs,
|
||||
enter_with,
|
||||
exit_with,
|
||||
dbg,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(&mut self, ir: &Lir, id: usize) -> JITFunc<Ctx> {
|
||||
let func_id = self
|
||||
.module
|
||||
.declare_function(
|
||||
format!("nixjit_thunk{id}").as_str(),
|
||||
Linkage::Local,
|
||||
&self.func_sig,
|
||||
)
|
||||
.unwrap();
|
||||
let mut func = Function::new();
|
||||
func.signature = self.func_sig.clone();
|
||||
let mut builder_ctx = self.builder_ctx.take().unwrap_or_default();
|
||||
let mut ctx = Context::new(self, FunctionBuilder::new(&mut func, &mut builder_ctx));
|
||||
|
||||
let entry = ctx.builder.create_block();
|
||||
ctx.builder.append_block_params_for_function_params(entry);
|
||||
ctx.builder.switch_to_block(entry);
|
||||
|
||||
let params = ctx.builder.block_params(entry);
|
||||
let engine = params[0];
|
||||
let env = params[1];
|
||||
let ret = params[2];
|
||||
let res = ir.compile(&mut ctx, engine, env);
|
||||
|
||||
let tag = ctx.builder.ins().stack_load(types::I64, res, 0);
|
||||
let val0 = ctx.builder.ins().stack_load(types::I64, res, 8);
|
||||
let val1 = ctx.builder.ins().stack_load(types::I64, res, 16);
|
||||
let val2 = ctx.builder.ins().stack_load(types::I64, res, 24);
|
||||
ctx.builder.ins().store(MemFlags::new(), tag, ret, 0);
|
||||
ctx.builder.ins().store(MemFlags::new(), val0, ret, 8);
|
||||
ctx.builder.ins().store(MemFlags::new(), val1, ret, 16);
|
||||
ctx.builder.ins().store(MemFlags::new(), val2, ret, 24);
|
||||
ctx.builder.ins().return_(&[]);
|
||||
ctx.builder.seal_all_blocks();
|
||||
ctx.builder.finalize();
|
||||
|
||||
let strings = ctx.strings;
|
||||
if cfg!(debug_assertions) {
|
||||
println!("{ir:#?}");
|
||||
println!("{}", func.display());
|
||||
}
|
||||
self.ctx.func = func;
|
||||
self.module.define_function(func_id, &mut self.ctx).unwrap();
|
||||
self.module.finalize_definitions().unwrap();
|
||||
self.ctx.clear();
|
||||
|
||||
let _ = self.builder_ctx.insert(builder_ctx);
|
||||
unsafe {
|
||||
JITFunc {
|
||||
func: std::mem::transmute::<*const u8, F<Ctx>>(
|
||||
self.module.get_finalized_function(func_id),
|
||||
),
|
||||
strings,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
evaluator/nixjit_lir/Cargo.toml
Normal file
16
evaluator/nixjit_lir/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "nixjit_lir"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
derive_more = { version = "2.0", features = ["full"] }
|
||||
hashbrown = "0.15"
|
||||
itertools = "0.14"
|
||||
rnix = "0.12"
|
||||
|
||||
nixjit_error = { path = "../nixjit_error" }
|
||||
nixjit_ir = { path = "../nixjit_ir" }
|
||||
nixjit_hir = { path = "../nixjit_hir" }
|
||||
nixjit_macros = { path = "../nixjit_macros" }
|
||||
nixjit_value = { path = "../nixjit_value" }
|
||||
244
evaluator/nixjit_lir/src/lib.rs
Normal file
244
evaluator/nixjit_lir/src/lib.rs
Normal file
@@ -0,0 +1,244 @@
|
||||
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
||||
|
||||
use nixjit_error::{Error, Result};
|
||||
use nixjit_hir as hir;
|
||||
use nixjit_ir::*;
|
||||
use nixjit_macros::ir;
|
||||
use nixjit_value::format_symbol;
|
||||
|
||||
ir! {
|
||||
Lir,
|
||||
|
||||
AttrSet,
|
||||
List,
|
||||
HasAttr,
|
||||
BinOp,
|
||||
UnOp,
|
||||
Select,
|
||||
If,
|
||||
Call,
|
||||
With,
|
||||
Assert,
|
||||
ConcatStrings,
|
||||
Const,
|
||||
Str,
|
||||
Var,
|
||||
Path,
|
||||
ExprRef(ExprId),
|
||||
FuncRef(ExprId),
|
||||
ArgRef(ArgIdx),
|
||||
}
|
||||
|
||||
pub enum LookupResult {
|
||||
Expr(ExprId),
|
||||
Arg(ArgIdx),
|
||||
Unknown,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
pub trait ResolveContext {
|
||||
fn new_dep(&mut self, expr: ExprId, dep: ExprId);
|
||||
fn new_func(&mut self, body: ExprId, param: Param);
|
||||
fn resolve(&mut self, expr: ExprId) -> Result<()>;
|
||||
fn lookup(&self, name: &str) -> LookupResult;
|
||||
fn with_with_env<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T);
|
||||
fn with_let_env<'a, T>(
|
||||
&mut self,
|
||||
bindings: impl IntoIterator<Item = (&'a String, &'a ExprId)>,
|
||||
f: impl FnOnce(&mut Self) -> T,
|
||||
) -> T;
|
||||
fn with_param_env<'a, T>(
|
||||
&mut self,
|
||||
ident: Option<&'a str>,
|
||||
f: impl FnOnce(&mut Self) -> T,
|
||||
) -> T;
|
||||
}
|
||||
|
||||
pub trait Resolve<Ctx: ResolveContext> {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir>;
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Hir {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
use hir::Hir::*;
|
||||
match self {
|
||||
AttrSet(x) => x.resolve(ctx),
|
||||
List(x) => x.resolve(ctx),
|
||||
HasAttr(x) => x.resolve(ctx),
|
||||
BinOp(x) => x.resolve(ctx),
|
||||
UnOp(x) => x.resolve(ctx),
|
||||
Select(x) => x.resolve(ctx),
|
||||
If(x) => x.resolve(ctx),
|
||||
Func(x) => x.resolve(ctx),
|
||||
Call(x) => x.resolve(ctx),
|
||||
With(x) => x.resolve(ctx),
|
||||
Assert(x) => x.resolve(ctx),
|
||||
ConcatStrings(x) => x.resolve(ctx),
|
||||
Const(x) => Ok(Lir::Const(x)),
|
||||
Str(x) => Ok(Lir::Str(x)),
|
||||
Var(x) => x.resolve(ctx),
|
||||
Path(x) => x.resolve(ctx),
|
||||
Let(x) => x.resolve(ctx),
|
||||
Arg(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
if self.rec {
|
||||
todo!()
|
||||
} else {
|
||||
for (_, &v) in self.stcs.iter() {
|
||||
ctx.resolve(v)?;
|
||||
}
|
||||
for &(k, v) in self.dyns.iter() {
|
||||
ctx.resolve(k)?;
|
||||
ctx.resolve(v)?;
|
||||
}
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for List {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
for &item in self.items.iter() {
|
||||
ctx.resolve(item)?;
|
||||
}
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for HasAttr {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.lhs)?;
|
||||
for attr in self.rhs.iter() {
|
||||
if let &Attr::Dynamic(expr) = attr {
|
||||
ctx.resolve(expr)?;
|
||||
}
|
||||
}
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.lhs)?;
|
||||
ctx.resolve(self.rhs)?;
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.rhs)?;
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Select {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.expr)?;
|
||||
for attr in self.attrpath.iter() {
|
||||
if let &Attr::Dynamic(expr) = attr {
|
||||
ctx.resolve(expr)?;
|
||||
}
|
||||
}
|
||||
if let Some(expr) = self.default {
|
||||
ctx.resolve(expr)?;
|
||||
}
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for If {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.cond)?;
|
||||
ctx.resolve(self.consq)?;
|
||||
ctx.resolve(self.alter)?;
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.with_param_env(self.param.ident.as_deref(), |ctx| ctx.resolve(self.body))?;
|
||||
ctx.new_func(self.body, self.param);
|
||||
Ok(Lir::FuncRef(self.body))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.func)?;
|
||||
for &arg in self.args.iter() {
|
||||
ctx.resolve(arg)?;
|
||||
}
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for With {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.namespace)?;
|
||||
let (env_used, res) = ctx.with_with_env(|ctx| ctx.resolve(self.expr));
|
||||
res?;
|
||||
if env_used {
|
||||
Ok(self.to_lir())
|
||||
} else {
|
||||
Ok(Lir::ExprRef(self.expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Assert {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.assertion)?;
|
||||
ctx.resolve(self.expr)?;
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
for &part in self.parts.iter() {
|
||||
ctx.resolve(part)?;
|
||||
}
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
use LookupResult::*;
|
||||
match ctx.lookup(&self.sym) {
|
||||
Expr(expr) => Ok(Lir::ExprRef(expr)),
|
||||
Arg(arg) => Ok(Lir::ArgRef(arg)),
|
||||
Unknown => Ok(self.to_lir()),
|
||||
NotFound => Err(Error::ResolutionError(format!(
|
||||
"undefined variable '{}'",
|
||||
format_symbol(&self.sym)
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.resolve(self.expr)?;
|
||||
Ok(self.to_lir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let {
|
||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||
ctx.with_let_env(self.bindings.iter(), |ctx| {
|
||||
for &id in self.bindings.values() {
|
||||
ctx.resolve(id)?;
|
||||
}
|
||||
ctx.resolve(self.body)
|
||||
})?;
|
||||
Ok(Lir::ExprRef(self.body))
|
||||
}
|
||||
}
|
||||
13
evaluator/nixjit_macros/Cargo.toml
Normal file
13
evaluator/nixjit_macros/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "nixjit_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
convert_case = "0.8"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
54
evaluator/nixjit_macros/src/ir.rs
Normal file
54
evaluator/nixjit_macros/src/ir.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use syn::{
|
||||
FieldsNamed, Ident, Token, Type, parenthesized,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
punctuated::Punctuated,
|
||||
token,
|
||||
};
|
||||
|
||||
pub enum VariantInput {
|
||||
Unit(Ident),
|
||||
Tuple(Ident, Type),
|
||||
Struct(Ident, FieldsNamed),
|
||||
}
|
||||
|
||||
pub struct MacroInput {
|
||||
pub base_name: Ident,
|
||||
pub variants: Punctuated<VariantInput, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for VariantInput {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let name: Ident = input.parse()?;
|
||||
|
||||
if input.peek(token::Paren) {
|
||||
let content;
|
||||
parenthesized!(content in input);
|
||||
let ty: Type = content.parse()?;
|
||||
|
||||
if !content.is_empty() {
|
||||
return Err(content.error("Expected a single type inside parentheses"));
|
||||
}
|
||||
|
||||
Ok(VariantInput::Tuple(name, ty))
|
||||
} else if input.peek(token::Brace) {
|
||||
let fields: FieldsNamed = input.parse()?;
|
||||
|
||||
Ok(VariantInput::Struct(name, fields))
|
||||
} else {
|
||||
Ok(VariantInput::Unit(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MacroInput {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let base_name = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
let variants = Punctuated::parse_terminated(input)?;
|
||||
|
||||
Ok(MacroInput {
|
||||
base_name,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
}
|
||||
121
evaluator/nixjit_macros/src/lib.rs
Normal file
121
evaluator/nixjit_macros/src/lib.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
mod ir;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn ir(input: TokenStream) -> TokenStream {
|
||||
use ir::*;
|
||||
let parsed_input = syn::parse_macro_input!(input as MacroInput);
|
||||
|
||||
let base_name = &parsed_input.base_name;
|
||||
let ref_name = format_ident!("{}Ref", base_name);
|
||||
let mut_name = format_ident!("{}Mut", base_name);
|
||||
let to_trait_name = format_ident!("To{}", base_name);
|
||||
let to_trait_fn_name = format_ident!("to_{}", base_name.to_string().to_case(Case::Snake));
|
||||
|
||||
let mut enum_variants = Vec::new();
|
||||
let mut struct_defs = Vec::new();
|
||||
let mut ref_variants = Vec::new();
|
||||
let mut mut_variants = Vec::new();
|
||||
let mut as_ref_arms = Vec::new();
|
||||
let mut as_mut_arms = Vec::new();
|
||||
let mut from_impls = Vec::new();
|
||||
let mut to_trait_impls = Vec::new();
|
||||
|
||||
for variant in parsed_input.variants {
|
||||
match variant {
|
||||
VariantInput::Unit(name) => {
|
||||
let inner_type = name.clone();
|
||||
enum_variants.push(quote! { #name(#inner_type) });
|
||||
ref_variants.push(quote! { #name(&'a #inner_type) });
|
||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||
from_impls.push(quote! {
|
||||
impl From<#inner_type> for #base_name {
|
||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||
}
|
||||
});
|
||||
to_trait_impls.push(quote! {
|
||||
impl #to_trait_name for #name {
|
||||
fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) }
|
||||
}
|
||||
});
|
||||
}
|
||||
VariantInput::Tuple(name, ty) => {
|
||||
enum_variants.push(quote! { #name(#ty) });
|
||||
ref_variants.push(quote! { #name(&'a #ty) });
|
||||
mut_variants.push(quote! { #name(&'a mut #ty) });
|
||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||
}
|
||||
VariantInput::Struct(name, fields) => {
|
||||
let inner_type = name.clone();
|
||||
struct_defs.push(quote! {
|
||||
#[derive(Debug)]
|
||||
pub struct #name #fields
|
||||
});
|
||||
enum_variants.push(quote! { #name(#inner_type) });
|
||||
ref_variants.push(quote! { #name(&'a #inner_type) });
|
||||
mut_variants.push(quote! { #name(&'a mut #inner_type) });
|
||||
as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) });
|
||||
as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) });
|
||||
from_impls.push(quote! {
|
||||
impl From<#inner_type> for #base_name {
|
||||
fn from(val: #inner_type) -> Self { #base_name::#name(val) }
|
||||
}
|
||||
});
|
||||
to_trait_impls.push(quote! {
|
||||
impl #to_trait_name for #name {
|
||||
fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||
pub enum #base_name {
|
||||
#( #enum_variants ),*
|
||||
}
|
||||
|
||||
#( #struct_defs )*
|
||||
|
||||
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||
pub enum #ref_name<'a> {
|
||||
#( #ref_variants ),*
|
||||
}
|
||||
|
||||
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||
pub enum #mut_name<'a> {
|
||||
#( #mut_variants ),*
|
||||
}
|
||||
|
||||
impl #base_name {
|
||||
pub fn as_ref(&self) -> #ref_name<'_> {
|
||||
match self {
|
||||
#( #as_ref_arms ),*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut(&mut self) -> #mut_name<'_> {
|
||||
match self {
|
||||
#( #as_mut_arms ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#( #from_impls )*
|
||||
|
||||
pub trait #to_trait_name {
|
||||
fn #to_trait_fn_name(self) -> #base_name;
|
||||
}
|
||||
|
||||
#( #to_trait_impls )*
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
8
evaluator/nixjit_value/Cargo.toml
Normal file
8
evaluator/nixjit_value/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "nixjit_value"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
derive_more = { version = "2.0", features = ["full"] }
|
||||
regex = "1.11"
|
||||
201
evaluator/nixjit_value/src/lib.rs
Normal file
201
evaluator/nixjit_value/src/lib.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
||||
use core::hash::Hash;
|
||||
use core::ops::Deref;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use derive_more::{Constructor, IsVariant, Unwrap};
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Constructor, Hash)]
|
||||
pub struct Catchable {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl<T: Into<String>> From<T> for Catchable {
|
||||
fn from(value: T) -> Self {
|
||||
Catchable { msg: value.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Catchable {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(f, "<error: {}>", self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, IsVariant, Unwrap)]
|
||||
pub enum Const {
|
||||
Bool(bool),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Null,
|
||||
}
|
||||
|
||||
impl Display for Const {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Const::*;
|
||||
match self {
|
||||
Int(x) => write!(f, "{x}"),
|
||||
Float(x) => write!(f, "{x}"),
|
||||
Bool(x) => write!(f, "{x}"),
|
||||
Null => write!(f, "null"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Const {
|
||||
fn from(value: bool) -> Self {
|
||||
Const::Bool(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Const {
|
||||
fn from(value: i64) -> Self {
|
||||
Const::Int(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Const {
|
||||
fn from(value: f64) -> Self {
|
||||
Const::Float(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)]
|
||||
pub struct Symbol(String);
|
||||
|
||||
impl<T: Into<String>> From<T> for Symbol {
|
||||
fn from(value: T) -> Self {
|
||||
Symbol(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_symbol<'a>(sym: &'a str) -> Cow<'a, str> {
|
||||
if REGEX.is_match(sym) {
|
||||
Cow::Borrowed(sym)
|
||||
} else {
|
||||
Cow::Owned(format!(r#""{sym}""#))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Symbol {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
if self.normal() {
|
||||
write!(f, "{}", self.0)
|
||||
} else {
|
||||
write!(f, r#""{}""#, self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r#"^[a-zA-Z\_][a-zA-Z0-9\_\'\-]*$"#).unwrap());
|
||||
impl Symbol {
|
||||
fn normal(&self) -> bool {
|
||||
REGEX.is_match(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Symbol {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
pub fn into_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn as_inner(&self) -> &String {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Constructor, Clone, PartialEq)]
|
||||
pub struct AttrSet {
|
||||
data: BTreeMap<Symbol, Value>,
|
||||
}
|
||||
|
||||
impl Debug for AttrSet {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
write!(f, "{{ ")?;
|
||||
for (k, v) in self.data.iter() {
|
||||
match v {
|
||||
List(_) => write!(f, "{k:?} = [ ... ]; ")?,
|
||||
AttrSet(_) => write!(f, "{k:?} = {{ ... }}; ")?,
|
||||
v => write!(f, "{k:?} = {v:?}; ")?,
|
||||
}
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AttrSet {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
write!(f, "{{ ")?;
|
||||
for (k, v) in self.data.iter() {
|
||||
write!(f, "{k} = ")?;
|
||||
match v {
|
||||
AttrSet(_) => write!(f, "{{ ... }}"),
|
||||
List(_) => write!(f, "[ ... ]"),
|
||||
v => write!(f, "{v}"),
|
||||
}?;
|
||||
write!(f, "; ")?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Constructor, Clone, Debug, PartialEq)]
|
||||
pub struct List {
|
||||
data: Vec<Value>,
|
||||
}
|
||||
|
||||
impl Display for List {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(f, "[ ")?;
|
||||
for v in self.data.iter() {
|
||||
write!(f, "{v} ")?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
||||
pub enum Value {
|
||||
Const(Const),
|
||||
String(String),
|
||||
AttrSet(AttrSet),
|
||||
List(List),
|
||||
Catchable(Catchable),
|
||||
Thunk,
|
||||
Func,
|
||||
PrimOp(&'static str),
|
||||
PrimOpApp(&'static str),
|
||||
Repeated,
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
match self {
|
||||
Const(x) => write!(f, "{x}"),
|
||||
String(x) => write!(f, "{x}"),
|
||||
AttrSet(x) => write!(f, "{x}"),
|
||||
List(x) => write!(f, "{x}"),
|
||||
Catchable(x) => write!(f, "{x}"),
|
||||
Thunk => write!(f, "<CODE>"),
|
||||
Func => write!(f, "<LAMBDA>"),
|
||||
PrimOp(x) => write!(f, "<PRIMOP {x}>"),
|
||||
PrimOpApp(x) => write!(f, "<PRIMOP-APP {x}>"),
|
||||
Repeated => write!(f, "<REPEATED>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user