#[cfg(feature = "prebuild-ir")]
fn main() {
use std::path::PathBuf;
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
println!("cargo::rerun-if-changed=../cljrs-builtins/src/bootstrap.cljrs");
println!("cargo::rerun-if-changed=../cljrs-ir/src/cljrs/compiler/anf.cljrs");
println!("cargo::rerun-if-changed=../cljrs-ir/src/cljrs/compiler/ir.cljrs");
println!("cargo::rerun-if-changed=../cljrs-ir/src/cljrs/compiler/known.cljrs");
println!("cargo::rerun-if-changed=../cljrs-ir/src/cljrs/compiler/escape.cljrs");
println!("cargo::rerun-if-changed=../cljrs-ir/src/cljrs/compiler/optimize.cljrs");
let result = std::thread::Builder::new()
.name("ir-prebuild".to_string())
.stack_size(32 * 1024 * 1024)
.spawn(move || prebuild_all(&out_dir))
.expect("failed to spawn prebuild thread")
.join()
.expect("prebuild thread panicked");
match result {
Ok((core_count, compiler_count)) => {
eprintln!(
"cljrs-stdlib build.rs: pre-lowered {core_count} clojure.core arities \
and {compiler_count} compiler arities to IR"
);
}
Err(e) => {
eprintln!("cljrs-stdlib build.rs: IR pre-lowering failed: {e}");
eprintln!(
"cljrs-stdlib build.rs: writing empty bundles (runtime will use tree-walking)"
);
let empty_bytes = cljrs_ir::serialize_bundle(&cljrs_ir::IrBundle::new()).unwrap();
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
std::fs::write(out_dir.join("core_ir.bin"), &empty_bytes).unwrap();
std::fs::write(out_dir.join("compiler_ir.bin"), &empty_bytes).unwrap();
}
}
}
#[cfg(feature = "prebuild-ir")]
fn prebuild_all(out_dir: &std::path::Path) -> Result<(usize, usize), String> {
let globals = cljrs_interp::standard_env_minimal(None, None, None);
cljrs_eval::register_compiler_sources(&globals);
let mut env = cljrs_eval::Env::new(globals.clone(), "user");
if !cljrs_eval::ensure_compiler_loaded(&globals, &mut env) {
return Err("failed to load compiler namespaces".to_string());
}
let (core_count, core_fails, core_bundle) =
lower_ns_to_bundle("clojure.core", &globals, &mut env);
if core_fails > 0 {
eprintln!(
"cljrs-stdlib build.rs: {core_fails} clojure.core arities failed to lower+optimize"
);
}
let (region_insts, fns_with_regions) = count_region_insts(&core_bundle);
eprintln!(
"cljrs-stdlib build.rs: core bundle contains {region_insts} region instructions \
across {fns_with_regions} functions"
);
let core_bytes = cljrs_ir::serialize_bundle(&core_bundle)
.map_err(|e| format!("core bundle serialization failed: {e}"))?;
std::fs::write(out_dir.join("core_ir.bin"), &core_bytes)
.map_err(|e| format!("failed to write core_ir.bin: {e}"))?;
const COMPILER_NS: &[&str] = &[
"cljrs.compiler.ir",
"cljrs.compiler.known",
"cljrs.compiler.anf",
"cljrs.compiler.escape",
"cljrs.compiler.optimize",
];
let mut compiler_bundle = cljrs_ir::IrBundle::new();
let mut compiler_count = 0usize;
let mut compiler_fails = 0usize;
for ns_name in COMPILER_NS {
let (count, fails, bundle) = lower_ns_to_bundle(ns_name, &globals, &mut env);
compiler_count += count;
compiler_fails += fails;
for (key, ir_func) in bundle.functions {
compiler_bundle.insert(key, ir_func);
}
}
if compiler_fails > 0 {
eprintln!(
"cljrs-stdlib build.rs: {compiler_fails} compiler arities skipped \
(destructured params or unsupported forms)"
);
}
let (compiler_region_insts, compiler_fns_with_regions) = count_region_insts(&compiler_bundle);
eprintln!(
"cljrs-stdlib build.rs: compiler bundle contains {compiler_region_insts} region \
instructions across {compiler_fns_with_regions} functions"
);
let compiler_bytes = cljrs_ir::serialize_bundle(&compiler_bundle)
.map_err(|e| format!("compiler bundle serialization failed: {e}"))?;
std::fs::write(out_dir.join("compiler_ir.bin"), &compiler_bytes)
.map_err(|e| format!("failed to write compiler_ir.bin: {e}"))?;
Ok((core_count, compiler_count))
}
#[cfg(feature = "prebuild-ir")]
fn lower_ns_to_bundle(
ns_name: &str,
globals: &std::sync::Arc<cljrs_eval::GlobalEnv>,
env: &mut cljrs_eval::Env,
) -> (usize, usize, cljrs_ir::IrBundle) {
use std::sync::Arc;
let mut bundle = cljrs_ir::IrBundle::new();
let mut count = 0usize;
let mut fail_count = 0usize;
let var_entries: Vec<(Arc<str>, cljrs_value::Value)> = {
let ns_map = globals.namespaces.read().unwrap();
let Some(ns) = ns_map.get(ns_name) else {
eprintln!("cljrs-stdlib build.rs: namespace {ns_name} not found, skipping");
return (0, 0, bundle);
};
let interns = ns.get().interns.lock().unwrap();
interns
.iter()
.map(|(name, var)| {
let val = var.get().deref().unwrap_or(cljrs_value::Value::Nil);
(name.clone(), val)
})
.collect()
};
for (var_name, val) in &var_entries {
let f = match val {
cljrs_value::Value::Fn(gc_fn) => gc_fn.get().clone(),
_ => continue,
};
if f.is_macro {
continue;
}
let ns_arc: Arc<str> = Arc::from(ns_name);
for arity in &f.arities {
if !arity.destructure_params.is_empty() || arity.destructure_rest.is_some() {
fail_count += 1;
continue;
}
let key = if arity.rest_param.is_some() {
format!("{ns_name}/{var_name}:{}+", arity.params.len())
} else {
format!("{ns_name}/{var_name}:{}", arity.params.len())
};
match cljrs_eval::lower::lower_and_optimize_arity(
f.name.as_deref(),
&arity.params,
arity.rest_param.as_ref(),
&arity.body,
&ns_arc,
env,
) {
Ok(ir_func) => {
bundle.insert(key, ir_func);
count += 1;
}
Err(_) => {
fail_count += 1;
}
}
}
}
(count, fail_count, bundle)
}
#[cfg(feature = "prebuild-ir")]
fn count_region_insts(bundle: &cljrs_ir::IrBundle) -> (usize, usize) {
let mut region_inst_count = 0usize;
let mut fns_with_regions = 0usize;
for ir_func in bundle.functions.values() {
let mut had_region = false;
for block in &ir_func.blocks {
for inst in &block.insts {
if matches!(
inst,
cljrs_ir::Inst::RegionStart(..)
| cljrs_ir::Inst::RegionAlloc(..)
| cljrs_ir::Inst::RegionEnd(..)
) {
region_inst_count += 1;
had_region = true;
}
}
}
if had_region {
fns_with_regions += 1;
}
}
(region_inst_count, fns_with_regions)
}
#[cfg(not(feature = "prebuild-ir"))]
fn main() {
println!("cargo::rerun-if-changed=build.rs");
}