4

I want to provide a WebAssembly module with an external JavaScript function that accepts a Rust function pointer.

Once this JS module is initialized, it will call the run() function from the .wasm module, and that will in turn call peekaboo:

window.Module = {};

const imports = {
  env: {
    memoryBase: 0,
    tableBase: 0,
    memory: new WebAssembly.Memory({ initial: 256 }),
    table: new WebAssembly.Table({ initial: 4, element: 'anyfunc' })
  }
};

imports.env.peekaboo = function(f) {
  const fn = imports.env.table.get(f);
  return fn(2);
};

fetch('game.wasm')
  .then(response => response.arrayBuffer())
  .then(bytes => WebAssembly.compile(bytes))
  .then(mod => WebAssembly.instantiate(mod, imports))
  .then(mod => {
    mod.exports.run();

    Module.memory = imports.env.memory;
    Module.dealloc_str = mod.exports.dealloc_str;
  });

The examples I've seen indicate that if I import the memory in this way, I should be able to use the table to resolve my function pointer. Here is the Rust code:

#![feature(wasm_import_memory)]
#![wasm_import_memory]

extern "C" {
    fn peekaboo(f: fn(u32) -> u32);
}

fn main() {}

#[no_mangle]
pub fn run() {
    let plus_one = |x: u32| -> u32 { x + 1 };

    unsafe {
        peekaboo(plus_one);
    }
}

Everything compiles fine but when I execute the peekaboo function, the fn variable is null, indicating that the table was unable to find the function pointer. Therefore executing fn(2) blows up with:

Uncaught (in promise) TypeError: fn is not a function

I more or less followed this example but since I'm working in Rust, the translation is not 1-to-1. I suspect that I've overlooked something that's not obvious to me because I'm new to both Rust and WebAssembly. Can anyone spot my mistake?

1 Answer 1

4

It seems so far the WebAssembly backend in Rust doesn't provide a way import or export the (function) table; the index f is just fine, but imports.env.table is not the same table used by the wasm instance (i.e. empty).

Also you should use extern fn in FFI.

If you want to take a look under the hood the Playground provides some nice WebAssembly optimizations, check out this example:

Playground

#![crate_type = "cdylib"]
#![feature(link_args)]
#![allow(unused_attributes)] // link_args actually is used
#![link_args = "--import-memory"]

extern "C" {
    fn peekaboo(f: extern "C" fn(u32) -> u32);
}

#[no_mangle]
pub fn run() {
    extern "C" fn plus_one(x: u32) -> u32 {
        x + 1
    }

    unsafe {
        peekaboo(plus_one);
    }
}

The result should look like this:

(module
  (type $t0 (func))
  (type $t1 (func (param i32) (result i32)))
  (type $t2 (func (param i32)))
  (import "env" "peekaboo" (func $peekaboo (type $t2)))
  (import "env" "memory" (memory $env.memory 17))
  (func $run (export "run") (type $t0)
    (call $peekaboo
      (i32.const 1)))
  (func $playground::run::plus_one::h85275af105f0cc85 (type $t1) (param $p0 i32) (result i32)
    (i32.add
      (get_local $p0)
      (i32.const 1)))
  (table $T0 2 2 anyfunc)
  (elem (i32.const 1) $playground::run::plus_one::h85275af105f0cc85))

If you want to reproduce this locally add this in your Cargo.toml:

[lib]
crate-type = ["cdylib"]

[profile.release]
lto = true

And build with cargo +nightly build --release --target wasm32-unknown-unknown (assuming a rustup setup with a nightly toolchain and wasm32-unknown-unknown target enabled for the nightly toolchain).

Sign up to request clarification or add additional context in comments.

7 Comments

using no_std — The correct way is to generate a dynamic library. I'm not sure why that information has yet to be picked up by the community.
Ah, these both look promising, thanks! Can I assume that some of the code in this example is irrelevent? e.g. eh_personality()? :)
@Samo If you use no_std, you need to provide some symbols otherwise provided by the standard library for the code to link, including the ones you see in the code here. I don't know what items exactly are needed; it depends on your code, but you can simply let the compiler tell you.
@Stefan You also need to use wasm-gc, which the playground uses.
@starwed should be fixed now in the answer... although such things are expected to happen with unstable features :) it also seems wasm-gc isn't needed anymore (at least not for this small example), cdylib and lto are doing a good job.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.