Linking Modules - Wasmtime

# Linking modules

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

This example shows off how to compile and instantiate modules which link together.

## `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.c`

    /*
    Example of compiling, instantiating, and linking two WebAssembly modules
    together.

    You can compile and run this example on Linux with:

       cargo build --release -p wasmtime-c-api
       cc examples/linking.c \
           -I crates/c-api/include \
           target/release/libwasmtime.a \
           -lpthread -ldl -lm \
           -o linking
       ./linking

    Note that on Windows and macOS the command will be similar, but you'll need
    to tweak the `-lpthread` and such annotations.

    You can also build using cmake:

    mkdir build && cd build && cmake .. && cmake --build . --target wasmtime-linking
    */

    #include <assert.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <wasi.h>
    #include <wasm.h>
    #include <wasmtime.h>

    #define MIN(a, b) ((a) < (b) ? (a) : (b))

    static void exit_with_error(const char *message, wasmtime_error_t *error,
                                wasm_trap_t *trap);
    static void read_wat_file(wasm_engine_t *engine, wasm_byte_vec_t *bytes,
                              const char *file);

    int main() {
      // Set up our context
      wasm_engine_t *engine = wasm_engine_new();
      assert(engine != NULL);
      wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
      assert(store != NULL);
      wasmtime_context_t *context = wasmtime_store_context(store);

      wasm_byte_vec_t linking1_wasm, linking2_wasm;
      read_wat_file(engine, &linking1_wasm, "examples/linking1.wat");
      read_wat_file(engine, &linking2_wasm, "examples/linking2.wat");

      // Compile our two modules
      wasmtime_error_t *error;
      wasmtime_module_t *linking1_module = NULL;
      wasmtime_module_t *linking2_module = NULL;
      error = wasmtime_module_new(engine, (uint8_t *)linking1_wasm.data,
                                  linking1_wasm.size, &linking1_module);
      if (error != NULL)
        exit_with_error("failed to compile linking1", error, NULL);
      error = wasmtime_module_new(engine, (uint8_t *)linking2_wasm.data,
                                  linking2_wasm.size, &linking2_module);
      if (error != NULL)
        exit_with_error("failed to compile linking2", error, NULL);
      wasm_byte_vec_delete(&linking1_wasm);
      wasm_byte_vec_delete(&linking2_wasm);

      // Configure WASI and store it within our `wasmtime_store_t`
      wasi_config_t *wasi_config = wasi_config_new();
      assert(wasi_config);
      wasi_config_inherit_argv(wasi_config);
      wasi_config_inherit_env(wasi_config);
      wasi_config_inherit_stdin(wasi_config);
      wasi_config_inherit_stdout(wasi_config);
      wasi_config_inherit_stderr(wasi_config);
      wasm_trap_t *trap = NULL;
      error = wasmtime_context_set_wasi(context, wasi_config);
      if (error != NULL)
        exit_with_error("failed to instantiate wasi", NULL, trap);

      // Create our linker which will be linking our modules together, and then add
      // our WASI instance to it.
      wasmtime_linker_t *linker = wasmtime_linker_new(engine);
      error = wasmtime_linker_define_wasi(linker);
      if (error != NULL)
        exit_with_error("failed to link wasi", error, NULL);

      // Instantiate `linking2` with our linker.
      wasmtime_instance_t linking2;
      error = wasmtime_linker_instantiate(linker, context, linking2_module,
                                          &linking2, &trap);
      if (error != NULL || trap != NULL)
        exit_with_error("failed to instantiate linking2", error, trap);

      // Register our new `linking2` instance with the linker
      error = wasmtime_linker_define_instance(linker, context, "linking2",
                                              strlen("linking2"), &linking2);
      if (error != NULL)
        exit_with_error("failed to link linking2", error, NULL);

      // Instantiate `linking1` with the linker now that `linking2` is defined
      wasmtime_instance_t linking1;
      error = wasmtime_linker_instantiate(linker, context, linking1_module,
                                          &linking1, &trap);
      if (error != NULL || trap != NULL)
        exit_with_error("failed to instantiate linking1", error, trap);

      // Lookup our `run` export function
      wasmtime_extern_t run;
      bool ok = wasmtime_instance_export_get(context, &linking1, "run", 3, &run);
      assert(ok);
      assert(run.kind == WASMTIME_EXTERN_FUNC);
      error = wasmtime_func_call(context, &run.of.func, NULL, 0, NULL, 0, &trap);
      if (error != NULL || trap != NULL)
        exit_with_error("failed to call run", error, trap);

      // Clean up after ourselves at this point
      wasmtime_linker_delete(linker);
      wasmtime_module_delete(linking1_module);
      wasmtime_module_delete(linking2_module);
      wasmtime_store_delete(store);
      wasm_engine_delete(engine);
      return 0;
    }

    static void read_wat_file(wasm_engine_t *engine, wasm_byte_vec_t *bytes,
                              const char *filename) {
      wasm_byte_vec_t wat;
      // Load our input file to parse it next
      FILE *file = fopen(filename, "r");
      if (!file) {
        printf("> Error loading file!\n");
        exit(1);
      }
      fseek(file, 0L, SEEK_END);
      size_t file_size = ftell(file);
      wasm_byte_vec_new_uninitialized(&wat, file_size);
      fseek(file, 0L, SEEK_SET);
      if (fread(wat.data, file_size, 1, file) != 1) {
        printf("> Error loading module!\n");
        exit(1);
      }
      fclose(file);

      // Parse the wat into the binary wasm format
      wasmtime_error_t *error = wasmtime_wat2wasm(wat.data, wat.size, bytes);
      if (error != NULL)
        exit_with_error("failed to parse wat", error, NULL);
      wasm_byte_vec_delete(&wat);
    }

    static void exit_with_error(const char *message, wasmtime_error_t *error,
                                wasm_trap_t *trap) {
      fprintf(stderr, "error: %s\n", message);
      wasm_byte_vec_t error_message;
      if (error != NULL) {
        wasmtime_error_message(error, &error_message);
        wasmtime_error_delete(error);
      } else {
        wasm_trap_message(trap, &error_message);
        wasm_trap_delete(trap);
      }
      fprintf(stderr, "%.*s\n", (int)error_message.size, error_message.data);
      wasm_byte_vec_delete(&error_message);
      exit(1);
    }