Importing a function from host environments

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.

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))