use std::collections::HashMap;
use object::read::{Object, ObjectSection, ObjectSymbol};
use object::{Architecture, RelocationFlags, SectionKind};
use crate::dispatch::KernelCif;
pub struct JitKernel {
_mmap: memmap2::MmapMut,
fn_ptr: *const (),
name: String,
var_names: Vec<String>,
cif: KernelCif,
}
unsafe impl Send for JitKernel {}
unsafe impl Sync for JitKernel {}
impl JitKernel {
pub fn compile(src: &str, name: &str, var_names: Vec<String>, buf_count: usize) -> crate::Result<Self> {
let obj = compile_to_object(src)?;
let (fn_ptr, mmap) = jit_load(&obj, name)?;
let cif = KernelCif::new(buf_count + var_names.len());
tracing::debug!(kernel.name = %name, "JIT kernel compiled and loaded");
Ok(Self { _mmap: mmap, fn_ptr, name: name.to_string(), var_names, cif })
}
pub unsafe fn execute_with_vals(&self, buffers: &[*mut u8], vals: &[i64]) -> crate::Result<()> {
unsafe { self.cif.dispatch(self.fn_ptr, buffers, vals, None) };
Ok(())
}
pub(crate) fn cif(&self) -> &KernelCif {
&self.cif
}
pub fn fn_ptr(&self) -> *const () {
self.fn_ptr
}
pub fn name(&self) -> &str {
&self.name
}
pub fn var_names(&self) -> &[String] {
&self.var_names
}
}
pub(crate) fn elf_target_triple() -> String {
let arch = std::env::consts::ARCH;
if arch == "powerpc64" && cfg!(target_endian = "little") {
"--target=powerpc64le-none-unknown-elf".to_string()
} else {
format!("--target={arch}-none-unknown-elf")
}
}
pub(crate) fn platform_clang_flags() -> &'static [&'static str] {
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
{
&["-ffixed-x18"]
}
#[cfg(not(all(target_arch = "aarch64", target_os = "macos")))]
{
&[]
}
}
fn compile_to_object(src: &str) -> crate::Result<Vec<u8>> {
use std::io::Write;
use std::process::{Command, Stdio};
let arch = std::env::consts::ARCH;
let march = match arch {
"x86_64" | "loongarch64" => "-march=native",
"riscv64" => "-march=rv64g",
_ => "-mcpu=native",
};
let target = elf_target_triple();
let mut args = vec![
"-c",
"-x",
"c",
"-O2",
march,
"-fPIC",
"-ffreestanding",
"-fno-math-errno",
"-fno-stack-protector",
"-nostdlib",
"-fno-ident",
];
args.push(&target);
args.extend_from_slice(platform_clang_flags());
args.extend_from_slice(&["-", "-o", "-"]);
let mut child = Command::new("clang")
.args(&args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| crate::Error::JitCompilation {
reason: format!("Failed to spawn clang: {e}. Is clang installed?"),
})?;
child
.stdin
.take()
.expect("stdin was piped")
.write_all(src.as_bytes())
.map_err(|e| crate::Error::JitCompilation { reason: format!("Failed to write to clang stdin: {e}") })?;
let output = child
.wait_with_output()
.map_err(|e| crate::Error::JitCompilation { reason: format!("Failed to wait for clang: {e}") })?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(crate::Error::JitCompilation {
reason: format!("clang compilation failed:\n{stderr}\nSource:\n{src}"),
});
}
if output.stdout.is_empty() {
return Err(crate::Error::JitCompilation { reason: "clang produced empty output".to_string() });
}
Ok(output.stdout)
}
pub(crate) fn jit_load(obj: &[u8], name: &str) -> crate::Result<(*const (), memmap2::MmapMut)> {
let elf = object::File::parse(obj)
.map_err(|e| crate::Error::JitCompilation { reason: format!("Failed to parse ELF: {e}") })?;
let arch = elf.architecture();
let mut section_offsets: HashMap<object::SectionIndex, usize> = HashMap::new();
let mut total_size: usize = 0;
for section in elf.sections() {
if matches!(section.kind(), SectionKind::Text | SectionKind::Data | SectionKind::ReadOnlyData) {
let align = section.align().max(1) as usize;
total_size = (total_size + align - 1) & !(align - 1);
section_offsets.insert(section.index(), total_size);
total_size += section.size() as usize;
}
}
if total_size == 0 {
return Err(crate::Error::JitCompilation { reason: "No loadable sections in ELF".to_string() });
}
let veneer_base = if arch == Architecture::Aarch64 {
let n = count_aarch64_external_calls(&elf, §ion_offsets);
let base = (total_size + 3) & !3; total_size = base + n * VENEER_SIZE;
base
} else {
total_size
};
let mut mmap = memmap2::MmapMut::map_anon(total_size)
.map_err(|e| crate::Error::JitCompilation { reason: format!("mmap failed: {e}") })?;
for section in elf.sections() {
if let Some(&offset) = section_offsets.get(§ion.index()) {
let data = section
.data()
.map_err(|e| crate::Error::JitCompilation { reason: format!("Failed to read section: {e}") })?;
mmap[offset..offset + data.len()].copy_from_slice(data);
}
}
let mmap_base = mmap.as_ptr() as u64;
let mut symbol_addrs: HashMap<object::SymbolIndex, u64> = HashMap::new();
for symbol in elf.symbols() {
if let Some(&sec_offset) = symbol.section_index().and_then(|si| section_offsets.get(&si)) {
symbol_addrs.insert(symbol.index(), mmap_base + sec_offset as u64 + symbol.address());
}
}
let mut state = RelocState { veneers: VeneerPool::new(veneer_base), ..Default::default() };
if arch == Architecture::PowerPc64 {
state.toc_base = elf.symbols().find(|s| s.name() == Ok(".TOC.")).and_then(|s| {
s.section_index().and_then(|si| section_offsets.get(&si)).map(|&off| mmap_base + off as u64 + s.address())
});
}
for section in elf.sections() {
if !matches!(section.kind(), SectionKind::Text | SectionKind::Data | SectionKind::ReadOnlyData) {
continue;
}
let Some(&sec_offset) = section_offsets.get(§ion.index()) else { continue };
for (reloc_offset, reloc) in section.relocations() {
let patch_mmap_offset = sec_offset + reloc_offset as usize;
let patch_addr = mmap_base + patch_mmap_offset as u64;
let target_addr = match reloc.target() {
object::RelocationTarget::Symbol(sym_idx) => {
if let Some(&addr) = symbol_addrs.get(&sym_idx) {
addr
} else {
let sym = elf
.symbol_by_index(sym_idx)
.map_err(|e| crate::Error::JitCompilation { reason: format!("Bad symbol index: {e}") })?;
let sym_name = sym
.name()
.map_err(|e| crate::Error::JitCompilation { reason: format!("Bad symbol name: {e}") })?;
let addr = resolve_symbol(sym_name)?;
symbol_addrs.insert(sym_idx, addr);
addr
}
}
object::RelocationTarget::Section(sec_idx) => section_offsets
.get(&sec_idx)
.map(|&off| mmap_base + off as u64)
.ok_or_else(|| crate::Error::JitCompilation {
reason: format!("Relocation references unloaded section {sec_idx:?}"),
})?,
other => {
return Err(crate::Error::JitCompilation {
reason: format!("Unsupported relocation target: {other:?}"),
});
}
};
let r_type = match reloc.flags() {
RelocationFlags::Elf { r_type } => r_type,
other => {
return Err(crate::Error::JitCompilation {
reason: format!("Non-ELF relocation format: {other:?}"),
});
}
};
apply_relocation(
&mut mmap,
patch_mmap_offset,
patch_addr,
target_addr,
reloc.addend(),
r_type,
arch,
&mut state,
)?;
}
}
let fn_offset = find_symbol_offset(&elf, name, §ion_offsets)?;
unsafe {
let ret = libc::mprotect(mmap.as_ptr() as *mut libc::c_void, mmap.len(), libc::PROT_READ | libc::PROT_EXEC);
if ret != 0 {
return Err(crate::Error::JitCompilation {
reason: format!("mprotect failed: {}", std::io::Error::last_os_error()),
});
}
}
#[cfg(not(target_arch = "x86_64"))]
unsafe {
unsafe extern "C" {
fn __clear_cache(start: *mut libc::c_void, end: *mut libc::c_void);
}
__clear_cache(mmap.as_ptr() as *mut _, mmap.as_ptr().add(mmap.len()) as *mut _);
}
Ok(((mmap_base + fn_offset as u64) as *const (), mmap))
}
#[derive(Default)]
struct RelocState {
pcrel_hi: HashMap<u64, i64>,
toc_base: Option<u64>,
veneers: VeneerPool,
}
#[derive(Default)]
struct VeneerPool {
next: usize,
map: HashMap<u64, usize>,
}
const VENEER_SIZE: usize = 16; const CALL26_MAX: i64 = (1 << 27) - 4;
impl VeneerPool {
fn new(base: usize) -> Self {
Self { next: base, map: HashMap::new() }
}
fn get_or_create(&mut self, mmap: &mut [u8], target_addr: u64) -> usize {
if let Some(&off) = self.map.get(&target_addr) {
return off;
}
let off = self.next;
self.next += VENEER_SIZE;
debug_assert!(self.next <= mmap.len(), "veneer pool overflow");
mmap[off..off + 4].copy_from_slice(&0x5800_0050u32.to_le_bytes());
mmap[off + 4..off + 8].copy_from_slice(&0xD61F_0200u32.to_le_bytes());
mmap[off + 8..off + 16].copy_from_slice(&target_addr.to_le_bytes());
self.map.insert(target_addr, off);
off
}
}
#[allow(clippy::too_many_arguments)]
fn apply_relocation(
mmap: &mut memmap2::MmapMut,
off: usize,
patch: u64,
target: u64,
addend: i64,
r_type: u32,
arch: Architecture,
state: &mut RelocState,
) -> crate::Result<()> {
match arch {
Architecture::X86_64 => reloc_x86_64(mmap, off, patch, target, addend, r_type),
Architecture::Aarch64 => reloc_aarch64(mmap, off, patch, target, addend, r_type, state),
Architecture::Riscv64 => reloc_riscv64(mmap, off, patch, target, addend, r_type, state),
Architecture::LoongArch64 => reloc_loongarch64(mmap, off, patch, target, addend, r_type),
Architecture::PowerPc64 => reloc_ppc64(mmap, off, patch, target, addend, r_type, state),
other => Err(unsupported_arch(other)),
}
}
fn reloc_x86_64(mmap: &mut [u8], off: usize, patch: u64, target: u64, addend: i64, r_type: u32) -> crate::Result<()> {
use object::elf::*;
match r_type {
R_X86_64_PC32 | R_X86_64_PLT32 | R_X86_64_GOTPCRELX | R_X86_64_REX_GOTPCRELX => {
let v = (target as i64 + addend - patch as i64) as i32;
mmap[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
R_X86_64_32S => {
let v = (target as i64 + addend) as i32;
mmap[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
R_X86_64_32 => {
let v = (target as i64 + addend) as u32;
mmap[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
R_X86_64_64 => {
let v = (target as i64 + addend) as u64;
mmap[off..off + 8].copy_from_slice(&v.to_le_bytes());
}
_ => return Err(unsupported_reloc("x86_64", r_type)),
}
Ok(())
}
fn reloc_aarch64(
mmap: &mut [u8],
off: usize,
patch: u64,
target: u64,
addend: i64,
r_type: u32,
state: &mut RelocState,
) -> crate::Result<()> {
use object::elf::*;
match r_type {
R_AARCH64_CALL26 | R_AARCH64_JUMP26 => {
let dest = (target as i64).wrapping_add(addend);
let offset = dest.wrapping_sub(patch as i64);
let final_offset = if !(-CALL26_MAX..=CALL26_MAX).contains(&offset) {
let mmap_base = patch - off as u64;
let veneer_off = state.veneers.get_or_create(mmap, dest as u64);
mmap_base as i64 + veneer_off as i64 - patch as i64
} else {
offset
};
let imm26 = (final_offset >> 2) as u32 & 0x03FF_FFFF;
patch_insn(mmap, off, 0xFC00_0000, imm26);
}
R_AARCH64_ADR_PREL_PG_HI21 => {
let page_delta = ((target as i64 + addend) & !0xFFF) - (patch as i64 & !0xFFF);
let imm = (page_delta >> 12) as u32;
patch_insn(mmap, off, 0x9F00_001F, ((imm & 0x3) << 29) | (((imm >> 2) & 0x7FFFF) << 5));
}
R_AARCH64_ADD_ABS_LO12_NC
| R_AARCH64_LDST8_ABS_LO12_NC
| R_AARCH64_LDST16_ABS_LO12_NC
| R_AARCH64_LDST32_ABS_LO12_NC
| R_AARCH64_LDST64_ABS_LO12_NC
| R_AARCH64_LDST128_ABS_LO12_NC => {
let shift = match r_type {
R_AARCH64_LDST16_ABS_LO12_NC => 1,
R_AARCH64_LDST32_ABS_LO12_NC => 2,
R_AARCH64_LDST64_ABS_LO12_NC => 3,
R_AARCH64_LDST128_ABS_LO12_NC => 4,
_ => 0,
};
let imm12 = (((target as i64 + addend) as u32) & 0xFFF) >> shift;
patch_insn(mmap, off, 0xFFC0_03FF, imm12 << 10);
}
_ => return Err(unsupported_reloc("aarch64", r_type)),
}
Ok(())
}
const RV_U_MASK: u32 = 0x0000_0FFF; const RV_I_MASK: u32 = 0x000F_FFFF; const RV_S_MASK: u32 = 0x01FF_F07F;
fn reloc_riscv64(
mmap: &mut [u8],
off: usize,
patch: u64,
target: u64,
addend: i64,
r_type: u32,
state: &mut RelocState,
) -> crate::Result<()> {
use object::elf::*;
match r_type {
R_RISCV_CALL | R_RISCV_CALL_PLT => {
let v = target as i64 + addend - patch as i64;
let hi = ((v + 0x800) >> 12) as u32;
let lo = (v as u32) & 0xFFF;
patch_insn(mmap, off, RV_U_MASK, hi << 12);
patch_insn(mmap, off + 4, RV_I_MASK, lo << 20);
}
R_RISCV_PCREL_HI20 => {
let v = target as i64 + addend - patch as i64;
let hi = ((v + 0x800) >> 12) as u32;
patch_insn(mmap, off, RV_U_MASK, hi << 12);
state.pcrel_hi.insert(patch, v);
}
R_RISCV_PCREL_LO12_I => {
let full = *state.pcrel_hi.get(&target).ok_or_else(|| crate::Error::JitCompilation {
reason: format!("PCREL_LO12_I: no paired HI20 at {target:#x}"),
})?;
patch_insn(mmap, off, RV_I_MASK, ((full as u32) & 0xFFF) << 20);
}
R_RISCV_PCREL_LO12_S => {
let full = *state.pcrel_hi.get(&target).ok_or_else(|| crate::Error::JitCompilation {
reason: format!("PCREL_LO12_S: no paired HI20 at {target:#x}"),
})?;
let lo = (full as u32) & 0xFFF;
patch_insn(mmap, off, RV_S_MASK, ((lo >> 5) << 25) | ((lo & 0x1F) << 7));
}
R_RISCV_HI20 => {
let v = (target as i64 + addend) as u32;
patch_insn(mmap, off, RV_U_MASK, v.wrapping_add(0x800) & 0xFFFF_F000);
}
R_RISCV_LO12_I => {
let lo = ((target as i64 + addend) as u32) & 0xFFF;
patch_insn(mmap, off, RV_I_MASK, lo << 20);
}
R_RISCV_LO12_S => {
let lo = ((target as i64 + addend) as u32) & 0xFFF;
patch_insn(mmap, off, RV_S_MASK, ((lo >> 5) << 25) | ((lo & 0x1F) << 7));
}
R_RISCV_BRANCH => {
let v = (target as i64 + addend - patch as i64) as u32;
let bits = ((v >> 12) & 1) << 31 | ((v >> 5) & 0x3F) << 25 | ((v >> 1) & 0xF) << 8 | ((v >> 11) & 1) << 7;
patch_insn(mmap, off, RV_S_MASK, bits);
}
R_RISCV_JAL => {
let v = (target as i64 + addend - patch as i64) as u32;
let bits =
((v >> 20) & 1) << 31 | ((v >> 1) & 0x3FF) << 21 | ((v >> 11) & 1) << 20 | ((v >> 12) & 0xFF) << 12;
patch_insn(mmap, off, RV_U_MASK, bits);
}
R_RISCV_64 => {
let v = (target as i64 + addend) as u64;
mmap[off..off + 8].copy_from_slice(&v.to_le_bytes());
}
R_RISCV_32 => {
let v = (target as i64 + addend) as u32;
mmap[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
R_RISCV_RELAX => {}
_ => return Err(unsupported_reloc("riscv64", r_type)),
}
Ok(())
}
fn reloc_loongarch64(
mmap: &mut [u8],
off: usize,
patch: u64,
target: u64,
addend: i64,
r_type: u32,
) -> crate::Result<()> {
use object::elf::*;
match r_type {
R_LARCH_B26 => {
let offs = ((target as i64 + addend - patch as i64) >> 2) as u32;
patch_insn(mmap, off, 0xFC00_0000, ((offs & 0xFFFF) << 10) | ((offs >> 16) & 0x3FF));
}
R_LARCH_PCALA_HI20 => {
let page_delta = ((target as i64 + addend + 0x800) >> 12) - (patch as i64 >> 12);
patch_insn(mmap, off, 0xFE00_001F, ((page_delta as u32) & 0xF_FFFF) << 5);
}
R_LARCH_PCALA_LO12 => {
let lo12 = ((target as i64 + addend) as u32) & 0xFFF;
patch_insn(mmap, off, 0xFFC0_03FF, lo12 << 10);
}
R_LARCH_64 => {
let v = (target as i64 + addend) as u64;
mmap[off..off + 8].copy_from_slice(&v.to_le_bytes());
}
R_LARCH_32 => {
let v = (target as i64 + addend) as u32;
mmap[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
R_LARCH_RELAX => {}
_ => return Err(unsupported_reloc("loongarch64", r_type)),
}
Ok(())
}
fn reloc_ppc64(
mmap: &mut [u8],
off: usize,
patch: u64,
target: u64,
addend: i64,
r_type: u32,
state: &mut RelocState,
) -> crate::Result<()> {
use object::elf::*;
match r_type {
R_PPC64_REL24 => {
let li = ((target as i64 + addend - patch as i64) >> 2) as u32 & 0x00FF_FFFF;
patch_insn(mmap, off, 0xFC00_0003, li << 2);
}
R_PPC64_TOC16_HA => {
let toc = toc_base(state)?;
let v = target as i64 + addend - toc as i64;
let ha = (((v >> 16) as u32).wrapping_add((v as u32 >> 15) & 1)) & 0xFFFF;
patch_insn(mmap, off, 0xFFFF_0000, ha);
}
R_PPC64_TOC16_LO => {
let toc = toc_base(state)?;
let lo = ((target as i64 + addend - toc as i64) as u32) & 0xFFFF;
patch_insn(mmap, off, 0xFFFF_0000, lo);
}
R_PPC64_TOC16_LO_DS => {
let toc = toc_base(state)?;
let lo = ((target as i64 + addend - toc as i64) as u32) & 0xFFFC;
patch_insn(mmap, off, 0xFFFF_0003, lo);
}
R_PPC64_REL32 => {
let v = (target as i64 + addend - patch as i64) as i32;
mmap[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
R_PPC64_ADDR64 => {
let v = (target as i64 + addend) as u64;
mmap[off..off + 8].copy_from_slice(&v.to_le_bytes());
}
R_PPC64_ADDR32 => {
let v = (target as i64 + addend) as u32;
mmap[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
R_PPC64_TOC16 | R_PPC64_TOC16_HI => {
let toc = toc_base(state)?;
let v = target as i64 + addend - toc as i64;
let bits = match r_type {
R_PPC64_TOC16 => (v as u32) & 0xFFFF,
R_PPC64_TOC16_HI => ((v >> 16) as u32) & 0xFFFF,
_ => unreachable!(),
};
patch_insn(mmap, off, 0xFFFF_0000, bits);
}
_ => return Err(unsupported_reloc("ppc64", r_type)),
}
Ok(())
}
fn toc_base(state: &RelocState) -> crate::Result<u64> {
state.toc_base.ok_or_else(|| crate::Error::JitCompilation {
reason: "PPC64 TOC relocation but no .TOC. symbol found in ELF".to_string(),
})
}
fn patch_insn(mmap: &mut [u8], off: usize, mask: u32, bits: u32) {
let insn = u32::from_le_bytes(mmap[off..off + 4].try_into().unwrap());
mmap[off..off + 4].copy_from_slice(&((insn & mask) | bits).to_le_bytes());
}
fn unsupported_reloc(arch: &str, r_type: u32) -> crate::Error {
crate::Error::JitCompilation { reason: format!("Unsupported {arch} relocation type: {r_type}") }
}
fn unsupported_arch(arch: Architecture) -> crate::Error {
crate::Error::JitCompilation { reason: format!("Unsupported ELF architecture: {arch:?}") }
}
fn find_symbol_offset(
elf: &object::File,
name: &str,
section_offsets: &HashMap<object::SectionIndex, usize>,
) -> crate::Result<usize> {
let prefixed = format!("_{name}");
for symbol in elf.symbols() {
let sym_name = symbol.name().unwrap_or("");
if (sym_name == name || sym_name == prefixed)
&& let Some(&sec_offset) = symbol.section_index().and_then(|si| section_offsets.get(&si))
{
return Ok(sec_offset + symbol.address() as usize);
}
}
Err(crate::Error::FunctionNotFound { name: name.to_string() })
}
fn count_aarch64_external_calls(elf: &object::File, section_offsets: &HashMap<object::SectionIndex, usize>) -> usize {
use std::collections::HashSet;
let mut external = HashSet::new();
for section in elf.sections() {
if !matches!(section.kind(), SectionKind::Text | SectionKind::Data | SectionKind::ReadOnlyData) {
continue;
}
for (_, reloc) in section.relocations() {
let r_type = match reloc.flags() {
RelocationFlags::Elf { r_type } => r_type,
_ => continue,
};
if r_type != object::elf::R_AARCH64_CALL26 && r_type != object::elf::R_AARCH64_JUMP26 {
continue;
}
if let object::RelocationTarget::Symbol(sym_idx) = reloc.target()
&& let Ok(sym) = elf.symbol_by_index(sym_idx)
&& sym.section_index().and_then(|si| section_offsets.get(&si)).is_none()
{
external.insert(sym_idx);
}
}
}
external.len()
}
fn resolve_symbol(name: &str) -> crate::Result<u64> {
let cname = std::ffi::CString::new(name)
.map_err(|e| crate::Error::JitCompilation { reason: format!("Invalid symbol name: {e}") })?;
let ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, cname.as_ptr()) };
if ptr.is_null() {
return Err(crate::Error::JitCompilation { reason: format!("Cannot resolve symbol: {name}") });
}
Ok(ptr as u64)
}
#[cfg(test)]
#[path = "test/unit/jit_loader.rs"]
mod tests;