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::{
fn_path_from_node_name, intern_names, intern_params, method_link_name, str_rt, CallEdge, EvalError,
Interp, Uncompilable,
};
use crate::compile::externs::{
is_native_lowered_primitive_method, is_rt_builtin, rt_extern_functions,
};
use crate::compile::symbols::{CompiledItem, 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 _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<&crate::compile::symbols::CompiledItem> = lib
.items
.iter()
.filter(|item| {
module.get_function(&item.symbol_name()).is_some_and(|f| f.count_basic_blocks() > 0)
})
.collect();
let internal_names: Vec<String> = items.iter().map(|item| item.symbol_name()).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))?;
for (item, cf) in items.iter().zip(compiled_fns) {
let def = match item {
crate::compile::symbols::CompiledItem::Fn(path) => interp.root.borrow().get_fn(path),
crate::compile::symbols::CompiledItem::Method(type_path, method) => {
interp.root.borrow().get_method(type_path, method)
}
}
.ok_or_else(|| {
format!("{}: `{}` is not registered — exec its SOURCE first", lib.label, item.node_name())
})?;
*def.compiled.borrow_mut() = Some(Rc::new(cf));
}
interp.printer_tables_changed();
Ok(())
}
pub(crate) fn fresh_module_with_declarations(name: &str, items: &[CompiledItem]) -> 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 = item.symbol_name();
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>>>,
name: &str,
internal_name: &str,
) -> Result<(), EvalError> {
let (params, body) = interp.compiled_fn_body(name)?;
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 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(())
}
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 (name, already_compiled) = match target {
CompileTarget::Fn(r) => {
interp.resolve_fn_ref(r).ok_or_else(|| {
EvalError::Internal(format!(
"compile: `{}` resolved at check time but not here",
r.written.join("::")
))
})?;
(r.resolved.to_string(), interp.root.borrow().fn_compiled(&r.resolved))
}
CompileTarget::Method { type_name, method, home } => {
interp.root.borrow().resolve_method(home, type_name, method).ok_or_else(|| {
EvalError::Internal(format!(
"compile: `{}` resolved at check time but not here",
method_link_name(type_name, method)
))
})?;
(method_link_name(type_name, method), interp.root.borrow().method_compiled(type_name, method))
}
};
if already_compiled {
return Ok(Value::Bool(true));
}
for scc in compute_sccs(interp, heap, &name)? {
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 = match target {
CompileTarget::Fn(r) => {
interp.resolve_fn_ref(r).ok_or_else(|| {
EvalError::Internal(format!(
"disassemble: `{}` resolved at check time but not here",
r.written.join("::")
))
})?;
r.resolved.to_string()
}
CompileTarget::Method { type_name, method, home } => {
interp.root.borrow().resolve_method(home, type_name, method).ok_or_else(|| {
EvalError::Internal(format!(
"disassemble: `{}` resolved at check time but not here",
method_link_name(type_name, method)
))
})?;
method_link_name(type_name, method)
}
};
if interp.fn_is_ffi(&name) {
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 = crate::compile::symbols::user_symbol_name(&name);
let edges = call_graph_edges(interp, heap, &name)?;
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 = match edge {
CallEdge::Fn(p) => crate::compile::symbols::user_symbol_name(&p.to_string()),
CallEdge::Method(t, m) => crate::compile::symbols::user_method_symbol_name(t, m),
};
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(), &name, &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, name: &str) -> Result<(), Uncompilable> {
match compute_sccs(interp, heap, name) {
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, name: &str) -> Result<Vec<CallEdge>, EvalError> {
let path = fn_path_from_node_name(name);
let method_key = interp.method_key(name);
let (_, body) = interp.compiled_fn_body(name)?;
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| *p != path && !is_rt_builtin(p))
.map(CallEdge::Fn),
);
let method_targets: Vec<(Path, String)> = targets
.methods
.into_iter()
.filter(|key| method_key.as_ref() != Some(key))
.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: name.to_string(),
target: method_link_name(type_name, method),
});
}
}
edges.extend(method_targets.into_iter().map(|(p, m)| CallEdge::Method(p, m)));
Ok(edges)
}
pub(crate) fn compute_sccs(interp: &Interp, heap: &Heap, name: &str) -> Result<Vec<Vec<String>>, EvalError> {
let mut counter = 0usize;
let mut indices: HashMap<String, usize> = HashMap::new();
let mut lowlink: HashMap<String, usize> = HashMap::new();
let mut on_stack: HashSet<String> = HashSet::new();
let mut stack: Vec<String> = Vec::new();
let mut sccs: Vec<Vec<String>> = Vec::new();
scc_strongconnect(interp, heap, name, &mut counter, &mut indices, &mut lowlink, &mut on_stack, &mut stack, &mut sccs)?;
Ok(sccs)
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
pub(crate) fn scc_strongconnect(
interp: &Interp,
heap: &Heap,
node: &str,
counter: &mut usize,
indices: &mut HashMap<String, usize>,
lowlink: &mut HashMap<String, usize>,
on_stack: &mut HashSet<String>,
stack: &mut Vec<String>,
sccs: &mut Vec<Vec<String>>,
) -> Result<(), EvalError> {
indices.insert(node.to_string(), *counter);
lowlink.insert(node.to_string(), *counter);
*counter += 1;
stack.push(node.to_string());
on_stack.insert(node.to_string());
for edge in call_graph_edges(interp, heap, node)? {
let already_compiled = match &edge {
CallEdge::Fn(p) => interp.root.borrow().fn_compiled(p),
CallEdge::Method(p, m) => interp.root.borrow().method_compiled(p, m),
};
if already_compiled {
continue;
}
let target = edge.node_name();
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.to_string(), merged);
} else if on_stack.contains(&target) {
let merged = lowlink[node].min(indices[&target]);
lowlink.insert(node.to_string(), 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: &[String]) -> Result<(), EvalError> {
emitted_layout_is_runnable().map_err(EvalError::Internal)?;
let member_set: HashSet<&str> = members.iter().map(|s| s.as_str()).collect();
let mut call_targets: Vec<Path> = Vec::new();
let mut method_targets: Vec<(Path, String)> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for member in members {
for edge in call_graph_edges(interp, heap, member)? {
let target_name = edge.node_name();
if member_set.contains(target_name.as_str()) || !seen.insert(target_name) {
continue;
}
match edge {
CallEdge::Fn(p) => call_targets.push(p),
CallEdge::Method(p, m) => method_targets.push((p, m)),
}
}
}
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 {
crate::compile::llvm_builtins::declare_external_compiled_function(&module, &crate::compile::symbols::user_symbol_name(member));
}
for target in &call_targets {
crate::compile::llvm_builtins::declare_external_compiled_function(&module, &crate::compile::symbols::user_symbol_name(&target.to_string()));
}
for (type_name, method) in &method_targets {
crate::compile::llvm_builtins::declare_external_compiled_function(&module, &crate::compile::symbols::user_method_symbol_name(type_name, method));
}
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, &crate::compile::symbols::user_symbol_name(member))?;
}
let _guard = crate::compile::COMPILE_LOCK.lock().unwrap();
let mut externals: Vec<(String, usize)> = call_targets
.iter()
.map(|p| {
let f = interp.root.borrow().get_fn(p).expect("crate::eval::interp::Interp::compute_sccs's finish order guarantees this is already compiled");
let addr = f.compiled.borrow().as_ref().expect("crate::eval::interp::Interp::compute_sccs's finish order guarantees this is already compiled").address();
(crate::compile::symbols::user_symbol_name(&p.to_string()), addr)
})
.collect();
externals.extend(method_targets.iter().map(|(type_name, method)| {
let f = interp.root.borrow().get_method(type_name, method).expect("crate::eval::interp::Interp::compute_sccs's finish order guarantees this is already compiled");
let addr = f.compiled.borrow().as_ref().expect("crate::eval::interp::Interp::compute_sccs's finish order guarantees this is already compiled").address();
(crate::compile::symbols::user_method_symbol_name(type_name, method), addr)
}));
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(|m| crate::compile::symbols::user_symbol_name(m)).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) {
match interp.method_key(member) {
Some((type_path, method)) => {
let f = interp.root.borrow().get_method(&type_path, &method).expect("member is a registered method");
*f.compiled.borrow_mut() = Some(Rc::new(compiled));
}
None => {
let f = interp.root.borrow().get_fn(&fn_path_from_node_name(member)).expect("member is a registered function");
*f.compiled.borrow_mut() = Some(Rc::new(compiled));
}
}
}
interp.publish_vtables();
interp.printer_tables_changed();
drop(module);
Ok(())
}