SapphireAmoeba5 added the cranelift label to Issue #7820.
SapphireAmoeba5 added the bug label to Issue #7820.
SapphireAmoeba5 opened issue #7820:
Steps to Reproduce
extern "C" my_function(val: i32) { println!("The value is: {val}"); } // ... let mut jit_builder = JITBuilder::new(...); jit_builder.symbol("my_function", my_function as *const u8); let mut module = JITModule::new(jit_builder); let sig_my_function = module.make_signature(); sig_my_function.params.push(AbiParam::new(types::I32)); let func_my_function = module .declare_function("my_function", Linkage::Local, &sig_my_function) .unwrap(); // ... let local_function = module.declare_func_in_func(func_my_function, &mut builder_context.func); let toprint = builder_context.ins().iconst(types::I32, 2468); // Removing this line gets rid of the panic builder_context.ins().call(local_function, &[toprint]); // ... // This line here panics module.finalize_definitions().unwrap()
Expected Results
finalize_definitions() to not panic, and to behave as it does on my x86_64 linux machine
Actual Results
finalize_definitions() panics
thread 'main' panicked at /path/to/cranelift/crate/src/compiled_blob.rs:89:21: assertion failed: (diff >> 26 == -1) || (diff >> 26 == 0)
Versions and Environment
M1 Pro MacOS running Sonoma 14.0
[dependencies] cranelift = "0.103.0" cranelift-jit = "0.103.0" cranelift-module = "0.103.0" cranelift-native = "0.103.0"
Cranelift version or commit: 0.103.0
Operating system: MacOS
Architecture: Arm64
Extra Info
I tested the code on my x86_64 linux machine and it works exactly as expected
Here is the full source code I used that panics when I run in
use codegen::ir::UserFuncName; use cranelift::prelude::*; use cranelift_jit::{JITBuilder, JITModule}; use cranelift_module::{default_libcall_names, Linkage, Module}; use std::mem; extern "C" fn print_message(x: i32) { println!("Called from JIT {x}"); } fn main() { let mut flag_builder = settings::builder(); flag_builder.set("use_colocated_libcalls", "false").unwrap(); flag_builder.set("is_pic", "false").unwrap(); 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(); let mut jit_builder = JITBuilder::with_isa(isa, default_libcall_names()); jit_builder.symbol("print_message", print_message as *const u8); let mut module = JITModule::new(jit_builder); let mut ctx = module.make_context(); let mut func_ctx = FunctionBuilderContext::new(); let mut sig_print_message = module.make_signature(); sig_print_message.params.push(AbiParam::new(types::I32)); let mut sig_a = module.make_signature(); sig_a.params.push(AbiParam::new(types::I32)); sig_a.returns.push(AbiParam::new(types::I32)); let mut sig_b = module.make_signature(); sig_b.returns.push(AbiParam::new(types::I32)); let func_a = module .declare_function("a", Linkage::Local, &sig_a) .unwrap(); let func_b = module .declare_function("b", Linkage::Local, &sig_b) .unwrap(); let func_print_message = module .declare_function("print_message", Linkage::Local, &sig_print_message) .unwrap(); ctx.func.signature = sig_a; ctx.func.name = UserFuncName::user(0, func_a.as_u32()); { let mut bcx: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); let block = bcx.create_block(); bcx.switch_to_block(block); bcx.append_block_params_for_function_params(block); let param = bcx.block_params(block)[0]; let cst = bcx.ins().iconst(types::I32, 37); let add = bcx.ins().iadd(cst, param); let local_function = module.declare_func_in_func(func_print_message, &mut bcx.func); let toprint = bcx.ins().iconst(types::I32, 2468); bcx.ins().call(local_function, &[toprint]); bcx.ins().return_(&[add]); bcx.seal_all_blocks(); bcx.finalize(); } module.define_function(func_a, &mut ctx).unwrap(); module.clear_context(&mut ctx); ctx.func.signature = sig_b; ctx.func.name = UserFuncName::user(0, func_b.as_u32()); { let mut bcx: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); let block = bcx.create_block(); bcx.switch_to_block(block); let local_func = module.declare_func_in_func(func_a, &mut bcx.func); let arg = bcx.ins().iconst(types::I32, 5); let call = bcx.ins().call(local_func, &[arg]); let value = { let results = bcx.inst_results(call); assert_eq!(results.len(), 1); results[0].clone() }; bcx.ins().return_(&[value]); bcx.seal_all_blocks(); bcx.finalize(); } module.define_function(func_b, &mut ctx).unwrap(); module.clear_context(&mut ctx); // Perform linking. module.finalize_definitions().unwrap(); // Get a raw pointer to the generated code. let code_b = module.get_finalized_function(func_b); // Cast it to a rust function pointer type. let ptr_b = unsafe { mem::transmute::<_, extern "C" fn() -> u32>(code_b) }; // Call it! let res = ptr_b(); println!("Return value: {}", res); assert_eq!(res, 42); }
cfallin commented on issue #7820:
@SapphireAmoeba5 sorry for the delayed response on this one!
I think the issue is your usage of
Linkage::Local
with a symbol actually defined in another module (namely, your host program). aarch64 (aka arm64 aka "Apple Silicon") has some quirks, like many RISC ISAs, that lead to more complications on call relocations than on x86. Specifically, the native call instruction can address only +/- 128MiB; the26
you see in the assertion's expression comes from the width (26 bits) of the offset bitfield.The usual way around that is to use an indirect call with an address built by multiple instructions, or loaded from memory. E.g., calls between a binary and shared libraries (loaded at very different places in the address space) often go through the PLT (procedure linkage table), which stores resolved addresses in a table.
Cranelift supports arbitrary-range calls too. Because the core compiler is agnostic to the surrounding compilation model (shared libraries, PLTs, etc), it has a notion of "colocated" functions instead, and I believe
cranelift-module
surfaces this in theLinkage
enum that you see. The appropriate value is probablyLinkage::Import
. That will tellcranelift-module
to not use a "colocated" function signature in the Cranelift IR, which in turn will tell Cranelift to compile the call with anAbs8
(absolute 64-bit address) relocation instead, which can be resolved to anywhere in the address space.(Why is the symbol so far away from its use? Because your program is likely loaded to a random location due to ASLR, and Cranelift-generated code is put in another random location due to
mmap
's randomization. So the call offset from one to the other is quite large and needs this special relocation kind.)
SapphireAmoeba5 commented on issue #7820:
@SapphireAmoeba5 sorry for the delayed response on this one!
I think the issue is your usage of
Linkage::Local
with a symbol actually defined in another module (namely, your host program). aarch64 (aka arm64 aka "Apple Silicon") has some quirks, like many RISC ISAs, that lead to more complications on call relocations than on x86. Specifically, the native call instruction can address only +/- 128MiB; the26
you see in the assertion's expression comes from the width (26 bits) of the offset bitfield.The usual way around that is to use an indirect call with an address built by multiple instructions, or loaded from memory. E.g., calls between a binary and shared libraries (loaded at very different places in the address space) often go through the PLT (procedure linkage table), which stores resolved addresses in a table.
Cranelift supports arbitrary-range calls too. Because the core compiler is agnostic to the surrounding compilation model (shared libraries, PLTs, etc), it has a notion of "colocated" functions instead, and I believe
cranelift-module
surfaces this in theLinkage
enum that you see. The appropriate value is probablyLinkage::Import
. That will tellcranelift-module
to not use a "colocated" function signature in the Cranelift IR, which in turn will tell Cranelift to compile the call with anAbs8
(absolute 64-bit address) relocation instead, which can be resolved to anywhere in the address space.(Why is the symbol so far away from its use? Because your program is likely loaded to a random location due to ASLR, and Cranelift-generated code is put in another random location due to
mmap
's randomization. So the call offset from one to the other is quite large and needs this special relocation kind.)Hi, thanks for the response! I changed
Linkage::Local
toLinkage::Import
and everything seems to work perfectly so far. Thanks for the assistance!
SapphireAmoeba5 closed issue #7820:
Steps to Reproduce
extern "C" my_function(val: i32) { println!("The value is: {val}"); } // ... let mut jit_builder = JITBuilder::new(...); jit_builder.symbol("my_function", my_function as *const u8); let mut module = JITModule::new(jit_builder); let sig_my_function = module.make_signature(); sig_my_function.params.push(AbiParam::new(types::I32)); let func_my_function = module .declare_function("my_function", Linkage::Local, &sig_my_function) .unwrap(); // ... let local_function = module.declare_func_in_func(func_my_function, &mut builder_context.func); let toprint = builder_context.ins().iconst(types::I32, 2468); // Removing this line gets rid of the panic builder_context.ins().call(local_function, &[toprint]); // ... // This line here panics module.finalize_definitions().unwrap()
Expected Results
finalize_definitions() to not panic, and to behave as it does on my x86_64 linux machine
Actual Results
finalize_definitions() panics
thread 'main' panicked at /path/to/cranelift/crate/src/compiled_blob.rs:89:21: assertion failed: (diff >> 26 == -1) || (diff >> 26 == 0)
Versions and Environment
M1 Pro MacOS running Sonoma 14.0
[dependencies] cranelift = "0.103.0" cranelift-jit = "0.103.0" cranelift-module = "0.103.0" cranelift-native = "0.103.0"
Cranelift version or commit: 0.103.0
Operating system: MacOS
Architecture: Arm64
Extra Info
I tested the code on my x86_64 linux machine and it works exactly as expected
Here is the full source code I used that panics when I run in
use codegen::ir::UserFuncName; use cranelift::prelude::*; use cranelift_jit::{JITBuilder, JITModule}; use cranelift_module::{default_libcall_names, Linkage, Module}; use std::mem; extern "C" fn print_message(x: i32) { println!("Called from JIT {x}"); } fn main() { let mut flag_builder = settings::builder(); flag_builder.set("use_colocated_libcalls", "false").unwrap(); flag_builder.set("is_pic", "false").unwrap(); 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(); let mut jit_builder = JITBuilder::with_isa(isa, default_libcall_names()); jit_builder.symbol("print_message", print_message as *const u8); let mut module = JITModule::new(jit_builder); let mut ctx = module.make_context(); let mut func_ctx = FunctionBuilderContext::new(); let mut sig_print_message = module.make_signature(); sig_print_message.params.push(AbiParam::new(types::I32)); let mut sig_a = module.make_signature(); sig_a.params.push(AbiParam::new(types::I32)); sig_a.returns.push(AbiParam::new(types::I32)); let mut sig_b = module.make_signature(); sig_b.returns.push(AbiParam::new(types::I32)); let func_a = module .declare_function("a", Linkage::Local, &sig_a) .unwrap(); let func_b = module .declare_function("b", Linkage::Local, &sig_b) .unwrap(); let func_print_message = module .declare_function("print_message", Linkage::Local, &sig_print_message) .unwrap(); ctx.func.signature = sig_a; ctx.func.name = UserFuncName::user(0, func_a.as_u32()); { let mut bcx: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); let block = bcx.create_block(); bcx.switch_to_block(block); bcx.append_block_params_for_function_params(block); let param = bcx.block_params(block)[0]; let cst = bcx.ins().iconst(types::I32, 37); let add = bcx.ins().iadd(cst, param); let local_function = module.declare_func_in_func(func_print_message, &mut bcx.func); let toprint = bcx.ins().iconst(types::I32, 2468); bcx.ins().call(local_function, &[toprint]); bcx.ins().return_(&[add]); bcx.seal_all_blocks(); bcx.finalize(); } module.define_function(func_a, &mut ctx).unwrap(); module.clear_context(&mut ctx); ctx.func.signature = sig_b; ctx.func.name = UserFuncName::user(0, func_b.as_u32()); { let mut bcx: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); let block = bcx.create_block(); bcx.switch_to_block(block); let local_func = module.declare_func_in_func(func_a, &mut bcx.func); let arg = bcx.ins().iconst(types::I32, 5); let call = bcx.ins().call(local_func, &[arg]); let value = { let results = bcx.inst_results(call); assert_eq!(results.len(), 1); results[0].clone() }; bcx.ins().return_(&[value]); bcx.seal_all_blocks(); bcx.finalize(); } module.define_function(func_b, &mut ctx).unwrap(); module.clear_context(&mut ctx); // Perform linking. module.finalize_definitions().unwrap(); // Get a raw pointer to the generated code. let code_b = module.get_finalized_function(func_b); // Cast it to a rust function pointer type. let ptr_b = unsafe { mem::transmute::<_, extern "C" fn() -> u32>(code_b) }; // Call it! let res = ptr_b(); println!("Return value: {}", res); assert_eq!(res, 42); }
Last updated: Dec 23 2024 at 13:07 UTC