I have been experimenting with Cranelift, This is where I have got to. Mostly I think I understand what is going on. It seems to work as expected. The "scary" bit is taking the raw address of rustfunc and then having to declare the parameters and return type to match correctly. It seems there is no way in Rust to automate this (unless I am missing something). I was expecting to have to choose and declare an explicit calling convention, but it seems to work correctly without doing that. The other slight mystery is the settings, "use_colocated_libcalls" and "is_pic". I have no idea what "is_pic" means, or where it is documented. I copied these (and other bits of code) from GitHub - bytecodealliance/cranelift-jit-demo: JIT compiler and runtime for a toy language, using Cranelift
Edit: in view of the answer, I edited the code inserting extern "C" for the declaration of rustfunc, also on the declaration of the Cranelift function ( the transmute line ).
use cranelift::prelude::{
settings, AbiParam, Configurable, FunctionBuilder, FunctionBuilderContext, InstBuilder, Value,
}; // ABI = Application Binary Interface
use cranelift_jit::{JITBuilder, JITModule}; // JIT = Just In Time
use cranelift_module::{Linkage, Module};
extern "C" fn rustfunc(x: isize, y: isize) -> isize {
println!("Hello from rustfunc x={} y={}", x, y);
x - y
}
fn main() {
let mut jb: JITBuilder = {
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").unwrap();
flag_builder.set("is_pic", "false").unwrap();
/* isa = Instruction Set Architecture */
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
});
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.unwrap();
JITBuilder::with_isa(isa, cranelift_module::default_libcall_names())
};
jb.symbol("rustfunc", rustfunc as *const u8);
let mut module = JITModule::new(jb);
let int = module.target_config().pointer_type();
let mut ctx = module.make_context();
ctx.func.signature.params.push(AbiParam::new(int));
ctx.func.signature.returns.push(AbiParam::new(int));
let id = module
.declare_function("test", Linkage::Export, &ctx.func.signature)
.unwrap();
{
let mut fb_context = FunctionBuilderContext::new();
let mut fb = FunctionBuilder::new(&mut ctx.func, &mut fb_context);
let entry_block = fb.create_block();
fb.append_block_params_for_function_params(entry_block);
fb.switch_to_block(entry_block);
fb.seal_block(entry_block);
let x: Value = fb.block_params(entry_block)[0];
// rustfunc(x+60,60)
let result: Value = {
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(int));
sig.params.push(AbiParam::new(int));
sig.returns.push(AbiParam::new(int));
let callee = module
.declare_function("rustfunc", Linkage::Import, &sig)
.unwrap();
let local_callee = module.declare_func_in_func(callee, fb.func);
let mut arg_values = Vec::new();
let sixty: Value = fb.ins().iconst(int, 60);
let val = fb.ins().iadd(x, sixty);
arg_values.push(val);
arg_values.push(sixty);
let call = fb.ins().call(local_callee, &arg_values);
fb.inst_results(call)[0]
};
fb.ins().return_(&[result]);
fb.finalize();
}
module.define_function(id, &mut ctx).unwrap();
module.clear_context(&mut ctx);
module.finalize_definitions().unwrap();
let code: *const u8 = module.get_finalized_function(id);
let code = unsafe { core::mem::transmute::<_, extern "C" fn(isize) -> isize>(code) };
let x = 40;
let z = code(x);
println!("code({})={}", x, z);
assert_eq!(x, z);
}