Skip to main content

cljrs_eval/
lib.rs

1//! IR-accelerated evaluation for clojurust.
2//!
3//! Wraps the tree-walking interpreter (`cljrs-interp`) with IR lowering and
4//! interpretation.  When a function has been lowered to IR (eagerly at
5//! definition time or from a pre-built cache), calls are dispatched to the
6//! tier-1 IR interpreter; otherwise they fall back to tree-walking.
7//!
8//! Key components:
9//! - `ir_interp` — tier-1 IR interpreter (register-file execution of `IrFunction`)
10//! - `ir_cache` — thread-safe cache of lowered IR keyed by arity ID
11//! - `ir_convert` — converts Clojure Value data → Rust `IrFunction`
12//! - `lower` — bridges the Clojure compiler front-end to produce IR
13//! - `apply` — IR-aware function dispatch with tree-walk fallback
14
15// EvalError::Thrown wraps a full Value; boxing would require pervasive changes.
16#![allow(clippy::result_large_err)]
17// Namespace/GlobalEnv use Mutex<HashMap<Arc<str>, GcPtr<Var>>> — intentionally verbose for clarity.
18#![allow(clippy::type_complexity)]
19
20pub mod apply;
21pub mod ir_cache;
22pub mod ir_convert;
23pub mod ir_interp;
24pub mod lower;
25
26pub use cljrs_env::callback::invoke;
27pub use cljrs_env::env::{Env, GlobalEnv};
28pub use cljrs_env::error::{EvalError, EvalResult};
29pub use cljrs_env::loader::load_ns;
30pub use cljrs_interp::eval::eval;
31
32use crate::ir_interp::eager_lower_fn;
33use std::sync::Arc;
34
35pub fn register_compiler_sources(globals: &Arc<GlobalEnv>) {
36    globals.register_builtin_source("cljrs.compiler.ir", cljrs_ir::COMPILER_IR_SOURCE);
37    globals.register_builtin_source("cljrs.compiler.known", cljrs_ir::COMPILER_KNOWN_SOURCE);
38    globals.register_builtin_source("cljrs.compiler.anf", cljrs_ir::COMPILER_ANF_SOURCE);
39    globals.register_builtin_source("cljrs.compiler.escape", cljrs_ir::COMPILER_ESCAPE_SOURCE);
40    globals.register_builtin_source(
41        "cljrs.compiler.optimize",
42        cljrs_ir::COMPILER_OPTIMIZE_SOURCE,
43    );
44}
45
46/// Load the Clojure compiler namespaces and mark the compiler as ready
47/// for IR lowering.  Called lazily on first lowering attempt.
48pub fn ensure_compiler_loaded(globals: &Arc<GlobalEnv>, env: &mut Env) -> bool {
49    // Already loaded?
50    if globals
51        .compiler_ready
52        .load(std::sync::atomic::Ordering::Acquire)
53    {
54        return true;
55    }
56
57    // Don't load if CLJRS_NO_IR is set.
58    if std::env::var("CLJRS_NO_IR").is_ok() {
59        return false;
60    }
61
62    // Prevent concurrent loading attempts.
63    static LOADING: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
64    if LOADING.swap(true, std::sync::atomic::Ordering::AcqRel) {
65        return false; // Another thread is loading.
66    }
67
68    let span = || cljrs_types::span::Span::new(Arc::new("<compiler-load>".to_string()), 0, 0, 1, 1);
69    for ns_name in &[
70        "cljrs.compiler.ir",
71        "cljrs.compiler.known",
72        "cljrs.compiler.anf",
73    ] {
74        let require_form = cljrs_reader::Form::new(
75            cljrs_reader::form::FormKind::List(vec![
76                cljrs_reader::Form::new(
77                    cljrs_reader::form::FormKind::Symbol("require".into()),
78                    span(),
79                ),
80                cljrs_reader::Form::new(
81                    cljrs_reader::form::FormKind::Quote(Box::new(cljrs_reader::Form::new(
82                        cljrs_reader::form::FormKind::Symbol((*ns_name).into()),
83                        span(),
84                    ))),
85                    span(),
86                ),
87            ]),
88            span(),
89        );
90        if let Err(e) = eval(&require_form, env) {
91            eprintln!("[compiler-load warning] failed to load {ns_name}: {e:?}");
92            LOADING.store(false, std::sync::atomic::Ordering::Release);
93            return false;
94        }
95    }
96
97    globals
98        .compiler_ready
99        .store(true, std::sync::atomic::Ordering::Release);
100    LOADING.store(false, std::sync::atomic::Ordering::Release);
101    true
102}
103
104pub fn standard_env_minimal() -> Arc<GlobalEnv> {
105    cljrs_interp::standard_env_minimal(Some(eval), Some(apply::call_cljrs_fn), Some(eager_lower_fn))
106}
107
108pub fn standard_env() -> Arc<GlobalEnv> {
109    let globals = standard_env_minimal();
110    register_compiler_sources(&globals);
111    globals
112}
113
114pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
115    let globals = standard_env();
116    globals.set_source_paths(source_paths);
117    globals
118}
119
120/// Load pre-built IR from a serialized bundle into the IR cache.
121///
122/// Walks all namespaces in the `GlobalEnv`, and for each function var whose
123/// arity matches a bundle key (`"ns/name:param_count"` or `"ns/name:param_count+"`
124/// for variadic), stores the pre-built IR in the cache keyed by the runtime
125/// `ir_arity_id`.
126///
127/// Returns the number of arities successfully loaded.
128pub fn load_prebuilt_ir(globals: &Arc<GlobalEnv>, bundle: &cljrs_ir::IrBundle) -> usize {
129    use cljrs_value::Value;
130
131    let ns_map = globals.namespaces.read().unwrap();
132    let mut loaded = 0usize;
133
134    for (ns_name, ns_ptr) in ns_map.iter() {
135        let interns = ns_ptr.get().interns.lock().unwrap();
136        for (var_name, var) in interns.iter() {
137            let val = var.get().deref().unwrap_or(Value::Nil);
138            let f = match &val {
139                Value::Fn(gc_fn) => gc_fn.get(),
140                _ => continue,
141            };
142            if f.is_macro {
143                continue;
144            }
145
146            for arity in &f.arities {
147                let key = if arity.rest_param.is_some() {
148                    format!("{ns_name}/{var_name}:{}+", arity.params.len())
149                } else {
150                    format!("{ns_name}/{var_name}:{}", arity.params.len())
151                };
152
153                if let Some(ir_func) = bundle.get(&key) {
154                    ir_cache::store_cached(arity.ir_arity_id, Arc::new(ir_func.clone()));
155                    loaded += 1;
156                }
157            }
158        }
159    }
160
161    loaded
162}