Importing a function from host environments

Swift 6.0 or later

If you are using Swift 6.0 or later, you can use experimental @_extern(wasm) attribute

Swift 6.0 introduces a new attribute @_extern(wasm) to import a function from the host environment. To use this experimental feature, you need to enable it in your SwiftPM manifest file:

.executableTarget(
    name: "Example",
    swiftSettings: [
        .enableExperimentalFeature("Extern")
    ]),

Then, you can import a function from the host environment as follows without using C headers:

@_extern(wasm, module: "env", name: "add")
func add(_ a: Int, _ b: Int) -> Int

print(add(2, 2))

Swift 5.10 or earlier

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.9
import PackageDescription

let package = Package(
    name: "Example",
    targets: [
      .target(name: "HostFunction", dependencies: []),
      .executableTarget(name: "Example", 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"))).

// File name: main.mjs
import { WASI, File, OpenFile, ConsoleStdout } from "@bjorn3/browser_wasi_shim";
import fs from "fs/promises";

const main = async () => {

  // Instantiate a new WASI Instance
  // See https://github.com/bjorn3/browser_wasi_shim/ for more detail about constructor options
  let wasi = new WASI([], [],
    [
      new OpenFile(new File([])), // stdin
      ConsoleStdout.lineBuffered(msg => console.log(`[WASI stdout] ${msg}`)),
      ConsoleStdout.lineBuffered(msg => console.warn(`[WASI stderr] ${msg}`)),
    ],
    { debug: false }
  );

  const wasmBinary = await fs.readFile(".build/wasm32-unknown-wasi/debug/Example.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.