chore: comment

This commit is contained in:
2025-08-07 21:00:32 +08:00
parent f946cb2fd1
commit 67cdcfea33
24 changed files with 734 additions and 105 deletions

View File

@@ -1,3 +1,16 @@
//! The Low-level Intermediate Representation (LIR) for nixjit.
//!
//! This module defines the LIR, which is a more resolved and explicit representation
//! than the HIR. The key transformation from HIR to LIR is the resolution of variable
//! lookups. In the LIR, variable references are either resolved to a specific expression,
//! a function argument, or are left as-is for dynamic lookup in a `with` environment.
//!
//! Key components:
//! - `Lir`: An enum representing all LIR expression types, generated by the `ir!` macro.
//! - `Resolve`: A trait for converting HIR nodes into LIR expressions.
//! - `ResolveContext`: A trait providing the context for resolution, including scope
//! management and dependency tracking.
use derive_more::{IsVariant, TryUnwrap, Unwrap};
use nixjit_error::{Error, Result};
@@ -6,6 +19,7 @@ use nixjit_ir::*;
use nixjit_macros::ir;
use nixjit_value::format_symbol;
// The `ir!` macro generates the `Lir` enum and related structs and traits.
ir! {
Lir,
@@ -30,35 +44,58 @@ ir! {
ArgRef(ArgIdx),
}
#[derive(Debug)]
pub struct Builtins;
/// Represents the result of a variable lookup within the `ResolveContext`.
#[derive(Debug)]
pub enum LookupResult {
/// The variable was found and resolved to a specific expression.
Expr(ExprId),
/// The variable was found and resolved to a function argument.
Arg(ArgIdx),
/// The variable could not be resolved statically, likely due to a `with` expression.
/// The lookup must be performed dynamically at evaluation time.
Unknown,
/// The variable was not found in any scope.
NotFound,
}
/// A context for the HIR-to-LIR resolution process.
///
/// This trait abstracts the environment in which expressions are resolved, managing
/// scopes, dependencies, and the resolution of expressions themselves.
pub trait ResolveContext {
/// Records a dependency of one expression on another.
fn new_dep(&mut self, expr: &ExprId, dep: ExprId);
/// Creates a new function, associating a parameter specification with a body expression.
fn new_func(&mut self, body: &ExprId, param: Param);
/// Triggers the resolution of a given expression.
fn resolve(&mut self, expr: &ExprId) -> Result<()>;
/// Looks up a variable by name in the current scope.
fn lookup(&self, name: &str) -> LookupResult;
/// Enters a `with` scope for the duration of a closure's execution.
fn with_with_env<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T);
/// Enters a `let` scope with a given set of bindings for the duration of a closure.
fn with_let_env<'a, T>(
&mut self,
bindings: impl Iterator<Item = (&'a String, &'a ExprId)>,
f: impl FnOnce(&mut Self) -> T,
) -> T;
/// Enters a function parameter scope for the duration of a closure.
fn with_param_env<T>(&mut self, ident: Option<String>, f: impl FnOnce(&mut Self) -> T) -> T;
}
/// A trait for converting (resolving) an HIR node into an LIR expression.
pub trait Resolve<Ctx: ResolveContext> {
/// Performs the resolution.
fn resolve(self, ctx: &mut Ctx) -> Result<Lir>;
}
/// The main entry point for resolving any HIR expression.
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Hir {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
use hir::Hir::*;
@@ -80,14 +117,21 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Hir {
Var(x) => x.resolve(ctx),
Path(x) => x.resolve(ctx),
Let(x) => x.resolve(ctx),
// The `Arg` in HIR is a placeholder. During resolution, it's replaced by
// a reference to the *current* function's argument. We assume index 0
// here, as the context manages the actual argument index.
Arg(_) => unsafe { Ok(Lir::ArgRef(ArgIdx::from(0))) },
}
}
}
/// Resolves an `AttrSet`. If it's recursive, resolution is more complex (and currently a TODO).
/// Otherwise, it resolves all key and value expressions.
impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
if self.rec {
// TODO: Implement resolution for recursive attribute sets.
// This requires setting up a recursive scope where attributes can refer to each other.
todo!()
} else {
for (_, v) in self.stcs.iter() {
@@ -102,6 +146,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
}
}
/// Resolves a `List` by resolving each of its items.
impl<Ctx: ResolveContext> Resolve<Ctx> for List {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
for item in self.items.iter() {
@@ -111,18 +156,20 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for List {
}
}
/// Resolves a `HasAttr` expression by resolving the LHS and any dynamic attributes in the path.
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)?;
ctx.resolve(expr)?;
}
}
Ok(self.to_lir())
}
}
/// Resolves a `BinOp` by resolving its left and right hand sides.
impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(&self.lhs)?;
@@ -131,6 +178,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp {
}
}
/// Resolves a `UnOp` by resolving its right hand side.
impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(&self.rhs)?;
@@ -138,12 +186,14 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
}
}
/// Resolves a `Select` by resolving the expression being selected from, any dynamic
/// attributes in the path, and the default value if it exists.
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)?;
ctx.resolve(expr)?;
}
}
if let Some(ref expr) = self.default {
@@ -153,6 +203,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Select {
}
}
/// Resolves an `If` expression by resolving the condition, consequence, and alternative.
impl<Ctx: ResolveContext> Resolve<Ctx> for If {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(&self.cond)?;
@@ -162,6 +213,8 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for If {
}
}
/// Resolves a `Func` by resolving its body within a new parameter scope.
/// It then registers the function with the context.
impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
ctx.with_param_env(self.param.ident.clone(), |ctx| ctx.resolve(&self.body))?;
@@ -170,6 +223,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
}
}
/// Resolves a `Call` by resolving the function and all of its arguments.
impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(&self.func)?;
@@ -180,11 +234,15 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
}
}
/// Resolves a `With` expression by resolving the namespace and the body.
/// The body is resolved within a special "with" scope.
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?;
// Optimization: if the `with` environment was not actually used by any variable
// lookup in the body, we can elide the `With` node entirely.
if env_used {
Ok(self.to_lir())
} else {
@@ -193,6 +251,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for With {
}
}
/// Resolves an `Assert` by resolving the assertion condition and the body.
impl<Ctx: ResolveContext> Resolve<Ctx> for Assert {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(&self.assertion)?;
@@ -201,6 +260,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Assert {
}
}
/// Resolves a `ConcatStrings` by resolving each part.
impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
for part in self.parts.iter() {
@@ -210,6 +270,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
}
}
/// Resolves a `Var` by looking it up in the current context.
impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
use LookupResult::*;
@@ -225,6 +286,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
}
}
/// Resolves a `Path` by resolving the underlying expression that defines the path's content.
impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
ctx.resolve(&self.expr)?;
@@ -232,6 +294,8 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
}
}
/// Resolves a `Let` expression by creating a new scope for the bindings, resolving
/// the bindings and the body, and then returning a reference to the body.
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let {
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
ctx.with_let_env(self.bindings.iter(), |ctx| {
@@ -240,6 +304,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let {
}
ctx.resolve(&self.body)
})?;
// The `let` expression itself evaluates to its body.
Ok(Lir::ExprRef(self.body))
}
}