[solved] Get wasm32-unknown-unknown to emit funcref for fn parameter type?

Hi, I'm wondering if it's possible to get Rust to emit a wasm function that takes a funcref as a parameter. This was my best guess, but alas the type in the generated wasm is i32. Which I guess makes some sense since pointers are emitted as i32.

extern "C" {
    fn register_series(
        series: u64,
        extract_key: extern fn(buffer: *const u8, len: u32) -> (*const u8, u32),
    );
}

const FOO: u64 = 0;

pub extern "C" fn foo_key(buffer: *const u8, len: u32) -> (*const u8, u32) {
    (buffer, 8)
}

#[no_mangle]
pub extern "C" fn register_all_series() {
    unsafe {
        register_series(FOO, foo_key);
    }
}

See "register_series" import and it's type, "type (;0;) ..." (at line 2 below). I was hoping to get this instead for the function type:

(type (;0;) (func (param i64 funcref)))

But ended up with this:

(module
  (type (;0;) (func (param i64 i32)))
  (type (;1;) (func (param i32 i32 i32)))
  (type (;2;) (func))
  (import "env" "register_series" (func $register_series (type 0)))
  (func $_ZN10test_query7foo_key17hac4e0a3afbd67308E (type 1) (param i32 i32 i32)
    local.get 0
    i32.const 8
    i32.store offset=4
    local.get 0
    local.get 1
    i32.store)
  (func $register_all_series (type 2)
    i64.const 0
    i32.const 1
    call $register_series)
  (table (;0;) 2 2 funcref)
  (memory (;0;) 16)
  (global (;0;) (mut i32) (i32.const 1048576))
  (global (;1;) i32 (i32.const 1048576))
  (global (;2;) i32 (i32.const 1048576))
  (export "memory" (memory 0))
  (export "register_all_series" (func $register_all_series))
  (export "__data_end" (global 1))
  (export "__heap_base" (global 2))
  (elem (;0;) (i32.const 1) func $_ZN10test_query7foo_key17hac4e0a3afbd67308E))

I googled around a bit and couldn't find much info at all about funcref actually... But this test file in wasmtime makes me think WebAssembly supports funcrefs as parameters: https://github.com/bytecodealliance/wasmtime/blob/c9a3f05afd45961b0b397f97c4ad79cd7a7c807d/tests/all/funcref.rs#L9

I wasn't able to directly answer my question, but I found a workaround for my use-case. I'll describe in case anyone happens upon this later.

So looking closer at the generated wasm, it looks like register_all_series is calling register_series with i32.const 1 for the extract_key parameter.

Looking down at the bottom of the generated wasm, a table of funcref is declared, and foo_key is inserted at position i32.const 1 (last line).

So on the host side all we have to do is get the table and use the passed extract_key parameter as an index into the table to get the funcref we want. But the table isn't being exported in the generated wasm. I edited my .cargo/config.toml to look like this:

[build]
target = "wasm32-unknown-unknown"
rustflags = ["-Clink-arg=--export-table"] # added --export-table

And voilà, we get an exported __indirect_function_table:

(module
  (type (;0;) (func (param i64 i32)))
  (type (;1;) (func (param i32 i32 i32)))
  (type (;2;) (func))
  (import "env" "register_series" (func $register_series (type 0)))
  (func $_ZN10test_query7foo_key17hac4e0a3afbd67308E (type 1) (param i32 i32 i32)
    local.get 0
    i32.const 8
    i32.store offset=4
    local.get 0
    local.get 1
    i32.store)
  (func $register_all_series (type 2)
    i64.const 0
    i32.const 1
    call $register_series)
  (table (;0;) 2 2 funcref)
  (memory (;0;) 16)
  (global (;0;) (mut i32) (i32.const 1048576))
  (global (;1;) i32 (i32.const 1048576))
  (global (;2;) i32 (i32.const 1048576))
  (export "memory" (memory 0))
  (export "__indirect_function_table" (table 0))
  (export "register_all_series" (func $register_all_series))
  (export "__data_end" (global 1))
  (export "__heap_base" (global 2))
  (elem (;0;) (i32.const 1) func $_ZN10test_query7foo_key17hac4e0a3afbd67308E))
2 Likes

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.