use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use inkwell::memory_buffer::MemoryBuffer;
use inkwell::module::Module;
use inkwell::AddressSpace;
use typelisp_mem::{Heap, RootScope, Value};
use crate::check::repr::Repr;
use crate::eval::interp::{intern_names, intern_params, str_rt, EvalError, Interp, Uncompilable};
use crate::compile::externs::{
is_native_lowered_primitive_method, is_rt_builtin, rt_extern_functions,
};
use crate::compile::symbols::{target_symbol_name, HASHTABLE_BUILTIN_METHODS};
use crate::types::{path_is_builtin, path_is_builtin_any, Path, LLVM_METHOD_RECEIVER_TYPES, NATIVE_LOWERED_PRIMITIVES};
use crate::CompileTarget;
pub fn emitted_layout_is_runnable() -> Result<(), String> {
if crate::compile::EMITTED_LAYOUT != typelisp_mem::tagged::LAYOUT {
return Err(format!(
"the loaded compiler island emits tag layout {} but this runtime is layout {} — \
a layout changeover in progress; only the island bootstrap may run in this state",
crate::compile::EMITTED_LAYOUT,
typelisp_mem::tagged::LAYOUT
));
}
Ok(())
}
pub fn install_compiled_library(interp: &Interp, lib: crate::compile::CompiledLibrary) -> Result<(), String> {
if lib.body_layout != typelisp_mem::tagged::LAYOUT {
return Err(format!(
"{}: its bodies were compiled under tag layout {} but this runtime is layout {} — \
a layout changeover in progress; regenerate the artifact (see compiler.rs's \
ISLAND_DUMP_BODY_LAYOUT)",
lib.label,
lib.body_layout,
typelisp_mem::tagged::LAYOUT
));
}
let wanted: Vec<String> = lib.items.iter().map(target_symbol_name).collect();
let installed = {
let mut cache = INSTALLED_LIBRARIES.lock().unwrap_or_else(|e| e.into_inner());
let hit = cache
.iter()
.find(|c| c.bitcode == lib.bitcode && c.body_abi == lib.body_abi && c.wanted == wanted)
.map(|c| c.bodies.iter().map(|(i, f)| (*i, f.share())).collect::<Vec<_>>());
match hit {
Some(bodies) => bodies,
None => {
let bodies = compile_library(&lib)?;
cache.push(InstalledLibrary {
bitcode: lib.bitcode.to_vec(),
body_abi: lib.body_abi,
wanted,
bodies: bodies.iter().map(|(i, f)| (*i, f.share())).collect(),
});
bodies
}
}
};
for (i, cf) in installed {
let item = &lib.items[i];
let def = interp.target_def(item).ok_or_else(|| {
format!("{}: `{}` is not registered — exec its SOURCE first", lib.label, item.link_name())
})?;
*def.compiled.borrow_mut() = Some(Rc::new(cf));
}
interp.printer_tables_changed();
Ok(())
}
struct InstalledLibrary {
bitcode: Vec<u8>,
body_abi: u8,
wanted: Vec<String>,
bodies: Vec<(usize, crate::compile::CompiledFn)>,
}
unsafe impl Send for InstalledLibrary {}
static INSTALLED_LIBRARIES: std::sync::Mutex<Vec<InstalledLibrary>> = std::sync::Mutex::new(Vec::new());
fn compile_library(lib: &crate::compile::CompiledLibrary) -> Result<Vec<(usize, crate::compile::CompiledFn)>, String> {
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
let buffer = MemoryBuffer::create_from_memory_range_copy(lib.bitcode, lib.label);
let module = Module::parse_bitcode_from_buffer(&buffer, crate::compile::llvm_context())
.map_err(|e| format!("{} bitcode failed to parse: {}", lib.label, e))?;
let items: Vec<usize> = (0..lib.items.len())
.filter(|&i| {
module.get_function(&target_symbol_name(&lib.items[i])).is_some_and(|f| f.count_basic_blocks() > 0)
})
.collect();
let internal_names: Vec<String> = items.iter().map(|&i| target_symbol_name(&lib.items[i])).collect();
let externals = crate::compile::runtime_externals(&module);
let compiled_fns = crate::compile::CompiledFn::new_multi(&module, &internal_names, &externals, lib.body_abi)
.map_err(|e| format!("{} JIT install failed: {}", lib.label, e))?;
Ok(items.into_iter().zip(compiled_fns).collect())
}
pub(crate) fn fresh_module_with_declarations(name: &str, items: &[CompileTarget]) -> Rc<RefCell<Module<'static>>> {
let ctx = crate::compile::llvm_context();
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
let module = ctx.create_module(name);
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let fn_ty = ctx.i64_type().fn_type(&[ptr_ty.into(), ctx.i32_type().into()], false);
for (name, _) in rt_extern_functions() {
module.add_function(name, fn_ty, None);
}
let lisp_fn_ty = if crate::compile::EMITTED_BODY_ABI == typelisp_abi::BODY_ABI_COROUTINE {
ctx.i64_type().fn_type(&[ctx.i64_type().into()], false)
} else {
fn_ty
};
for item in items {
let sym = target_symbol_name(item);
if module.get_function(&sym).is_none() {
module.add_function(&sym, lisp_fn_ty, None);
}
}
Rc::new(RefCell::new(module))
}
pub fn add_compiled_function(
interp: &Interp,
heap: &mut Heap,
module: Rc<RefCell<Module<'static>>>,
target: &CompileTarget,
internal_name: &str,
) -> Result<(), EvalError> {
let (params, body) = interp.compiled_fn_body(target)?;
translate_and_compile(interp, heap, module, ¶ms, &body, internal_name, &HashSet::new())
}
fn prepare_targets(interp: &Interp, heap: &mut Heap, body: &[Value]) -> Result<(), EvalError> {
let targets = crate::compile::core_bridge::collect_targets(heap, body).map_err(|e| EvalError::Panic(e.to_string()))?;
for target in &targets.globals {
interp.promote_global(heap, target)?;
}
for site in &targets.dyn_boxes {
interp.register_dyn_box(&site.concrete_key, &site.trait_path, &site.slots, &site.supers);
}
for to_trait in &targets.dyn_upcasts {
interp.trait_id_for(to_trait);
}
for trait_path in &targets.dyn_traits {
interp.dyn_dispatch_compiled.borrow_mut().insert(trait_path.clone());
}
Ok(())
}
pub fn translate_and_compile(
interp: &Interp,
heap: &mut Heap,
module: Rc<RefCell<Module<'static>>>,
params: &[(String, Repr)],
body: &[Value],
internal_name: &str,
extra_exclude_from_cell_names: &HashSet<String>,
) -> Result<(), EvalError> {
prepare_targets(interp, heap, body)?;
let defs = compile_definitions(interp);
let compiled_globals = interp.compiled_globals.borrow();
let vtable_ids = interp.vtable_ids.borrow();
let trait_ids = interp.trait_ids.borrow();
let dyn_tables = crate::compile::symbols::DynTables { vtables: &vtable_ids, trait_ids: &trait_ids };
let cx = crate::compile::core_bridge::Ctx::with_dyn_tables(&defs, &compiled_globals, dyn_tables);
let excluded = intern_names(heap, extra_exclude_from_cell_names);
let params = intern_params(heap, params);
let (param_list, body_sexpr) =
match crate::compile::core_bridge::function_parts(heap, ¶ms, body, cx, &excluded) {
Ok(v) => v,
Err(e) => return Err(EvalError::Panic(e.to_string())),
};
drop(compiled_globals);
drop(vtable_ids);
drop(trait_ids);
run_compile_function(interp, heap, module, internal_name, param_list, body_sexpr)
}
pub fn compile_definitions(interp: &Interp) -> crate::compile::core_bridge::Definitions {
let (structs, enums) = interp.root.borrow().collect_struct_and_enum_types();
let mut defs = crate::compile::core_bridge::Definitions::new();
for (path, _fields) in structs {
defs.record_struct(path);
}
for (path, _def) in enums {
defs.record_enum(path);
}
defs
}
pub fn run_compile_function(
interp: &Interp,
heap: &mut Heap,
module: Rc<RefCell<Module<'static>>>,
internal_name: &str,
param_list: Value,
body_sexpr: Value,
) -> Result<(), EvalError> {
crate::compile::install_llvm_backend();
let mut heap = RootScope::new(heap);
heap.push_root(param_list);
heap.push_root(body_sexpr);
let body_sexpr = optimize_ir(interp, &mut heap, body_sexpr)?;
heap.push_root(body_sexpr);
let heap = &mut *heap;
let compiler_path = Path::root("compile-function");
let argv = vec![
crate::compile::llvm_builtins::llvm_module_value_rc(module),
str_rt(heap, internal_name),
param_list,
body_sexpr,
];
let compiler_def = interp.root.borrow().get_fn(&compiler_path).ok_or_else(|| {
EvalError::Internal("compile: compiler body not loaded — call load_compiler first".into())
})?;
let compiled = compiler_def.compiled.borrow().clone();
if let Some(cf) = compiled {
let ret = Repr::Handle;
let mark = crate::compile::llvm_builtins::llvm_handles_mark();
let scope_mark = heap.session_root_count();
let param_reprs =
&compiler_def.sig.as_ref().expect("the compiler body always has a signature").0;
let r = interp.call_compiled(heap, cf.as_ref(), &argv, param_reprs, &ret);
crate::compile::llvm_builtins::llvm_handles_release(mark);
heap.truncate_session_roots(scope_mark);
r?;
return Ok(());
}
interp.enter(heap, &compiler_def, argv)?;
Ok(())
}
fn optimize_ir(interp: &Interp, heap: &mut Heap, body_sexpr: Value) -> Result<Value, EvalError> {
let level = crate::opt_level::opt_level();
if level == 0 {
return Ok(body_sexpr);
}
let def = interp.root.borrow().get_fn(&Path::root("optimize-ir")).ok_or_else(|| {
EvalError::Internal("compile: compiler body not loaded — call load_compiler first".into())
})?;
let argv = vec![Value::Int(level as i64), body_sexpr];
let compiled = def.compiled.borrow().clone();
match compiled {
Some(cf) => {
let (param_reprs, ret) = def.sig.as_ref().expect("the compiler body always has a signature");
interp.call_compiled(heap, cf.as_ref(), &argv, param_reprs, ret)
}
None => interp.enter(heap, &def, argv),
}
}
pub fn add_compiled_global_init(
interp: &Interp,
heap: &mut Heap,
module: Rc<RefCell<Module<'static>>>,
internal_name: &str,
form: Value,
) -> Result<(), EvalError> {
prepare_targets(interp, heap, std::slice::from_ref(&form))?;
let defs = compile_definitions(interp);
let compiled_globals = interp.compiled_globals.borrow();
let vtable_ids = interp.vtable_ids.borrow();
let trait_ids = interp.trait_ids.borrow();
let dyn_tables = crate::compile::symbols::DynTables { vtables: &vtable_ids, trait_ids: &trait_ids };
let cx = crate::compile::core_bridge::Ctx::with_dyn_tables(&defs, &compiled_globals, dyn_tables);
let body_sexpr = match crate::compile::core_bridge::global_init(heap, form, cx) {
Ok(Some(v)) => v,
Ok(None) => {
return Err(EvalError::Internal(
"compile: a global initializer was built from something that is not a `defvar`".into(),
))
}
Err(e) => return Err(EvalError::Panic(e.to_string())),
};
drop(compiled_globals);
drop(vtable_ids);
drop(trait_ids);
let mut s = RootScope::new(heap);
s.push_root(body_sexpr);
let param_list = Value::Empty;
run_compile_function(interp, &mut s, module, internal_name, param_list, body_sexpr)
}
pub fn compile_function(interp: &Interp, heap: &mut Heap, target: &CompileTarget) -> Result<Value, EvalError> {
let def = interp.target_def(target).ok_or_else(|| {
EvalError::Internal(format!("compile: `{}` resolved at check time but not here", target.link_name()))
})?;
let already_compiled = def.compiled.borrow().is_some();
if already_compiled {
return Ok(Value::Bool(true));
}
for scc in compute_sccs(interp, heap, target)? {
compile_scc(interp, heap, &scc)?;
}
Ok(Value::Bool(true))
}
pub fn disassemble_function(
interp: &Interp,
heap: &mut Heap,
target: &CompileTarget,
llvm_ir: bool,
) -> Result<String, EvalError> {
let name = target.link_name();
interp
.target_def(target)
.ok_or_else(|| EvalError::Internal(format!("disassemble: `{}` resolved at check time but not here", name)))?;
if interp.fn_is_ffi(target) {
return Err(EvalError::Panic(format!(
"disassemble: `{}` is a C function declared by `defffi`. What it reaches is the C \
function's own machine code, which this compiler did not produce and cannot show; \
the thunk in between is emitted by `crate::compile::ffi`.",
name
)));
}
let symbol = target_symbol_name(target);
let edges = call_graph_edges(interp, heap, target)?;
let module = {
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
let module = Rc::new(RefCell::new(crate::compile::llvm_context().create_module("disassemble")));
crate::compile::llvm_builtins::declare_external_compiled_function(&module, &symbol);
for edge in &edges {
let target_symbol = target_symbol_name(edge);
if target_symbol != symbol {
crate::compile::llvm_builtins::declare_external_compiled_function(&module, &target_symbol);
}
}
for (rt_name, _) in rt_extern_functions() {
crate::compile::llvm_builtins::declare_external_function(&module, rt_name);
}
module
};
add_compiled_function(interp, heap, module.clone(), target, &symbol)?;
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
let verified = crate::compile::verify_module_naming_functions(&module.borrow(), "disassemble: module");
if llvm_ir {
let text = module.borrow().print_to_string().to_string();
return Ok(match verified {
Ok(()) => text,
Err(e) => format!("{}\n; {}", text, e),
});
}
verified.map_err(EvalError::Panic)?;
let assembly = crate::compile::aot::assembly_of(&module.borrow());
assembly.map_err(EvalError::Panic)
}
pub(crate) fn precheck_compilable(interp: &Interp, heap: &Heap, target: &CompileTarget) -> Result<(), Uncompilable> {
match compute_sccs(interp, heap, target) {
Ok(_) => Ok(()),
Err(e) => Err(match e.into_kind() {
EvalError::NoSuchFunction(target) => Uncompilable::MissingTarget(target),
EvalError::Uncompilable { target, .. } => Uncompilable::MissingTarget(target),
other => Uncompilable::Other(other),
}),
}
}
pub(crate) fn call_graph_edges(interp: &Interp, heap: &Heap, node: &CompileTarget) -> Result<Vec<CompileTarget>, EvalError> {
let (_, body) = interp.compiled_fn_body(node)?;
let targets = match crate::compile::core_bridge::collect_targets(heap, &body) {
Ok(t) => t,
Err(e) => return Err(EvalError::Panic(e.to_string())),
};
let mut edges = Vec::new();
edges.extend(
targets
.calls
.into_iter()
.filter(|p| !matches!(node, CompileTarget::Fn(own) if own == p) && !is_rt_builtin(p))
.map(CompileTarget::Fn),
);
let method_targets: Vec<(Path, String)> = targets
.methods
.into_iter()
.filter(|(t, m)| !matches!(node, CompileTarget::Method(own_t, own_m) if own_t == t && own_m == m))
.filter(|key| {
if path_is_builtin(&key.0, "vector") && matches!(key.1.as_str(), "new" | "get" | "set" | "len" | "push" | "pop") {
return false;
}
if path_is_builtin(&key.0, "hashtable") && HASHTABLE_BUILTIN_METHODS.contains(&key.1.as_str()) {
return false;
}
if path_is_builtin_any(&key.0, &LLVM_METHOD_RECEIVER_TYPES) {
return false;
}
if key.0.is_simple()
&& (crate::compile::externs::rt_suspend_method_symbol(key.0.last_segment(), &key.1).is_some()
|| crate::compile::externs::rt_static_method_symbol(key.0.last_segment(), &key.1).is_some())
{
return false;
}
interp.root.borrow().has_method(&key.0, &key.1)
|| !path_is_builtin_any(&key.0, &NATIVE_LOWERED_PRIMITIVES)
|| !is_native_lowered_primitive_method(key.0.last_segment(), &key.1)
})
.collect();
for (type_name, method) in &method_targets {
if !interp.root.borrow().has_method(type_name, method) {
return Err(EvalError::Uncompilable {
caller: node.link_name(),
target: typelisp_mem::ConcreteNames::method_link(type_name, method),
});
}
}
edges.extend(method_targets.into_iter().map(|(p, m)| CompileTarget::Method(p, m)));
Ok(edges)
}
pub(crate) fn compute_sccs(interp: &Interp, heap: &Heap, root: &CompileTarget) -> Result<Vec<Vec<CompileTarget>>, EvalError> {
let mut counter = 0usize;
let mut indices: HashMap<CompileTarget, usize> = HashMap::new();
let mut lowlink: HashMap<CompileTarget, usize> = HashMap::new();
let mut on_stack: HashSet<CompileTarget> = HashSet::new();
let mut stack: Vec<CompileTarget> = Vec::new();
let mut sccs: Vec<Vec<CompileTarget>> = Vec::new();
scc_strongconnect(interp, heap, root, &mut counter, &mut indices, &mut lowlink, &mut on_stack, &mut stack, &mut sccs)?;
Ok(sccs)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn scc_strongconnect(
interp: &Interp,
heap: &Heap,
node: &CompileTarget,
counter: &mut usize,
indices: &mut HashMap<CompileTarget, usize>,
lowlink: &mut HashMap<CompileTarget, usize>,
on_stack: &mut HashSet<CompileTarget>,
stack: &mut Vec<CompileTarget>,
sccs: &mut Vec<Vec<CompileTarget>>,
) -> Result<(), EvalError> {
indices.insert(node.clone(), *counter);
lowlink.insert(node.clone(), *counter);
*counter += 1;
stack.push(node.clone());
on_stack.insert(node.clone());
for target in call_graph_edges(interp, heap, node)? {
if interp.is_compiled(&target) {
continue;
}
if !indices.contains_key(&target) {
scc_strongconnect(interp, heap, &target, counter, indices, lowlink, on_stack, stack, sccs)?;
let merged = lowlink[node].min(lowlink[&target]);
lowlink.insert(node.clone(), merged);
} else if on_stack.contains(&target) {
let merged = lowlink[node].min(indices[&target]);
lowlink.insert(node.clone(), merged);
}
}
if lowlink[node] == indices[node] {
let mut component = Vec::new();
loop {
let w = stack.pop().expect("node's own strongconnect frame pushed it onto the stack");
on_stack.remove(&w);
let is_root = w == *node;
component.push(w);
if is_root {
break;
}
}
sccs.push(component);
}
Ok(())
}
pub fn compile_scc(interp: &Interp, heap: &mut Heap, members: &[CompileTarget]) -> Result<(), EvalError> {
emitted_layout_is_runnable().map_err(EvalError::Internal)?;
let member_set: HashSet<&CompileTarget> = members.iter().collect();
let mut targets: Vec<CompileTarget> = Vec::new();
let mut seen: HashSet<CompileTarget> = HashSet::new();
for member in members {
for edge in call_graph_edges(interp, heap, member)? {
if member_set.contains(&edge) || !seen.insert(edge.clone()) {
continue;
}
targets.push(edge);
}
}
let module = {
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
let module = Rc::new(RefCell::new(crate::compile::llvm_context().create_module("compiled")));
for member in members.iter().chain(&targets) {
crate::compile::llvm_builtins::declare_external_compiled_function(&module, &target_symbol_name(member));
}
for (rt_name, _) in rt_extern_functions() {
crate::compile::llvm_builtins::declare_external_function(&module, rt_name);
}
module
};
for member in members {
add_compiled_function(interp, heap, module.clone(), member, &target_symbol_name(member))?;
}
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
let mut externals: Vec<(String, usize)> = targets
.iter()
.map(|t| {
let f = interp.target_def(t).expect("compute_sccs's finish order guarantees this is already compiled");
let addr = f.compiled.borrow().as_ref().expect("compute_sccs's finish order guarantees this is already compiled").address();
(target_symbol_name(t), addr)
})
.collect();
externals.extend(rt_extern_functions().iter().map(|(n, addr)| (n.to_string(), *addr)));
module.borrow().verify().map_err(|e| EvalError::Panic(format!("compile: module failed verification: {}", e)))?;
let internal_names: Vec<String> = members.iter().map(target_symbol_name).collect();
let compiled_fns = crate::compile::CompiledFn::new_multi(&module.borrow(), &internal_names, &externals, crate::compile::EMITTED_BODY_ABI)
.map_err(|e| EvalError::Panic(format!("compile: JIT failed: {}", e)))?;
for (member, compiled) in members.iter().zip(compiled_fns) {
let f = interp.target_def(member).expect("member is a registered definition");
*f.compiled.borrow_mut() = Some(Rc::new(compiled));
}
interp.publish_vtables();
interp.printer_tables_changed();
drop(module);
Ok(())
}