pub fn runtime_function_names() -> Vec<&'static str> {
crate::compile::externs::rt_extern_functions().iter().map(|(name, _)| *name).collect()
}
pub mod aot;
pub mod bootstrap;
pub mod core_bridge;
pub mod driver;
pub mod dump;
pub mod externs;
pub mod ffi;
pub mod llvm_builtins;
pub fn install_llvm_backend() {
crate::eval::interp::set_backend(crate::eval::interp::Backend {
llvm_builtin: llvm_builtins::eval_llvm_builtin_method,
handle_is_live: |h| llvm_builtins::llvm_handle_get(h).is_some(),
compile_function: driver::compile_function,
disassemble_function: driver::disassemble_function,
compile_file: aot::compile_file,
dump_image: dump::dump_image,
define_ffi: ffi::define_ffi,
callback_entry: ffi::callback_entry,
});
}
pub mod prelude_bootstrap;
pub mod symbols;
pub struct CompiledLibrary<'a> {
pub label: &'a str,
pub bitcode: &'a [u8],
pub items: &'a [symbols::CompiledItem],
pub body_abi: u8,
pub body_layout: u8,
}
pub const EMITTED_BODY_ABI: u8 = crate::compiler::ISLAND_DUMP_EMITS_ABI;
pub const EMITTED_LAYOUT: u8 = crate::compiler::ISLAND_DUMP_EMITS_LAYOUT;
pub use typelisp_rt as runtime;
pub use typelisp_rt::coroutine;
pub const USER_SYMBOL_PREFIX: &str = "tl_";
use std::sync::{Mutex, OnceLock};
use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::module::Module;
use inkwell::OptimizationLevel;
pub static COMPILE_LOCK: Mutex<()> = Mutex::new(());
struct RetiredLlvm {
objects: Vec<Box<dyn std::any::Any>>,
}
unsafe impl Send for RetiredLlvm {}
static RETIRED_LLVM: Mutex<RetiredLlvm> = Mutex::new(RetiredLlvm { objects: Vec::new() });
pub(crate) fn retire_llvm<T: 'static>(obj: T) {
RETIRED_LLVM.lock().unwrap_or_else(|e| e.into_inner()).objects.push(Box::new(obj));
}
fn destroy_retired_llvm() {
if typelisp_rt::coroutine::live_chains() > 0 {
return;
}
let objects = std::mem::take(&mut RETIRED_LLVM.lock().unwrap_or_else(|e| e.into_inner()).objects);
drop(objects);
}
struct ContextCell(Context);
unsafe impl Sync for ContextCell {}
static LLVM_CONTEXT: OnceLock<ContextCell> = OnceLock::new();
pub(crate) fn verify_module_naming_functions(module: &inkwell::module::Module<'static>, label: &str) -> Result<(), String> {
let Err(e) = module.verify() else { return Ok(()) };
let mut bad = Vec::new();
let mut f = module.get_first_function();
while let Some(func) = f {
if func.count_basic_blocks() > 0 && !func.verify(false) {
bad.push(func.get_name().to_string_lossy().into_owned());
}
f = func.get_next_function();
}
if bad.is_empty() {
return Err(format!("{} failed verification: {}", label, e));
}
Err(format!("{} failed verification in {}: {}", label, bad.join(", "), e))
}
pub fn llvm_context() -> &'static Context {
&LLVM_CONTEXT.get_or_init(|| ContextCell(Context::create())).0
}
pub type CompiledSignature = unsafe extern "C-unwind" fn(*const i64, u32) -> i64;
pub struct CompiledFn {
engine: Option<ExecutionEngine<'static>>,
addr: usize,
body_abi: u8,
}
impl Drop for CompiledFn {
fn drop(&mut self) {
if let Some(engine) = self.engine.take() {
retire_llvm(engine);
}
}
}
impl CompiledFn {
pub fn new(
module: &Module<'static>,
fn_name: &str,
externals: &[(String, usize)],
body_abi: u8,
) -> Result<CompiledFn, String> {
destroy_retired_llvm();
let engine = module.create_jit_execution_engine(OptimizationLevel::None).map_err(|e| e.to_string())?;
for (name, addr) in externals {
let decl = module
.get_function(name)
.ok_or_else(|| format!("internal error: no forward declaration for \"{}\" in this module", name))?;
engine.add_global_mapping(&decl, *addr);
}
let addr = engine.get_function_address(fn_name).map_err(|e| e.to_string())?;
Ok(CompiledFn { engine: Some(engine), addr, body_abi })
}
pub fn new_multi(
module: &Module<'static>,
fn_names: &[String],
externals: &[(String, usize)],
body_abi: u8,
) -> Result<Vec<CompiledFn>, String> {
destroy_retired_llvm();
let engine = module.create_jit_execution_engine(OptimizationLevel::None).map_err(|e| e.to_string())?;
for (name, addr) in externals {
let decl = module
.get_function(name)
.ok_or_else(|| format!("internal error: no forward declaration for \"{}\" in this module", name))?;
engine.add_global_mapping(&decl, *addr);
}
fn_names
.iter()
.map(|fn_name| {
let addr = engine.get_function_address(fn_name).map_err(|e| e.to_string())?;
Ok(CompiledFn { engine: Some(engine.clone()), addr, body_abi })
})
.collect()
}
pub fn call(&self, args: &[i64]) -> i64 {
<Self as crate::eval::interp::CompiledBody>::call(self, args)
}
pub fn address(&self) -> usize {
self.addr
}
}
impl crate::eval::interp::CompiledBody for CompiledFn {
fn address(&self) -> usize {
self.addr
}
fn body_abi(&self) -> u8 {
self.body_abi
}
}
#[cfg(test)]
mod tests {
use inkwell::AddressSpace;
use super::{llvm_context, CompiledFn, COMPILE_LOCK};
use crate::compile::runtime::rt_ping;
#[test]
fn jit_can_call_an_rt_extern_function() {
let ctx = llvm_context();
let _guard = COMPILE_LOCK.lock().unwrap();
let module = ctx.create_module("jit_ping_test");
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let fn_ty = ctx.i64_type().fn_type(&[ptr_ty.into(), ctx.i32_type().into()], false);
let rt_ping_decl = module.add_function("rt_ping", fn_ty, None);
let caller = module.add_function("jit_ping_test", fn_ty, None);
let builder = ctx.create_builder();
let entry = ctx.append_basic_block(caller, "entry");
builder.position_at_end(entry);
let one_slot = builder.build_alloca(ctx.i64_type(), "one_slot").unwrap();
builder.build_store(one_slot, ctx.i64_type().const_int(41, false)).unwrap();
let argc_one = ctx.i32_type().const_int(1, false);
let call = builder.build_call(rt_ping_decl, &[one_slot.into(), argc_one.into()], "rt_ping_result").unwrap();
let result = match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_int_value(),
inkwell::values::ValueKind::Instruction(_) => panic!("rt_ping call produced no value"),
};
builder.build_return(Some(&result)).unwrap();
module.verify().expect("module failed verification");
let externals = vec![("rt_ping".to_string(), rt_ping as *const () as usize)];
let compiled = CompiledFn::new(&module, "jit_ping_test", &externals, typelisp_abi::BODY_ABI_CLASSIC)
.expect("CompiledFn::new failed");
assert_eq!(compiled.call(&[]), 42);
}
#[test]
fn jit_compiled_code_sees_the_heap_registered_by_set_active_heap() {
use crate::compile::runtime::{rt_heap_live_count, set_active_heap};
use crate::{Heap, Value};
let ctx = llvm_context();
let _guard = COMPILE_LOCK.lock().unwrap();
let module = ctx.create_module("jit_heap_test");
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let fn_ty = ctx.i64_type().fn_type(&[ptr_ty.into(), ctx.i32_type().into()], false);
let rt_heap_live_count_decl = module.add_function("rt_heap_live_count", fn_ty, None);
let caller = module.add_function("jit_heap_test", fn_ty, None);
let builder = ctx.create_builder();
let entry = ctx.append_basic_block(caller, "entry");
builder.position_at_end(entry);
let null_args = ptr_ty.const_null();
let argc_zero = ctx.i32_type().const_int(0, false);
let call = builder
.build_call(rt_heap_live_count_decl, &[null_args.into(), argc_zero.into()], "live_count")
.unwrap();
let result = match call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_int_value(),
inkwell::values::ValueKind::Instruction(_) => panic!("rt_heap_live_count call produced no value"),
};
builder.build_return(Some(&result)).unwrap();
module.verify().expect("module failed verification");
let mut heap = Heap::with_capacity(8);
heap.cons(Value::Int(1), Value::Empty).expect("cons failed");
heap.cons(Value::Int(2), Value::Empty).expect("cons failed");
set_active_heap(&mut heap as *mut Heap);
let externals = vec![("rt_heap_live_count".to_string(), rt_heap_live_count as *const () as usize)];
let compiled = CompiledFn::new(&module, "jit_heap_test", &externals, typelisp_abi::BODY_ABI_CLASSIC)
.expect("CompiledFn::new failed");
assert_eq!(compiled.call(&[]), 2);
}
#[test]
fn jit_compiled_code_round_trips_a_cons_through_rt_cons_rt_car_rt_cdr() {
use crate::compile::runtime::{rt_car, rt_cdr, rt_cons};
use crate::Heap;
let ctx = llvm_context();
let _guard = COMPILE_LOCK.lock().unwrap();
let module = ctx.create_module("jit_cons_test");
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i64_ty = ctx.i64_type();
let fn_ty = i64_ty.fn_type(&[ptr_ty.into(), ctx.i32_type().into()], false);
let rt_cons_decl = module.add_function("rt_cons", fn_ty, None);
let rt_car_decl = module.add_function("rt_car", fn_ty, None);
let rt_cdr_decl = module.add_function("rt_cdr", fn_ty, None);
let caller = module.add_function("jit_cons_test", fn_ty, None);
let builder = ctx.create_builder();
let entry = ctx.append_basic_block(caller, "entry");
builder.position_at_end(entry);
let tagged_one = i64_ty.const_int(1 << 3, false);
let tagged_two = i64_ty.const_int(2 << 3, false);
let cons_args = builder.build_alloca(i64_ty.array_type(2), "cons_args").unwrap();
let argc_zero32 = ctx.i32_type().const_int(0, false);
let slot0 = unsafe { builder.build_gep(i64_ty, cons_args, &[argc_zero32], "slot0").unwrap() };
builder.build_store(slot0, tagged_one).unwrap();
let slot1 = unsafe { builder.build_gep(i64_ty, cons_args, &[ctx.i32_type().const_int(1, false)], "slot1").unwrap() };
builder.build_store(slot1, tagged_two).unwrap();
let argc_two = ctx.i32_type().const_int(2, false);
let pair = builder.build_call(rt_cons_decl, &[cons_args.into(), argc_two.into()], "pair").unwrap();
let pair = match pair.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_int_value(),
inkwell::values::ValueKind::Instruction(_) => panic!("rt_cons call produced no value"),
};
let one_slot = builder.build_alloca(i64_ty, "one_slot").unwrap();
builder.build_store(one_slot, pair).unwrap();
let argc_one = ctx.i32_type().const_int(1, false);
let car_call = builder.build_call(rt_car_decl, &[one_slot.into(), argc_one.into()], "car_result").unwrap();
let car_result = match car_call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_int_value(),
inkwell::values::ValueKind::Instruction(_) => panic!("rt_car call produced no value"),
};
let cdr_call = builder.build_call(rt_cdr_decl, &[one_slot.into(), argc_one.into()], "cdr_result").unwrap();
let cdr_result = match cdr_call.try_as_basic_value() {
inkwell::values::ValueKind::Basic(v) => v.into_int_value(),
inkwell::values::ValueKind::Instruction(_) => panic!("rt_cdr call produced no value"),
};
let three = i64_ty.const_int(3, false);
let car_untagged = builder.build_right_shift(car_result, three, true, "car_untagged").unwrap();
let cdr_untagged = builder.build_right_shift(cdr_result, three, true, "cdr_untagged").unwrap();
let thousand = i64_ty.const_int(1000, false);
let combined =
builder.build_int_add(builder.build_int_mul(car_untagged, thousand, "car_scaled").unwrap(), cdr_untagged, "combined").unwrap();
builder.build_return(Some(&combined)).unwrap();
module.verify().expect("module failed verification");
let mut heap = Heap::with_capacity(8);
crate::compile::runtime::set_active_heap(&mut heap as *mut Heap);
let externals = vec![
("rt_cons".to_string(), rt_cons as *const () as usize),
("rt_car".to_string(), rt_car as *const () as usize),
("rt_cdr".to_string(), rt_cdr as *const () as usize),
];
let compiled = CompiledFn::new(&module, "jit_cons_test", &externals, typelisp_abi::BODY_ABI_CLASSIC)
.expect("CompiledFn::new failed");
assert_eq!(compiled.call(&[]), 1002);
assert_eq!(heap.live_count(), 1);
}
}