Hello everyone, I am doing a JIT with cranelift, and I am having problems transferring a structure from cranelift to rust. I want to know if the steps I follow are correct, I have this structure in rust:
#[repr(C)]
struct MyValue {
pub r#type: i32,
pub value: *const u8,
}
That's my basic structure. Later, from Cranelift, I call aligned_alloc to get aligned memory. Without moving from there, I set the value of the first field, and then by moving the necessary bytes for the second field, I insert the value of the second field there. So far, everything works fine; everything is correctly aligned and offset.
Using part of this example of how to call a function rust function from cranelift, I tried putting my struct as a parameter. So, this is how my function looks like:
pub extern "C" fn my_function(value: MyValue) {
// ...
}
And then, what I do is call that function from cranelift and pass the pointer returned by aligned_alloc after inserting the fields. According to me, it should be valid to be passed to a Rust structure, but it's not. What happens is that only the first field is added correctly, that is, the r#type field is added correctly. The second field seems to have a memory address value, and every time I check the value of the second field, it changes to high numbers and sometimes negative values. That's why I think it has a memory address. Why is this happening? Is there a step that I might be missing when handling structures in Rust?
In summary, the only thing I do in memory is move 0 bytes from the initial pointer, and there I insert the first value. After that, I move the necessary bytes to insert the second field there. I don't do anything else; is there something I might be overlooking that could be preventing Rust from adding the second field correctly?
Here is my code, much of the code was taken from the link I put above on how to call a function in rust, and what was not it was taken from there I added it with comments, this is exactly what I do and here you can see what happens:
use std::mem;
use cranelift::{
codegen::ir::{MemFlags, Type},
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};
#[repr(C)]
#[derive(Debug)]
pub struct MyValue {
pub r#type: i32,
pub value: i64,
}
pub extern "C" fn my_function(value: MyValue) {
println!("value = {:#?}", value);
}
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("my_function", my_function as *const u8);
let mut module = JITModule::new(jb);
let int = module.target_config().pointer_type();
let mut ctx = module.make_context();
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.switch_to_block(entry_block);
fb.seal_block(entry_block);
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(int));
let callee = module
.declare_function("my_function", Linkage::Import, &sig)
.unwrap();
// create MyValue
let ptr = aligned_alloc(&mut fb, &mut module, int);
// alloc the first field
let r#type = fb.ins().iconst(int, 10);
fb.ins().store(MemFlags::trusted(), r#type, ptr, 0);
// alloc the second field
let value = fb.ins().iconst(int, 20);
fb.ins().store(MemFlags::trusted(), value, ptr, 8);
// this should have MyValue
let my_value = fb.ins().load(int, MemFlags::trusted(), ptr, 0);
let local_callee = module.declare_func_in_func(callee, fb.func);
fb.ins().call(local_callee, &[my_value]);
fb.ins().return_(&[]);
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()>(code) };
code();
}
// calls aligned_alloc and returns the pointer to the allocated memory
// aligned_alloc is a C function that is defined in the C standard library
fn aligned_alloc(builder: &mut FunctionBuilder, module: &mut JITModule, int: Type) -> 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("aligned_alloc", Linkage::Import, &sig)
.expect("problem declaring aligned_alloc function");
let local_callee = module.declare_func_in_func(callee, builder.func);
let alignment = builder.ins().iconst(int, mem::align_of::<MyValue>() as i64);
let size = builder.ins().iconst(int, mem::size_of::<MyValue>() as i64);
let call = builder.ins().call(local_callee, &[alignment, size]);
builder.inst_results(call)[0]
}