Stream: git-wasmtime

Topic: wasmtime / issue #7820 Cranelift: Calling Rust function c...


view this post on Zulip Wasmtime GitHub notifications bot (Jan 25 2024 at 04:47):

SapphireAmoeba5 added the cranelift label to Issue #7820.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 25 2024 at 04:47):

SapphireAmoeba5 added the bug label to Issue #7820.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 25 2024 at 04:47):

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);
}

view this post on Zulip Wasmtime GitHub notifications bot (Feb 11 2024 at 17:43):

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; the 26 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 the Linkage enum that you see. The appropriate value is probably Linkage::Import. That will tell cranelift-module to not use a "colocated" function signature in the Cranelift IR, which in turn will tell Cranelift to compile the call with an Abs8 (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.)

view this post on Zulip Wasmtime GitHub notifications bot (Feb 11 2024 at 20:35):

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; the 26 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 the Linkage enum that you see. The appropriate value is probably Linkage::Import. That will tell cranelift-module to not use a "colocated" function signature in the Cranelift IR, which in turn will tell Cranelift to compile the call with an Abs8 (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 to Linkage::Import and everything seems to work perfectly so far. Thanks for the assistance!

view this post on Zulip Wasmtime GitHub notifications bot (Feb 12 2024 at 04:55):

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