Helpers for bridging Rust types to Cranelift JIT
signatures and call sites. The crate trims the boilerplate of declaring
external functions, building Signatures, and lowering arguments, while still
giving you direct access to the underlying FunctionBuilder and Module.
JitParam— type-level: how a Rust type expands into CraneliftAbiParams (e.g.&strbecomes(ptr, len)).JitArg— value-level: how a Rust value lowers into one or morecranelift_codegen::ir::Values. Implemented for already-loweredValues, integer/float constants, raw pointers, and&'static str/&'static [T].jit_signature!(&module; fn(T1, T2) -> R)— build aSignaturefrom Rust types.jit_call!(&mut bcx, ptr_ty, callee; arg1, arg2, ...)— emit acallinstruction, lowering each argument throughJitArg.define_function(...)/define_jit_fn!(...)— declare + define a function in one shot. Your closure receives theFunctionBuilder, theModule, and the entry-block params; whatever it returns is funneled throughIntoReturnsand emitted asreturn_.#[jit_export](proc-macro) — annotate a Rust function so it can be called from JITed IR. Generates a sibling<fn>_jitmodule withregister,signature,declare,try_declare, andcallhelpers, and auto-injectsextern "C"if no ABI is specified.<fn>_jit::try_declare— fallible variant ofdeclare, returningModuleResult<FuncId>so signature conflicts surface as errors instead of panics.JitArg for &'static T/&'static mut T— embed a host reference as a single pointer immediate. Preferred over raw pointers when you already have a real Rust reference; the'staticbound guarantees the pointee outlives every JIT invocation.disasmmodule (featuredisas) —define_function_with_disasmandformat_disassemblyproduce a side-by-side opcode / mnemonic dump of the compiled bytes via Capstone.simmodule (featuresim) —Simulator/SimResultwalk a finalizedFunctionagainst a flat byte buffer, with optional per-instruction trace. A debug aid, not a runtime — hostcalls are stubbed.JitNaiveDate/JitNaiveTime/JitNaiveDateTime(featurechrono) —JitParam/JitArgnewtypes forchrononaive date/time, lowering to scalar immediates.
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{default_libcall_names, Linkage, Module};
use lower_ir_utils::{define_jit_fn, jit_export};
#[jit_export]
fn double_i64(x: i64) -> i64 {
x.wrapping_mul(2)
}
fn build(module: &mut JITModule) {
let ext_id = double_i64_jit::declare(module);
let _wrap_id = define_jit_fn!(
module, "wrap", Linkage::Export, fn(i64) -> i64,
|bcx, module, params| {
double_i64_jit::call(bcx, module, ext_id, params[0])
},
).unwrap();
}See tests/jit_integration.rs and tests/define_function.rs for end-to-end
examples covering multi-value returns, &str arguments, and slice arguments.
For a #[jit_export] function whose Rust signature is a tuple,
<fn>_jit::call returns cranelift_codegen::ir::Inst, not an array of
Values. Pull the results out via bcx.inst_results(inst):
#[jit_export]
fn divmod(a: i64, b: i64) -> (i64, i64) {
(a / b, a % b)
}
let inst = divmod_jit::call(&mut bcx, &mut module, ext_id, a, b);
let results: Vec<_> = bcx.inst_results(inst).to_vec();
bcx.ins().return_(&results);The Inst shape is intentional: tuple elements with multi-lane JitParam
impls (&str, &[T], nested tuples) push more than one AbiParam each,
so the ABI return count and the syntactic tuple arity can diverge — a
fixed-arity [Value; N] shape would silently drop lanes. See
tests/jit_export.rs for a (&'static str, i64) regression case.
All optional features are off by default. Enable them with
cargo add lower-ir-utils --features disas,sim,chrono (or
cargo test --features disas,sim,chrono).
Pulls in capstone (the same disassembler Cranelift uses behind its own
disas feature) and exposes define_function_with_disasm plus the
lower-level format_disassembly. Works transparently on x86_64,
aarch64, riscv64, and s390x.
use cranelift_module::Linkage;
use lower_ir_utils::{define_function_with_disasm, jit_signature};
let (id, dump) = define_function_with_disasm(
&mut module,
"wrap",
Linkage::Export,
jit_signature!(&module; fn(i64) -> i64),
|_bcx, _module, params| params[0],
)?;
println!("{}", dump.text);
// 0x0000 48 89 f8 mov rax, rdi
// 0x0003 c3 retA small interpreter that walks a finalized Function block by block
against a flat Vec<u8> memory, then renders the trace, SSA register
table, memory hexdump, and return values. Covers scalar int/float
arithmetic, bitwise / shift ops, icmp / fcmp, select, load /
store, branching, and casts. Host call instructions are stubbed
(zero-valued results) — useful for sanity-checking the IR around a
call without leaving the simulator.
use lower_ir_utils::sim::{SimValue, Simulator};
let mut sim = Simulator::with_memory(vec![0; 256]);
sim.trace = true;
let result = sim.run(&func, &[SimValue::I64(10), SimValue::I64(32)]);
result.dump();Pulls in chrono (default features off) and exposes newtype wrappers in
lower_ir_utils::external::chrono (re-exported at the crate root as
JitNaiveDate, JitNaiveTime, JitNaiveDateTime). Each lowers to plain
iconst scalars — no host pointers — so non-'static values are fine.
JitNaiveTime uses two I32 lanes (seconds from midnight, then
nanoseconds) so leap-second encodings round-trip injectively.
use chrono::NaiveDate;
use lower_ir_utils::{jit_export, JitNaiveDate};
#[jit_export]
fn days_since_ce(d: i32) -> i32 {
d
}
// …declare `days_since_ce` in your module, then:
let date = NaiveDate::from_ymd_opt(2026, 5, 21).unwrap();
days_since_ce_jit::call(&mut bcx, &mut module, ext_id, JitNaiveDate(date));See tests/chrono_jit_integration.rs for end-to-end JIT coverage.
src/abi.rs—JitParam/JitArgtraits and impls.src/builder.rs—define_function+IntoReturns.src/macros.rs—jit_signature!,jit_call!,define_jit_fn!.src/disasm.rs—define_function_with_disasm,format_disassembly(featuredisas).src/sim.rs—Simulator,SimValue,SimResult(featuresim).src/external/— foreign-typeJitParam/JitArgwrappers (feature-gated;chronosubmodule today).macros/— proc-macro crate exporting#[jit_export].tests/— integration tests (jit_integration,define_function,abi_unit,jit_export,disasm,sim,chrono_*) plus anexternal_consumerworkspace.
cargo build
cargo test
cargo test --features disas,sim,chrono # exercises the optional modules
Targets Cranelift 0.131. Tested on x86_64 Linux (System V ABI). The &str /
&[T] impls assume the platform passes fat pointers as separate (ptr, len)
args; on platforms where that doesn't hold, prefer flat scalar params.
MIT — see LICENSE.