diff --git a/Cargo.lock b/Cargo.lock index 7b5d207..83e50db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -407,6 +407,16 @@ dependencies = [ "rnix", ] +[[package]] +name = "nixjit_builtins" +version = "0.1.0" +dependencies = [ + "nixjit_error", + "nixjit_eval", + "nixjit_macros", + "nixjit_value", +] + [[package]] name = "nixjit_context" version = "0.1.0" @@ -417,6 +427,7 @@ dependencies = [ "cranelift-native", "hashbrown 0.15.4", "itertools", + "nixjit_builtins", "nixjit_error", "nixjit_eval", "nixjit_hir", diff --git a/Cargo.toml b/Cargo.toml index 532aaa7..6d67db6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "3" members = [ "evaluator/nixjit", + "evaluator/nixjit_builtins", "evaluator/nixjit_context", "evaluator/nixjit_error", "evaluator/nixjit_eval", diff --git a/evaluator/nixjit/src/lib.rs b/evaluator/nixjit/src/lib.rs index c7b7f76..37285d4 100644 --- a/evaluator/nixjit/src/lib.rs +++ b/evaluator/nixjit/src/lib.rs @@ -1,3 +1,15 @@ +//! The main library crate for the nixjit interpreter and JIT compiler. +//! +//! This crate orchestrates the entire process of parsing, analyzing, +//! and evaluating Nix expressions. It integrates all the other `nixjit_*` +//! components to provide a complete Nix evaluation environment. +//! +//! The primary workflow is demonstrated in the tests within `test.rs`: +//! 1. Parse Nix source code into an `rnix` AST. +//! 2. "Downgrade" the AST into the High-Level IR (HIR). +//! 3. "Resolve" the HIR into the Low-Level IR (LIR), handling variable lookups. +//! 4. "Evaluate" the LIR to produce a final value. + #[cfg(test)] mod test; diff --git a/evaluator/nixjit_builtins/Cargo.toml b/evaluator/nixjit_builtins/Cargo.toml new file mode 100644 index 0000000..791f1ae --- /dev/null +++ b/evaluator/nixjit_builtins/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "nixjit_builtins" +version = "0.1.0" +edition = "2024" + +[dependencies] +nixjit_error = { path = "../nixjit_error" } +nixjit_eval = { path = "../nixjit_eval" } +nixjit_macros = { path = "../nixjit_macros" } +nixjit_value = { path = "../nixjit_value" } diff --git a/evaluator/nixjit_builtins/src/lib.rs b/evaluator/nixjit_builtins/src/lib.rs new file mode 100644 index 0000000..85fdad7 --- /dev/null +++ b/evaluator/nixjit_builtins/src/lib.rs @@ -0,0 +1,38 @@ +use nixjit_macros::builtins; +use nixjit_eval::EvalContext; + +pub trait BuiltinsContext: EvalContext {} + +#[builtins] +mod builtins { + use nixjit_error::{Error, Result}; + use nixjit_eval::Value; + use nixjit_value::Const; + + use super::BuiltinsContext; + + const TRUE: Const = Const::Bool(true); + const FALSE: Const = Const::Bool(false); + const NULL: Const = Const::Null; + + fn add(a: Value, b: Value) -> Result> { + use Value::*; + Ok(match (a, b) { + (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), + (a @ Value::Catchable(_), _) => a, + (_, b @ Value::Catchable(_)) => b, + _ => return Err(Error::EvalError(format!(""))), + }) + } + + pub fn import(ctx: &mut Ctx, path: Value) -> Result> { + todo!() + } + + fn elem_at(list: Value, idx: Value) -> Result> { + todo!() + } +} diff --git a/evaluator/nixjit_context/Cargo.toml b/evaluator/nixjit_context/Cargo.toml index f25844b..210d788 100644 --- a/evaluator/nixjit_context/Cargo.toml +++ b/evaluator/nixjit_context/Cargo.toml @@ -13,6 +13,7 @@ cranelift-module = "0.122" cranelift-jit = "0.122" cranelift-native = "0.122" +nixjit_builtins = { path = "../nixjit_builtins" } nixjit_error = { path = "../nixjit_error" } nixjit_eval = { path = "../nixjit_eval" } nixjit_hir = { path = "../nixjit_hir" } diff --git a/evaluator/nixjit_eval/src/lib.rs b/evaluator/nixjit_eval/src/lib.rs index d804a44..bf56d2c 100644 --- a/evaluator/nixjit_eval/src/lib.rs +++ b/evaluator/nixjit_eval/src/lib.rs @@ -135,10 +135,10 @@ impl Evaluate for ir::BinOp { let mut lhs = self.lhs.eval(ctx)?; let mut rhs = self.rhs.eval(ctx)?; match self.kind { - Add => lhs.add(rhs), + Add => lhs.add(rhs)?, Sub => { rhs.neg(); - lhs.add(rhs); + lhs.add(rhs)?; } Mul => lhs.mul(rhs), Div => lhs.div(rhs)?, diff --git a/evaluator/nixjit_eval/src/value/mod.rs b/evaluator/nixjit_eval/src/value/mod.rs index 91f1b8b..c937b78 100644 --- a/evaluator/nixjit_eval/src/value/mod.rs +++ b/evaluator/nixjit_eval/src/value/mod.rs @@ -1,4 +1,5 @@ use std::hash::Hash; +use std::process::abort; use std::rc::Rc; use std::fmt::{write, Debug}; @@ -7,7 +8,7 @@ use derive_more::{IsVariant, Unwrap}; use func::FuncApp; use hashbrown::HashSet; use nixjit_ir::ExprId; -use replace_with::replace_with_or_abort; +use replace_with::{replace_with_and_return, replace_with_or_abort}; use nixjit_error::{Error, Result}; use nixjit_value::Const; @@ -307,21 +308,24 @@ impl Value { } } - pub fn add(&mut self, other: Self) { + pub fn add(&mut self, other: Self) -> Result<()> { 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!(), - }); + replace_with_and_return(self, || abort(), |a| { + let val = 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, + _ => return (Err(Error::EvalError(format!(""))), Value::Null), + }; + (Ok(()), val) + }) } pub fn mul(&mut self, other: Self) { diff --git a/evaluator/nixjit_macros/Cargo.toml b/evaluator/nixjit_macros/Cargo.toml index 6ad9140..09e0f08 100644 --- a/evaluator/nixjit_macros/Cargo.toml +++ b/evaluator/nixjit_macros/Cargo.toml @@ -8,6 +8,6 @@ proc-macro = true [dependencies] convert_case = "0.8" -proc-macro2 = "1.0" quote = "1.0" +proc-macro2 = "1.0" syn = { version = "2.0", features = ["full"] } diff --git a/evaluator/nixjit_macros/src/builtins.rs b/evaluator/nixjit_macros/src/builtins.rs new file mode 100644 index 0000000..983d4ee --- /dev/null +++ b/evaluator/nixjit_macros/src/builtins.rs @@ -0,0 +1,207 @@ +use convert_case::{Case, Casing}; +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote, ToTokens}; +use syn::{ + parse_macro_input, FnArg, Item, ItemConst, ItemFn, ItemMod, Pat, PatType, Type, + Visibility, +}; + +pub fn builtins_impl(input: TokenStream) -> TokenStream { + let item_mod = parse_macro_input!(input as ItemMod); + + let (_brace, items) = match item_mod.content.clone() { + Some(content) => content, + None => { + return syn::Error::new_spanned( + item_mod, + "`#[builtins]` macro can only be used on an inline module: `mod name { ... }`", + ) + .to_compile_error() + .into(); + } + }; + + let mut pub_item_mod: Vec = Vec::new(); + let mut const_inserters = Vec::new(); + let mut global_inserters = Vec::new(); + let mut scoped_inserters = Vec::new(); + let mut wrappers = Vec::new(); + + for item in &items { + match item { + Item::Const(item_const) => { + let inserter = generate_const_inserter(item_const); + const_inserters.push(inserter); + pub_item_mod.push(quote! { + pub #item_const + }.into()); + } + Item::Fn(item_fn) => { + let (inserter, wrapper) = match generate_fn_wrapper(item_fn) { + Ok(result) => result, + Err(e) => return e.to_compile_error().into(), + }; + if matches!(item_fn.vis, Visibility::Public(_)) { + global_inserters.push(inserter); + pub_item_mod.push(quote! { #item_fn }.into()); + } else { + scoped_inserters.push(inserter); + pub_item_mod.push(quote! { + pub #item_fn + }.into()); + } + wrappers.push(wrapper); + } + item => pub_item_mod.push(item.to_token_stream()) + } + } + + let output = quote! { + mod builtins { + #(#pub_item_mod)* + #(#wrappers)* + } + + pub struct Builtins { + pub consts: ::std::vec::Vec<(String, ::nixjit_value::Const)>, + pub global: ::std::vec::Vec<(String, fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value>)>, + pub scoped: ::std::vec::Vec<(String, fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value>)>, + } + + impl Builtins { + pub fn new() -> Self { + let mut consts = ::std::vec::Vec::new(); + let mut global = ::std::vec::Vec::new(); + let mut scoped = ::std::vec::Vec::new(); + + #(#const_inserters)* + #(#global_inserters)* + #(#scoped_inserters)* + + Self { consts, global, scoped } + } + } + }; + + output.into() +} + +fn generate_const_inserter( + item_const: &ItemConst, +) -> proc_macro2::TokenStream { + let name_str = item_const.ident.to_string().from_case(Case::UpperSnake).to_case(Case::Camel); + let const_name = &item_const.ident; + + quote! { + consts.push((#name_str.to_string(), builtins::#const_name)); + } +} + +fn generate_fn_wrapper( + item_fn: &ItemFn, +) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> { + let fn_name = &item_fn.sig.ident; + let name_str = fn_name.to_string().from_case(Case::Snake).to_case(Case::Camel); + let wrapper_name = format_ident!("wrapper_{}", fn_name); + let mod_name = format_ident!("builtins"); + + let is_pub = matches!(item_fn.vis, Visibility::Public(_)); + let mut user_args = item_fn.sig.inputs.iter().peekable(); + + let has_ctx = if let Some(FnArg::Typed(first_arg)) = user_args.peek() { + if let Type::Reference(_) = *first_arg.ty { + user_args.next(); + true + } else { + false + } + } else { + return Err(syn::Error::new_spanned( + fn_name, + "A builtin function must not have a receiver argument", + )); + }; + + let arg_pats: Vec<_> = user_args.rev().collect(); + let arg_count = arg_pats.len(); + let arg_unpacks = arg_pats.iter().enumerate().map(|(i, arg)| { + let arg_name = match &arg { + FnArg::Typed(PatType { pat, .. }) => { + if let Pat::Ident(pat_ident) = &**pat { + pat_ident.ident.clone() + } else { + format_ident!("arg{}", i, span = Span::call_site()) + } + } + _ => format_ident!("arg{}", i, span = Span::call_site()), + }; + let arg_ty = match &arg { + FnArg::Typed(PatType { ty, .. }) => ty, + _ => unreachable!(), + }; + + quote! { + let #arg_name: #arg_ty = args.pop().ok_or_else(|| ::nixjit_error::Error::EvalError("Not enough arguments provided".to_string()))? + .try_into().map_err(|e| ::nixjit_error::Error::EvalError(format!("Argument type conversion failed: {}", e)))?; + } + }); + + let arg_names: Vec<_> = arg_pats + .iter() + .enumerate() + .map(|(i, arg)| match &arg { + FnArg::Typed(PatType { pat, .. }) => { + if let Pat::Ident(pat_ident) = &**pat { + pat_ident.ident.clone() + } else { + format_ident!("arg{}", i, span = Span::call_site()) + } + } + _ => unreachable!(), + }) + .rev() + .collect(); + + let mut call_args = quote! { #(#arg_names),* }; + if has_ctx { + call_args = quote! { ctx, #(#arg_names),* }; + } + + let returns_result = match &item_fn.sig.output { + syn::ReturnType::Type(_, ty) => { + if let Type::Path(type_path) = &**ty { + type_path.path.segments.iter().any(|s| s.ident == "Result") + } else { + false + } + } + _ => false, + }; + + let call_expr = if returns_result { + quote! { #fn_name(#call_args) } + } else { + quote! { Ok(#fn_name(#call_args).into()) } + }; + + let fn_type = quote! { fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value> }; + let inserter = if is_pub { + quote! { global.push((#name_str.to_string(), #mod_name::#wrapper_name as #fn_type)); } + } else { + quote! { scoped.push((#name_str.to_string(), #mod_name::#wrapper_name as #fn_type)); } + }; + + let wrapper = quote! { + pub fn #wrapper_name(ctx: &mut Ctx, mut args: Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value> { + if args.len() != #arg_count { + return Err(::nixjit_error::Error::EvalError(format!("Function '{}' expects {} arguments, but received {}", #name_str, #arg_count, args.len()))); + } + #(#arg_unpacks)* + + #call_expr + } + }; + + Ok((inserter, wrapper)) +} diff --git a/evaluator/nixjit_macros/src/ir.rs b/evaluator/nixjit_macros/src/ir.rs index 589bd89..bb85c43 100644 --- a/evaluator/nixjit_macros/src/ir.rs +++ b/evaluator/nixjit_macros/src/ir.rs @@ -1,3 +1,6 @@ +use convert_case::{Case, Casing}; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; use syn::{ FieldsNamed, Ident, Token, Type, parenthesized, parse::{Parse, ParseStream, Result}, @@ -52,3 +55,117 @@ impl Parse for MacroInput { }) } } + +pub fn ir_impl(input: TokenStream) -> TokenStream { + 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) +} diff --git a/evaluator/nixjit_macros/src/lib.rs b/evaluator/nixjit_macros/src/lib.rs index bb01254..69eed95 100644 --- a/evaluator/nixjit_macros/src/lib.rs +++ b/evaluator/nixjit_macros/src/lib.rs @@ -1,121 +1,14 @@ -use convert_case::{Case, Casing}; use proc_macro::TokenStream; -use quote::{format_ident, quote}; +mod builtins; 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) + ir::ir_impl(input) +} + +#[proc_macro_attribute] +pub fn builtins(_attr: TokenStream, input: TokenStream) -> TokenStream { + builtins::builtins_impl(input) }