Linking modules together

You can also browse this source code online and clone the wasmtime repository to run the example locally.

This example shows off how to link multiple wasm modules together, where one module imports functions exported by another.

linking1.wat

(module
  (import "linking2" "double" (func $double (param i32) (result i32)))
  (import "linking2" "log" (func $log (param i32 i32)))
  (import "linking2" "memory" (memory 1))
  (import "linking2" "memory_offset" (global $offset i32))

  (func (export "run")
    ;; Call into the other module to double our number, and we could print it
    ;; here but for now we just drop it
    i32.const 2
    call $double
    drop

    ;; Our `data` segment initialized our imported memory, so let's print the
    ;; string there now.
    global.get $offset
    i32.const 14
    call $log
  )

  (data (global.get $offset) "Hello, world!\n")
)

linking2.wat

(module
  (type $fd_write_ty (func (param i32 i32 i32 i32) (result i32)))
  (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (type $fd_write_ty)))

  (func (export "double") (param i32) (result i32)
    local.get 0
    i32.const 2
    i32.mul
  )

  (func (export "log") (param i32 i32)
    ;; store the pointer in the first iovec field
    i32.const 4
    local.get 0
    i32.store

    ;; store the length in the first iovec field
    i32.const 4
    local.get 1
    i32.store offset=4

    ;; call the `fd_write` import
    i32.const 1     ;; stdout fd
    i32.const 4     ;; iovs start
    i32.const 1     ;; number of iovs
    i32.const 0     ;; where to write nwritten bytes
    call $fd_write
    drop
  )

  (memory (export "memory") 2)
  (global (export "memory_offset") i32 (i32.const 65536))
)

linking.cc

#include <fstream>
#include <iostream>
#include <sstream>
#include <wasmtime.hh>

using namespace wasmtime;

template <typename T, typename E> T unwrap(Result<T, E> result) {
  if (result) {
    return result.ok();
  }
  std::cerr << "error: " << result.err().message() << "\n";
  std::abort();
}

std::string readFile(const char *name) {
  std::ifstream watFile;
  watFile.open(name);
  std::stringstream strStream;
  strStream << watFile.rdbuf();
  return strStream.str();
}

int main() {
  Engine engine;
  Store store(engine);

  // Read our input `*.wat` files into `std::string`s
  std::string linking1_wat = readFile("examples/linking1.wat");
  std::string linking2_wat = readFile("examples/linking2.wat");

  // Compile our two modules
  Module linking1_module = Module::compile(engine, linking1_wat).unwrap();
  Module linking2_module = Module::compile(engine, linking2_wat).unwrap();

  // Configure WASI and store it within our `wasmtime_store_t`
  WasiConfig wasi;
  wasi.inherit_argv();
  wasi.inherit_env();
  wasi.inherit_stdin();
  wasi.inherit_stdout();
  wasi.inherit_stderr();
  store.context().set_wasi(std::move(wasi)).unwrap();

  // Create our linker which will be linking our modules together, and then add
  // our WASI instance to it.
  Linker linker(engine);
  linker.define_wasi().unwrap();

  // Instantiate our first module which only uses WASI, then register that
  // instance with the linker since the next linking will use it.
  Instance linking2 = linker.instantiate(store, linking2_module).unwrap();
  linker.define_instance(store, "linking2", linking2).unwrap();

  // And with that we can perform the final link and the execute the module.
  Instance linking1 = linker.instantiate(store, linking1_module).unwrap();
  Func f = std::get<Func>(*linking1.get(store, "run"));
  f.call(store, {}).unwrap();
}