cljrs-eval 0.1.44

IR-accelerated evaluation for clojurust (tier-1 IR interpreter + lowering)
Documentation

cljrs-eval

IR-accelerated evaluation for clojurust. Wraps the tree-walking interpreter (cljrs-interp) with IR lowering and interpretation for faster function execution.

Phase: IR tier-1 interpreter — implemented.


Purpose

When a Clojure function has been lowered to IR (eagerly at definition time via the on_fn_defined hook, or from a pre-built cache), calls are dispatched to the tier-1 IR interpreter. Otherwise they fall back to the tree-walking interpreter in cljrs-interp.

The crate also manages the Clojure compiler namespaces (cljrs.compiler.anf, cljrs.compiler.ir, cljrs.compiler.known) that perform ANF lowering of Clojure source to the IR representation defined in cljrs-ir.


File layout

src/
  lib.rs          — module declarations, re-exports, standard_env_minimal/standard_env/standard_env_with_paths,
                    register_compiler_sources, ensure_compiler_loaded
  apply.rs        — IR-aware function dispatch: tries IR cache, falls back to cljrs_interp::apply
  ir_interp.rs    — tier-1 IR interpreter: executes IrFunction over a VarId→Value register file
  ir_cache.rs     — thread-safe cache of lowered IR keyed by arity ID (NotAttempted/Cached/Unsupported)
  ir_convert.rs   — converts Clojure Value data structures (maps/vectors/keywords) → Rust IR types
  lower.rs        — bridges the Clojure compiler front-end (cljrs.compiler.anf/lower-fn-body) to produce IrFunction

Public API

/// Re-exports from cljrs-interp and cljrs-env:
pub use cljrs_interp::eval::eval;
pub use cljrs_env::env::{Env, GlobalEnv};
pub use cljrs_env::error::{EvalError, EvalResult};
pub use cljrs_env::callback::invoke;
pub use cljrs_env::loader::load_ns;

/// Create a minimal GlobalEnv with IR-accelerated eval/call dispatch.
/// Passes cljrs-eval's apply::call_cljrs_fn (IR + tree-walk fallback)
/// and eager_lower_fn hook to cljrs-interp's standard_env_minimal.
pub fn standard_env_minimal() -> Arc<GlobalEnv>;

/// Like standard_env_minimal() but also registers compiler sources.
pub fn standard_env() -> Arc<GlobalEnv>;

/// Like standard_env() but also sets user source paths.
pub fn standard_env_with_paths(source_paths: Vec<PathBuf>) -> Arc<GlobalEnv>;

/// Register the Clojure compiler namespace sources into the GlobalEnv.
pub fn register_compiler_sources(globals: &Arc<GlobalEnv>);

/// Load the Clojure compiler namespaces and mark the compiler as ready
/// for IR lowering. Thread-safe; idempotent.
pub fn ensure_compiler_loaded(globals: &Arc<GlobalEnv>, env: &mut Env) -> bool;

/// Load pre-built IR from a serialized bundle into the IR cache.
/// Walks all namespaces, matches bundle keys to runtime arity IDs.
/// Returns the number of arities loaded.
pub fn load_prebuilt_ir(globals: &Arc<GlobalEnv>, bundle: &IrBundle) -> usize;

/// IR lowering helpers (in module `lower`):
///
/// `lower_arity` — runs only `cljrs.compiler.anf/lower-fn-body`.
/// `lower_and_optimize_arity` — also runs `cljrs.compiler.optimize/optimize`,
///     which inserts `RegionStart`/`RegionAlloc`/`RegionEnd` instructions for
///     non-escaping allocations.  Caller must have required
///     `cljrs.compiler.optimize` first.  Used by `cljrs-stdlib`'s prebuild
///     (`prebuild-ir` feature) and by AOT compilation, so prebuilt/AOT code
///     gets bump-allocated regions.
pub mod lower {
    pub fn lower_arity(...) -> Result<IrFunction, LowerError>;
    pub fn lower_and_optimize_arity(...) -> Result<IrFunction, LowerError>;
}

IR dispatch flow

  1. apply::call_cljrs_fn is registered as the call_cljrs_fn function pointer in GlobalEnv
  2. On each call, it checks ir_cache::get_cached(arity_id) for a pre-lowered IR function
  3. If cached: executes via ir_interp::interpret_ir (register-file interpreter)
  4. If not cached: falls back to cljrs_interp::apply::call_cljrs_fn (tree-walking)
  5. Eager lowering: ir_interp::eager_lower_fn is registered as the on_fn_defined hook; when compiler_ready is true, new fn* definitions are lowered immediately
  6. eval_call in cljrs_interp routes Value::Fn calls through globals.call_cljrs_fn (the registered hook) rather than calling the tree-walker directly, so IR-cached arities are used on direct call paths too

Special-form coverage in the IR interpreter

Several clojure.core entries are sentinel stubs that error unconditionally when called through the normal function-call path — the real logic lives in eval_call's special-form dispatch. ir_interp.rs handles all of them without going through the stubs:

Operation How handled in IR
swap! (KnownFn::AtomSwap) cljrs_interp::apply::eval_swap_bang
with-bindings* (KnownFn::WithBindings) cljrs_interp::apply::eval_with_bindings_star
volatile! dispatch_sentinel_by_nameeval_volatile
vreset! dispatch_sentinel_by_nameeval_vreset_bang
vswap! dispatch_sentinel_by_nameeval_vswap_bang
make-delay dispatch_sentinel_by_namemake_delay_from_fn
alter-var-root dispatch_sentinel_by_nameeval_alter_var_root
vary-meta dispatch_sentinel_by_nameeval_vary_meta
send / send-off dispatch_sentinel_by_nameeval_send_to_agent

Both Inst::Call (where the callee register holds a sentinel NativeFunction) and Inst::CallDirect (where the callee is named directly) are intercepted.


Dependencies

Crate Role
cljrs-interp Tree-walking interpreter (fallback)
cljrs-env Env, GlobalEnv, EvalError, callbacks, loader
cljrs-builtins form_to_value (used by lower.rs)
cljrs-ir IR types (IrFunction, Block, Inst, etc.) and compiler source strings
cljrs-types Span
cljrs-gc GcPtr<T>
cljrs-reader Form AST (for compiler loading)
cljrs-value Value, CljxFn, collections