pub fn runtime_function_names() -> Vec<&'static str> {
crate::compile::externs::rt_extern_functions().iter().map(|(name, _)| *name).collect()
}
pub fn runtime_externals(module: &Module<'static>) -> Vec<(String, usize)> {
crate::compile::externs::rt_extern_functions()
.iter()
.filter(|(name, _)| module.get_function(name).is_some())
.map(|(name, addr)| (name.to_string(), *addr))
.collect()
}
pub fn jit_engine(module: &Module<'static>, externals: &[(String, usize)]) -> Result<orc::JitCode, String> {
orc::JitCode::new(module, externals)
}
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 mod orc;
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::{Arc, Mutex, OnceLock};
use inkwell::context::Context;
use inkwell::module::Module;
pub static COMPILE_LOCK: Mutex<()> = Mutex::new(());
static RETIRED_CODE: Mutex<Vec<Arc<orc::JitCode>>> = Mutex::new(Vec::new());
pub(crate) fn retire_llvm(code: Arc<orc::JitCode>) {
RETIRED_CODE.lock().unwrap_or_else(|e| e.into_inner()).push(code);
}
fn destroy_retired_llvm() {
if typelisp_rt::coroutine::live_chains() > 0 {
return;
}
let retired = std::mem::take(&mut *RETIRED_CODE.lock().unwrap_or_else(|e| e.into_inner()));
drop(retired);
}
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 {
code: Option<Arc<orc::JitCode>>,
addr: usize,
body_abi: u8,
}
impl Drop for CompiledFn {
fn drop(&mut self) {
if let Some(code) = self.code.take() {
retire_llvm(code);
}
}
}
impl CompiledFn {
pub fn new(
module: &Module<'static>,
fn_name: &str,
externals: &[(String, usize)],
body_abi: u8,
) -> Result<CompiledFn, String> {
Ok(Self::new_multi(module, &[fn_name.to_string()], externals, body_abi)?
.pop()
.expect("one name asked for, one function back"))
}
pub fn new_multi(
module: &Module<'static>,
fn_names: &[String],
externals: &[(String, usize)],
body_abi: u8,
) -> Result<Vec<CompiledFn>, String> {
destroy_retired_llvm();
let code = Arc::new(jit_engine(module, externals)?);
fn_names
.iter()
.map(|fn_name| {
let addr = code.address(fn_name)?;
Ok(CompiledFn { code: Some(code.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);
}
}