use std::collections::HashMap;
use std::ffi::{c_char, c_void, CStr, CString};
use std::sync::{Condvar, Mutex, OnceLock};
use inkwell::llvm_sys;
use inkwell::module::Module;
use inkwell::targets::{CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetData, TargetMachine, TargetTriple};
use inkwell::OptimizationLevel;
use llvm_sys::error::{LLVMDisposeErrorMessage, LLVMErrorRef, LLVMGetErrorMessage};
use llvm_sys::orc2::lljit::{
LLVMOrcCreateLLJIT, LLVMOrcCreateLLJITBuilder, LLVMOrcLLJITAddObjectFile, LLVMOrcLLJITGetDataLayoutStr,
LLVMOrcLLJITGetExecutionSession, LLVMOrcLLJITGetTripleString, LLVMOrcLLJITMangleAndIntern, LLVMOrcLLJITRef,
};
use llvm_sys::orc2::{
LLVMJITEvaluatedSymbol, LLVMJITSymbolFlags, LLVMJITSymbolGenericFlags, LLVMOrcAbsoluteSymbols,
LLVMOrcCJITDylibSearchOrderElement, LLVMOrcCLookupSetElement, LLVMOrcCSymbolMapPair, LLVMOrcCSymbolMapPairs,
LLVMOrcDisposeMaterializationUnit, LLVMOrcExecutionSessionCreateBareJITDylib, LLVMOrcExecutionSessionLookup,
LLVMOrcExecutionSessionSetErrorReporter, LLVMOrcJITDylibClear, LLVMOrcJITDylibDefine, LLVMOrcJITDylibLookupFlags,
LLVMOrcJITDylibRef, LLVMOrcLookupKind, LLVMOrcReleaseSymbolStringPoolEntry, LLVMOrcSymbolLookupFlags,
LLVMOrcSymbolStringPoolEntryRef,
};
const RUST_EH_PERSONALITY: &str = "rust_eh_personality";
extern "C" {
fn rust_eh_personality();
fn _Unwind_Resume();
fn floor(x: f64) -> f64;
fn ceil(x: f64) -> f64;
fn trunc(x: f64) -> f64;
fn round(x: f64) -> f64;
fn sqrt(x: f64) -> f64;
fn sin(x: f64) -> f64;
fn cos(x: f64) -> f64;
fn exp(x: f64) -> f64;
fn log(x: f64) -> f64;
fn pow(x: f64, y: f64) -> f64;
fn fmax(x: f64, y: f64) -> f64;
fn fmin(x: f64, y: f64) -> f64;
fn fmod(x: f64, y: f64) -> f64;
}
fn codegen_helpers() -> [(&'static str, usize); 15] {
[
(RUST_EH_PERSONALITY, rust_eh_personality as *const () as usize),
("_Unwind_Resume", _Unwind_Resume as *const () as usize),
("floor", floor as *const () as usize),
("ceil", ceil as *const () as usize),
("trunc", trunc as *const () as usize),
("round", round as *const () as usize),
("sqrt", sqrt as *const () as usize),
("sin", sin as *const () as usize),
("cos", cos as *const () as usize),
("exp", exp as *const () as usize),
("log", log as *const () as usize),
("pow", pow as *const () as usize),
("fmax", fmax as *const () as usize),
("fmin", fmin as *const () as usize),
("fmod", fmod as *const () as usize),
]
}
struct Jit {
jit: LLVMOrcLLJITRef,
machine: TargetMachine,
layout: String,
triple: TargetTriple,
mach_o: bool,
}
unsafe impl Send for Jit {}
unsafe impl Sync for Jit {}
static JIT: OnceLock<Result<Jit, String>> = OnceLock::new();
static REPORTED: Mutex<Vec<String>> = Mutex::new(Vec::new());
struct FreeDylibs(Vec<LLVMOrcJITDylibRef>);
unsafe impl Send for FreeDylibs {}
static FREE_DYLIBS: Mutex<FreeDylibs> = Mutex::new(FreeDylibs(Vec::new()));
fn take_error(err: LLVMErrorRef) -> Result<(), String> {
if err.is_null() {
return Ok(());
}
unsafe {
let message = LLVMGetErrorMessage(err);
let text = CStr::from_ptr(message).to_string_lossy().into_owned();
LLVMDisposeErrorMessage(message);
Err(text)
}
}
extern "C" fn report_error(_ctx: *mut c_void, err: LLVMErrorRef) {
if let Err(text) = take_error(err) {
REPORTED.lock().unwrap_or_else(|e| e.into_inner()).push(text);
}
}
fn reported_errors() -> Result<(), String> {
let reported = std::mem::take(&mut *REPORTED.lock().unwrap_or_else(|e| e.into_inner()));
if reported.is_empty() {
Ok(())
} else {
Err(reported.join("; "))
}
}
fn jit() -> Result<&'static Jit, String> {
JIT.get_or_init(|| {
Target::initialize_native(&InitializationConfig::default())
.map_err(|e| format!("failed to initialize the native target: {}", e))?;
let mut jit = std::ptr::null_mut();
unsafe {
take_error(LLVMOrcCreateLLJIT(&mut jit, LLVMOrcCreateLLJITBuilder()))
.map_err(|e| format!("failed to create the JIT: {}", e))?;
LLVMOrcExecutionSessionSetErrorReporter(LLVMOrcLLJITGetExecutionSession(jit), report_error, std::ptr::null_mut());
}
let (triple, layout) = unsafe {
(
CStr::from_ptr(LLVMOrcLLJITGetTripleString(jit)).to_string_lossy().into_owned(),
CStr::from_ptr(LLVMOrcLLJITGetDataLayoutStr(jit)).to_string_lossy().into_owned(),
)
};
let triple = TargetTriple::create(&triple);
let target = Target::from_triple(&triple).map_err(|e| e.to_string())?;
let machine = target
.create_target_machine(&triple, "", "", OptimizationLevel::None, RelocMode::PIC, CodeModel::Default)
.ok_or_else(|| format!("failed to create a target machine for `{}`", triple))?;
let ours = machine.get_target_data().get_data_layout().as_str().to_string_lossy().into_owned();
if ours != layout {
return Err(format!(
"the target machine for `{}` lays data out as `{}`, the JIT as `{}`",
triple, ours, layout
));
}
let mach_o = triple.as_str().to_string_lossy().contains("-apple-");
Ok(Jit { jit, machine, layout, triple, mach_o })
})
.as_ref()
.map_err(|e| e.clone())
}
impl Jit {
fn intern(&self, name: &str) -> Result<LLVMOrcSymbolStringPoolEntryRef, String> {
let name = CString::new(name).map_err(|_| format!("symbol name `{}` contains a NUL", name))?;
Ok(unsafe { LLVMOrcLLJITMangleAndIntern(self.jit, name.as_ptr()) })
}
fn dylib(&self) -> LLVMOrcJITDylibRef {
if let Some(dylib) = FREE_DYLIBS.lock().unwrap_or_else(|e| e.into_inner()).0.pop() {
return dylib;
}
static NEXT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
let name = format!("typelisp-jit-{}", NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
let name = CString::new(name).expect("no NUL in a formatted number");
unsafe { LLVMOrcExecutionSessionCreateBareJITDylib(LLVMOrcLLJITGetExecutionSession(self.jit), name.as_ptr()) }
}
fn define(&self, dylib: LLVMOrcJITDylibRef, symbols: &[(&str, usize)]) -> Result<(), String> {
let flags = LLVMJITSymbolGenericFlags::LLVMJITSymbolGenericFlagsExported as u8
| LLVMJITSymbolGenericFlags::LLVMJITSymbolGenericFlagsCallable as u8;
let mut pairs = Vec::with_capacity(symbols.len());
for (name, addr) in symbols {
pairs.push(LLVMOrcCSymbolMapPair {
Name: self.intern(name)?,
Sym: LLVMJITEvaluatedSymbol {
Address: *addr as u64,
Flags: LLVMJITSymbolFlags { GenericFlags: flags, TargetFlags: 0 },
},
});
}
unsafe {
let unit = LLVMOrcAbsoluteSymbols(pairs.as_mut_ptr(), pairs.len());
if let Err(e) = take_error(LLVMOrcJITDylibDefine(dylib, unit)) {
LLVMOrcDisposeMaterializationUnit(unit);
return Err(e);
}
}
Ok(())
}
fn lookup(&self, dylib: LLVMOrcJITDylibRef, names: &[String]) -> Result<HashMap<String, usize>, String> {
struct Answer {
result: Mutex<Option<Result<Vec<(LLVMOrcSymbolStringPoolEntryRef, u64)>, String>>>,
ready: Condvar,
}
extern "C" fn handle(err: LLVMErrorRef, pairs: LLVMOrcCSymbolMapPairs, n: usize, ctx: *mut c_void) {
let answer = unsafe { &*(ctx as *const Answer) };
let out = take_error(err).map(|()| {
(0..n).map(|i| unsafe { ((*pairs.add(i)).Name, (*pairs.add(i)).Sym.Address) }).collect::<Vec<_>>()
});
*answer.result.lock().unwrap_or_else(|e| e.into_inner()) = Some(out);
answer.ready.notify_all();
}
let mut interned = Vec::with_capacity(names.len());
for name in names {
interned.push(self.intern(name)?);
}
let release = |interned: &[LLVMOrcSymbolStringPoolEntryRef]| {
for entry in interned {
unsafe { LLVMOrcReleaseSymbolStringPoolEntry(*entry) };
}
};
let mut set: Vec<LLVMOrcCLookupSetElement> = interned
.iter()
.map(|entry| LLVMOrcCLookupSetElement {
Name: *entry,
LookupFlags: LLVMOrcSymbolLookupFlags::LLVMOrcSymbolLookupFlagsRequiredSymbol,
})
.collect();
let mut order = [LLVMOrcCJITDylibSearchOrderElement {
JD: dylib,
JDLookupFlags: LLVMOrcJITDylibLookupFlags::LLVMOrcJITDylibLookupFlagsMatchAllSymbols,
}];
let answer = Answer { result: Mutex::new(None), ready: Condvar::new() };
unsafe {
LLVMOrcExecutionSessionLookup(
LLVMOrcLLJITGetExecutionSession(self.jit),
LLVMOrcLookupKind::LLVMOrcLookupKindStatic,
order.as_mut_ptr(),
order.len(),
set.as_mut_ptr(),
set.len(),
handle,
&answer as *const Answer as *mut c_void,
);
}
let mut slot = answer.result.lock().unwrap_or_else(|e| e.into_inner());
while slot.is_none() {
slot = answer.ready.wait(slot).unwrap_or_else(|e| e.into_inner());
}
let found = slot.take().expect("the loop above waits for a result");
drop(slot);
let found = match found {
Ok(found) => found,
Err(e) => {
release(&interned);
return Err(e);
}
};
let mut addresses = HashMap::with_capacity(names.len());
for (name, entry) in names.iter().zip(&interned) {
match found.iter().find(|(n, _)| n == entry) {
Some((_, addr)) => {
addresses.insert(name.clone(), *addr as usize);
}
None => {
release(&interned);
return Err(format!("the lookup of `{}` returned no address", name));
}
}
}
release(&interned);
Ok(addresses)
}
}
const UNWIND_TABLE_END: &str = "typelisp.unwind_table_end";
const UNWIND_TABLE_END_CALLEE: &str = "typelisp.unwind_table_end.callee";
fn end_unwind_table(module: &Module<'static>) -> Result<(), String> {
if module.get_function(UNWIND_TABLE_END).is_some() || module.get_function(UNWIND_TABLE_END_CALLEE).is_some() {
return Err(format!("internal error: the module was already JIT-compiled (it has `{}`)", UNWIND_TABLE_END));
}
let ctx = module.get_context();
let void_fn = ctx.void_type().fn_type(&[], false);
let personality = match module.get_function(RUST_EH_PERSONALITY) {
Some(f) => f,
None => module.add_function(RUST_EH_PERSONALITY, ctx.i32_type().fn_type(&[], true), None),
};
let callee = module.add_function(UNWIND_TABLE_END_CALLEE, void_fn, None);
let end = module.add_function(UNWIND_TABLE_END, void_fn, None);
end.set_personality_function(personality);
let builder = ctx.create_builder();
builder.position_at_end(ctx.append_basic_block(callee, "entry"));
builder.build_return(None).map_err(|e| e.to_string())?;
let entry = ctx.append_basic_block(end, "entry");
let normal = ctx.append_basic_block(end, "normal");
let pad = ctx.append_basic_block(end, "pad");
builder.position_at_end(entry);
builder.build_invoke(callee, &[], normal, pad, "").map_err(|e| e.to_string())?;
builder.position_at_end(normal);
builder.build_return(None).map_err(|e| e.to_string())?;
builder.position_at_end(pad);
let ptr = ctx.ptr_type(inkwell::AddressSpace::default());
let landing = builder
.build_landing_pad(ctx.struct_type(&[ptr.into(), ctx.i32_type().into()], false), personality, &[], true, "")
.map_err(|e| e.to_string())?;
builder.build_resume(landing).map_err(|e| e.to_string())?;
Ok(())
}
pub struct JitCode {
dylib: LLVMOrcJITDylibRef,
addresses: HashMap<String, usize>,
}
unsafe impl Send for JitCode {}
unsafe impl Sync for JitCode {}
impl JitCode {
pub fn new(module: &Module<'static>, externals: &[(String, usize)]) -> Result<JitCode, String> {
reported_errors()?;
let jit = jit()?;
module.set_triple(&jit.triple);
module.set_data_layout(&TargetData::create(&jit.layout).get_data_layout());
if jit.mach_o {
end_unwind_table(module)?;
}
let object = jit.machine.write_to_memory_buffer(module, FileType::Object).map_err(|e| e.to_string())?;
let defined: Vec<String> = module
.get_functions()
.filter(|f| f.count_basic_blocks() > 0)
.map(|f| f.get_name().to_string_lossy().into_owned())
.collect();
let mut symbols: Vec<(&str, usize)> = externals.iter().map(|(name, addr)| (name.as_str(), *addr)).collect();
for (name, addr) in codegen_helpers() {
if !symbols.iter().any(|(n, _)| *n == name) {
symbols.push((name, addr));
}
}
let mut code = JitCode { dylib: jit.dylib(), addresses: HashMap::new() };
let dylib = code.dylib;
if !symbols.is_empty() {
jit.define(dylib, &symbols)?;
}
let bytes = object.as_slice();
unsafe {
let copy = llvm_sys::core::LLVMCreateMemoryBufferWithMemoryRangeCopy(
bytes.as_ptr() as *const c_char,
bytes.len(),
c"typelisp-jit-object".as_ptr(),
);
take_error(LLVMOrcLLJITAddObjectFile(jit.jit, dylib, copy))?;
}
if !defined.is_empty() {
code.addresses = jit.lookup(dylib, &defined).map_err(|e| match reported_errors() {
Ok(()) => e,
Err(reported) => format!("{}; {}", reported, e),
})?;
}
reported_errors()?;
Ok(code)
}
pub fn address(&self, name: &str) -> Result<usize, String> {
self.addresses
.get(name)
.copied()
.ok_or_else(|| format!("`{}` is not a function this module defined", name))
}
}
impl Drop for JitCode {
fn drop(&mut self) {
match take_error(unsafe { LLVMOrcJITDylibClear(self.dylib) }) {
Ok(()) => FREE_DYLIBS.lock().unwrap_or_else(|e| e.into_inner()).0.push(self.dylib),
Err(e) => REPORTED
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(format!("clearing a JIT dylib failed: {}", e)),
}
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
pub fn unsupported_host_warning() -> Option<&'static str> {
let release = typelisp_rt::os::os_release().expect("uname reports a release");
let darwin: u32 = release
.split('.')
.next()
.and_then(|major| major.parse().ok())
.unwrap_or_else(|| panic!("uname's release `{}` does not start with a number", release));
(darwin < 25).then_some(
"warning: on Apple Silicon, typelisp needs macOS 26 or later; on this macOS, a panic, throw or unwind-protect in JIT-compiled code can crash",
)
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
pub fn unsupported_host_warning() -> Option<&'static str> {
None
}