I am trying to call a Rust function that takes in a struct from within cranelift, but after I finish generating the IR and setting everything up calling finalize_definitions
my program panics with the message assertion failed: (diff >> 26 == -1) || (diff >> 26 == 0
The program only panics when running on my M1 Mac Laptop, when I run it on my x86_64 linux machine everything is as I expect it to be.
The struct
#[repr(C)]
struct MyS {
x: i64,
y: i64,
z: i64,
w: i64,
}
Here is the rust function
extern "C" fn pass_struct(x: MyS) {
println!("Called from cranelift: {x:?}")
}
Here is the cranelift function signature
let mut sig_pass_struct = module.make_signature();
sig_pass_struct.params.push(AbiParam::special(
module.target_config().pointer_type(),
ArgumentPurpose::StructArgument(mem::size_of::<MyS>() as u32),
));
let func_pass_struct = module
.declare_function("pass_struct", Linkage::Import, &sig_pass_struct)
.unwrap();
And the code used to generate cranelift IR
let mut bcx = FunctionBuilder::new(&mut ctx.func, &mut func_ctx);
// ...
let param_0 = bcx.block_params(block)[0]; // i64
let param_1 = bcx.block_params(block)[1]; // i64
let sum = bcx.ins().iadd(param_0, param_1);
let diff = bcx.ins().isub(param_0, param_1);
let product = bcx.ins().imul(param_0, param_1);
let square = bcx.ins().imul(param_0, param_0);
let stack_slot = bcx.create_sized_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
mem::size_of::<MyS>() as u32,
));
bcx.ins().stack_store(sum, stack_slot, 0);
bcx.ins().stack_store(diff, stack_slot, 8);
bcx.ins().stack_store(product, stack_slot, 16);
bcx.ins().stack_store(square, stack_slot, 24);
let addr = bcx.ins().stack_addr(types::I64, stack_slot, 0);
let function = module.declare_func_in_func(func_pass_struct, &mut bcx.func);
bcx.ins().call(function, &[addr]);
When I change the cranelift signature for the Rust function from taking in an AbiParam::special(ArgumentPurpose::StructArgument(...))
to taking in a AbiParam::new(types::I64)
it doesn't panic and runs exactly as I expect.
The assertion is almost certainly something to do with aarch64's 26-bit limit on various kinds of offsets. I don't remember exactly how StructArgument
lowers but IIRC it was for a special kind of compiler-managed memcpy; if you're allocating structs and managing pointers to them in your compiled code, you might as well pass a raw pointer (an I64
, as you switched to) as well. Specifically, StructArgument
doesn't mean "this is a struct" or something like that; Cranelift doesn't care what is behind the I64
, or even that it's a pointer.
StructArgument is passed at a fixed position on the stack to the callee. It is only required for compatibility with existing C code. If you write your own code I suggest that you explicitly pass your structs as pointers on both the caller and callee side. So extern "C" fn pass_struct(x: &MyS)
in your case.
Last updated: Dec 23 2024 at 12:05 UTC