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        "cljrs.compiler.escape",
74        "cljrs.compiler.optimize",
75    ] {
76        let require_form = cljrs_reader::Form::new(
77            cljrs_reader::form::FormKind::List(vec![
78                cljrs_reader::Form::new(
79                    cljrs_reader::form::FormKind::Symbol("require".into()),
80                    span(),
81                ),
82                cljrs_reader::Form::new(
83                    cljrs_reader::form::FormKind::Quote(Box::new(cljrs_reader::Form::new(
84                        cljrs_reader::form::FormKind::Symbol((*ns_name).into()),
85                        span(),
86                    ))),
87                    span(),
88                ),
89            ]),
90            span(),
91        );
92        if let Err(e) = eval(&require_form, env) {
93            eprintln!("[compiler-load warning] failed to load {ns_name}: {e:?}");
94            LOADING.store(false, std::sync::atomic::Ordering::Release);
95            return false;
96        }
97    }
98
99    globals
100        .compiler_ready
101        .store(true, std::sync::atomic::Ordering::Release);
102    LOADING.store(false, std::sync::atomic::Ordering::Release);
103    true
104}
105
106pub fn standard_env_minimal() -> Arc<GlobalEnv> {
107    cljrs_interp::standard_env_minimal(Some(eval), Some(apply::call_cljrs_fn), Some(eager_lower_fn))
108}
109
110pub fn standard_env() -> Arc<GlobalEnv> {
111    let globals = standard_env_minimal();
112    register_compiler_sources(&globals);
113    globals
114}
115
116pub fn standard_env_with_paths(source_paths: Vec<std::path::PathBuf>) -> Arc<GlobalEnv> {
117    let globals = standard_env();
118    globals.set_source_paths(source_paths);
119    globals
120}
121
122/// Load pre-built IR from a serialized bundle into the IR cache.
123///
124/// Walks all namespaces in the `GlobalEnv`, and for each function var whose
125/// arity matches a bundle key (`"ns/name:param_count"` or `"ns/name:param_count+"`
126/// for variadic), stores the pre-built IR in the cache keyed by the runtime
127/// `ir_arity_id`.
128///
129/// Returns the number of arities successfully loaded.
130pub fn load_prebuilt_ir(globals: &Arc<GlobalEnv>, bundle: &cljrs_ir::IrBundle) -> usize {
131    use cljrs_value::Value;
132
133    let ns_map = globals.namespaces.read().unwrap();
134    let mut loaded = 0usize;
135
136    for (ns_name, ns_ptr) in ns_map.iter() {
137        let interns = ns_ptr.get().interns.lock().unwrap();
138        for (var_name, var) in interns.iter() {
139            let val = var.get().deref().unwrap_or(Value::Nil);
140            let f = match &val {
141                Value::Fn(gc_fn) => gc_fn.get(),
142                _ => continue,
143            };
144            if f.is_macro {
145                continue;
146            }
147
148            for arity in &f.arities {
149                let key = if arity.rest_param.is_some() {
150                    format!("{ns_name}/{var_name}:{}+", arity.params.len())
151                } else {
152                    format!("{ns_name}/{var_name}:{}", arity.params.len())
153                };
154
155                if let Some(ir_func) = bundle.get(&key) {
156                    ir_cache::store_cached(arity.ir_arity_id, Arc::new(ir_func.clone()));
157                    loaded += 1;
158                }
159            }
160        }
161    }
162
163    loaded
164}