Importing a function from host environments

You can import a function from your host environment using the integration of Swift Package Manager with C targets. Firstly, you should declare a signature for your function in a C header with an appropriate __import_name__ attribute:

__attribute__((__import_name__("add")))
extern int add(int lhs, int rhs);

Here __import_name__ specifies the name under which this function will be exposed to Swift code. Move this C header to a separate target, we'll call it HostFunction in this example. Your Package.swift manifest for your WebAssembly app would look like this:

// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
    name: "SwiftWasmApp",
    targets: [
      .target(name: "HostFunction", dependencies: []),
      .target(name: "SwiftWasmApp", dependencies: ["HostFunction"]),
    ]
)

Place this header into the include subdirectory of your HostFunction target directory. You can then import this host function module into Swift code just as any other module:

import HostFunction

print(add(2, 2))

Then, you can inject a host function into the produced WebAssembly binary.

Note that we use env as default import module name. You can specify the module name as __import_module__ in your C header. The full list of attributes in the header could look like __attribute__((__import_module__("env"),__import_name__("add"))).

const WASI = require("@wasmer/wasi").WASI;
const WasmFs = require("@wasmer/wasmfs").WasmFs;

const promisify = require("util").promisify;
const fs = require("fs");
const readFile = promisify(fs.readFile);

const main = async () => {
  const wasmFs = new WasmFs();
  // Output stdout and stderr to console
  const originalWriteSync = wasmFs.fs.writeSync;
  wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => {
    const text = new TextDecoder("utf-8").decode(buffer);
    switch (fd) {
      case 1:
        console.log(text);
        break;
      case 2:
        console.error(text);
        break;
    }
    return originalWriteSync(fd, buffer, offset, length, position);
  };

  // Instantiate a new WASI Instance
  let wasi = new WASI({
    args: [],
    env: {},
    bindings: {
      ...WASI.defaultBindings,
      fs: wasmFs.fs,
    },
  });

  const wasmBinary = await readFile("lib.wasm");

  // Instantiate the WebAssembly file
  let { instance } = await WebAssembly.instantiate(wasmBinary, {
    wasi_snapshot_preview1: wasi.wasiImport,
    env: {
      add: (lhs, rhs) => (lhs + rhs),
    }
  });

  wasi.start(instance);
};

main()

If you use Go bindings for Wasmer as your host environment, you should check an example repository from one of our contributors that shows an integration with an imported host function.

A more streamlined way to import host functions will be implemented in the future version of the SwiftWasm toolchain.