How to implement a C function with a function pointer param in Wasmtime

Hi, I want to implement a C function with a function pointer param in Wasmtime, but I can't do that and I want to know why and how to accomplish that.

here is my C code.

// src.c
int add_def(int a, int b) { return a + b; }

extern void test_func(int (*add)(int, int));

int main() {
    test_func(add_def);
}

And here is my Rust code in Wasmtime.

#[link(name = "helper")]
extern "C" {
    test_func(add: extern "C" fn(i32, i32) -> i32);
}

fn wrap_test_func(mut caller: Caller<'_, Host>, _add: i32) {
    let memory = caller.get_export("memory").unwrap().into_memory().unwrap();
    let linear_memory = memory.data_mut(&mut caller).as_mut_ptr();
    unsafe {
        // the problem happened around here, but I don't know how to fix it.
        let add: *mut extern "C" fn(i32, i32) -> i32 = linear_memory.add(_add as usize).cast();
        test_func(*add)
    }   
}
// ... 
linker.func_wrap("env", "test_func", wrap_test_func);

And here is the how test_func is implemented by C. The code below will be complied and linked to Wasmtime via build.rs .

// helper.c
void test_func(int (*add)(int, int)) {
    printf("%d\n", add(1, 2));
}

After that, I tried to call test_func in the main function of src.c and compiled src.c into src.wasm via clang. Then I ran it by Wasmtime src.wasm. And the error message shows like below.

[1]    89896 segmentation fault  wasmtime src.wasm

So how could I fix that?

You are trying to interpret a wasm function pointer as a host function pointer. This doesn't work. Wasm function pointers are indexes into the function table of a wasm module, not addresses. You will have to look up the wasm function in the function table. If the function table is exorted you can look it up by name using caller.get_export(). If not (which I think is the case) I would suggest that you create a couple of trampoline functions in the wasm module which take a wasm function pointer and the arguments you want to call the function with as arguments and which calls the passed in function pointer. You can then use this trampoline to call from the host into the wasm function you have a function pointee to.

2 Likes

Looks like you can compile with the -Wl,--export-table link arg to export the function table. Not sure under which name though, so you will have to take a look yourself.

1 Like

Yeah, this can work! I can call the function in Wasmtime now. Besides, the table name is __indirect_function_table. And the code is here.

And a trickey thing happened when I tried this. As you can see, the index I used to get the function from table is 1 instead of 0. And the size of the table in this case is 2. The item whose index is 0 is a None.

fn wrap_test_func(mut caller: Caller<'_, Host>, _add: i32) {
    let table = caller.get_export("__indirect_function_table").unwrap().into_table().unwrap();
    let func = table.get(&mut caller, 1).unwrap()
                                        .funcref().unwrap().unwrap()
                                        .typed::<(i32, i32), i32, _>(&caller).unwrap();
    let res = func.call(&mut caller, (1, 2)).unwrap();
    println!("res: {}", res);
    // ...
}

And then I'm going to try to pass the function to my C function where the true test_func is defined.

But sadly, I still don't know how to pass the param to test_func. Because I need it to be a function pointer, but all I got is a wasmtime::func::Func.

fn wrap_test_func(mut caller: Caller<'_, Host>, _add: i32) {
    let table = caller.get_export("__indirect_function_table").unwrap().into_table().unwrap();
    let func = table.get(&mut caller, 1).unwrap()
                                        .funcref().unwrap().unwrap()
                                        .typed::<(i32, i32), i32, _>(&caller).unwrap();
    let res = func.call(&mut caller, (1, 2)).unwrap();
    println!("res: {}", res);
    unsafe{
        // how can I construct the param?
        test_func(??);
    }
}

test_func accepts a function pointer, but doesn't allow passing any extra data to indicate the wasm instance and such. You will have to change test_func to accept an extra pointer argument to pass to the function. And then use this to pass a closure rather than a single function pointer. This closure can then call func.call().

1 Like

That makes sense! I see what you mean. Thanks a lot! :alien:

But it can be so cumbersome and there are so many function pointers needed to be handled in my project.

The alternative less portable option is to generate a trampoline function at runtime that calls the closure with a fixed environment. This makes memory management a lot harder and will leak memory if you never deallocate those trampolines again. Gcc does something like this for nested functions. Gcc however puts the trampoline directly on the stack, so the function pointer is invalidated immediately when returning from the current function and it forces the stack to be executable, which makes exploiting memory safety bugs much easier.

1 Like

But I'm not so familiar with the trampoline, so maybe I just do it by old way. :persevere:

Thanks again! This really helps me.

In Rust, I would create some sort of abstraction which manages memory and lets you bundle a function pointer with a pointer to its state.

If the C function doesn't accept some sort of state pointer then I'd argue it is poorly designed. Almost all non-trivial callbacks will require access to some sort of state, so if the C function only accepts a function pointer, the only way for your function to still use state is via global variables.

You might be interested in an article I wrote a while back about how to pass Rust closures to C code. In there, we start simple with a basic function pointer, then introduce state and trampolines.

1 Like

That would be wonderful, Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.