Introduction
Welcome to the SwiftWasm Documentation!
SwiftWasm is an open source project to support the WebAssembly target for Swift.
The goal of this project is to fully support the WebAssembly target for Swift and to be merged into the upstream repository.
WebAssembly is described on its home page as:
WebAssembly (abbreviated as Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
We use LLVM as a compiler backend to produce WebAssembly binaries. Our resulting binaries also depend on WASI, which is a modular system interface for WebAssembly. WASI is mainly required to compile Swift Standard Library.
Project Status
The remaining works are:
- Integrating the build system with the official Swift CI.
Getting Started
This is a getting started guide section to use SwiftWasm.
You can learn about:
- How to set up a Swift toolchain for compiling to WebAssembly
- How to compile a simple Swift code and Swift Package into WebAssembly
- How to interoperate with JavaScript
Installation - Latest Release (SwiftWasm 5.10)
To install Swift for WebAssembly toolchain, download one of the packages below and follow the instructions for your operating system.
Tag: swift-wasm-5.10.0-RELEASE
Download | Docker Tag |
---|---|
macOS arm64 | Unavailable |
macOS x86 | Unavailable |
Ubuntu 18.04 x86_64 | 5.10-bionic, bionic |
Ubuntu 20.04 x86_64 | 5.10-focal, focal |
Ubuntu 20.04 aarch64 | 5.10-focal, focal |
Ubuntu 22.04 x86_64 | 5.10, 5.10-jammy, jammy, latest |
You can find older releases from the GitHub Releases page
Toolchain Installation
macOS
- Download the latest package release according to your CPU architecture (arm64 for Apple Silicon Macs, x86 for Intel Macs).
- Run the package installer, which will install an Xcode toolchain into
/Library/Developer/Toolchains/
. - To use the Swift toolchain with command-line tools, use
env TOOLCHAINS=swiftwasm swift
or add the Swift toolchain to your path as follows:
export PATH=/Library/Developer/Toolchains/<toolchain name>.xctoolchain/usr/bin:"${PATH}"
- Run
swift --version
. If you installed the toolchain successfully, you can get the following message.
$ swift --version
# Or TOOLCHAINS=swiftwasm swift --version
SwiftWasm Swift version 5.10-dev
Target: x86_64-apple-darwin21.6.0
If you want to uninstall the toolchain, you can remove the toolchain directory from /Library/Developer/Toolchains/
and make sure to remove the toolchain from your PATH
.
Linux
- Download the latest package release according to your Ubuntu version and CPU architecture.
- Follow the official Swift installation guide for Linux from swift.org while skipping GPG key verification, which is not provided for SwiftWasm releases.
Experimental: Swift SDK
SwiftWasm provides Swift SDKs for WebAssembly. You can use the Swift SDK to cross-compile Swift packages for WebAssembly without installing the whole toolchain.
To use the Swift SDK, you need to install the official Swift toolchain 5.10 or later. Then, you can install the Swift SDK using the following command while replacing <your platform>
:
$ swift experimental-sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-5.10.0-RELEASE/swift-wasm-5.10.0-RELEASE-<your platform>.artifactbundle.zip
You can find the latest Swift SDK release from the GitHub Releases page.
After installing the Swift SDK, you can see the installed SDKs using the following command:
$ swift experimental-sdk list
<SDK name>
...
You can use the installed SDKs to cross-compile Swift packages for WebAssembly using the following command:
$ swift build --experimental-swift-sdk <SDK name>
Docker
SwiftWasm offical Docker images are hosted on GitHub Container Registry.
SwiftWasm Dockerfiles are located on swiftwasm-docker repository.
Supported Platforms
- Ubuntu 18.04 (x86_64)
- Ubuntu 20.04 (x86_64, aarch64)
- Ubuntu 22.04 (x86_64)
Using Docker Images
- Pull the Docker image from GitHub Container Registry:
docker pull ghcr.io/swiftwasm/swift:latest
- Create a container using tag
latest
and attach it to the container:
docker run --rm -it ghcr.io/swiftwasm/swift:latest /bin/bash
Installation - Development Snapshot
Swift SDK for WebAssembly - Cross compile to WebAssembly
The upstream Open Source Swift toolchain does support WebAssembly as a target platform since Swift 6.0. For those versions, you just need to install Swift SDK for cross-compilation.
Instructions
-
Find a Development Snapshot
Visit the SwiftWasm GitHub Releases page.
-
Match the Swift Toolchain Version
Refer to the release note's table to find the corresponding upstream Swift toolchain version.
For example,
swift-wasm-DEVELOPMENT-SNAPSHOT-2024-06-12-a
corresponds toswift-DEVELOPMENT-SNAPSHOT-2024-06-11-a
-
Download the Swift Toolchain
Download the appropriate Swift toolchain version from swift.org/install.
-
Get the Swift SDK artifactbundle URL
Find the URL ending with
.artifactbundle.zip
from the release page you found in step 1. Currently we provide Swift SDKs for the following WebAssembly targets:wasm32-unknown-wasi
(Recommended): for WASI Preview 1wasm32-unknown-wasip1-threads
: for WASI Preview 1 with wasi-threads extension
Choose
wasm32-unknown-wasi
if you are not sure which target to use. -
Install the Swift SDK for WebAssembly
Use the following command to install the Swift SDK:
$ swift sdk install <URL-or-filename-of-swift-sdk-artifactbundle>
For example, if you selected
swift-wasm-DEVELOPMENT-SNAPSHOT-2024-06-12-a
, you need to run the following command:$ swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-06-12-a/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-06-12-a-wasm32-unknown-wasi.artifactbundle.zip
-
You can see the installed SDKs using the following command:
$ swift sdk list <Swift SDK id> ...
Once you don't need the SDK, you can remove it using the following command:
$ swift sdk remove <Swift SDK id>
How to use the Swift SDK
First, create a new Swift package using the following command:
$ mkdir Example
$ cd Example
$ swift package init --type executable
Then, you can build the package using the Swift SDK:
$ swift build --swift-sdk wasm32-unknown-wasi
# or
$ swift build --swift-sdk <Swift SDK id>
FAQ
What is the difference between the Swift Toolchain and the Swift SDK?
The Swift toolchain is a complete package that includes the Swift compiler, standard library, and other tools.
The Swift SDK includes a subset of the Swift toolchain that includes only the necessary components for cross-compilation and some supplementary resources.
What is included in the Swift SDK for WebAssembly?
The Swift SDK for WebAssembly includes only the pre-built Swift standard libraries for WebAssembly. It does not include the Swift compiler or other tools that are part of the Swift toolchain.
Hello, World!
This section will show you how to compile a simple Swift code into WebAssembly and run the produced binary on WASI supported WebAssembly runtime.
1. Create a Swift file
$ echo 'print("Hello, world!")' > hello.swift
2. Compile Swift code into WebAssembly with WASI
$ swiftc -target wasm32-unknown-wasi hello.swift -o hello.wasm
3. Run the produced binary on WebAssembly runtime
You can the run the produced binary with wasmtime (or other WebAssembly runtime):
$ wasmtime hello.wasm
The produced binary depends on WASI which is an interface of system call for WebAssembly. So you need to use WASI supported runtime and when you run the binary on browser, you need WASI polyfill library like @bjorn3/browser_wasi_shim.
Compile a SwiftPM package to WebAssembly
You can also use SwiftPM for managing packages in the same way as other platforms.
1. Create a package from template
$ swift package init --type executable --name Example
Creating executable package: Example
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/main.swift
2. Build the Project into a WebAssembly binary
You need to pass --triple
option, which indicates that you are building for the target.
$ swift build --triple wasm32-unknown-wasi
If you installed Swift SDK instead of the whole toolchain, you need to use the following command:
$ swift build --swift-sdk <SDK name>
3. Run the produced binary
Just as in the previous section, you can run the produced binary with WebAssembly runtimes like wasmtime
.
$ wasmtime ./.build/wasm32-unknown-wasi/debug/Example.wasm
Hello, world!
Porting code from other platforms to WebAssembly
In the form that's currently standardized and supported by browsers and other hosts, WebAssembly
is a 32-bit architecture. You have to take this into account when porting code from other
platforms, since Int
type is a signed 32-bit integer, and UInt
is an unsigned 32-bit integer
when building with SwiftWasm. You'll need to audit codepaths that cast 64-bit integers to Int
or UInt
, and a good amount of cross-platform test coverage can help with that.
Additionally, there a differences in APIs exposed by the standard C library and Swift core libraries which we discuss in the next few subsections.
WASILibc
module
When porting existing projects from other platforms to SwiftWasm you might stumble upon code that
relies on importing a platform-specific C
library module directly. It looks like import Glibc
on Linux, or import Darwin
on Apple platforms. Fortunately, most of the standard C library
APIs are available when using SwiftWasm, you just need to use import WASILibc
to get access to it.
Most probably you want to preserve compatibility with other platforms, thus your imports would look
like this:
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(WASILibc)
import WASILibc
#endif
Limitations
WebAssembly and WASI provide a constrained environment, which currently does
not directly support multi-threading. Thus, you should not
expect these APIs to work when importing WASILibc
. Please be aware of these limitations when
porting your code, which also has an impact on what can be supported in the Swift
Foundation at the moment.
Swift Foundation and Dispatch
The Foundation core library is available in SwiftWasm, but in a limited capacity. The main reason is that the Dispatch core library is unavailable due to the lack of standardized multi-threading support in WebAssembly and SwiftWasm itself. Many Foundation APIs rely on the presence of Dispatch under the hood, specifically threading helpers. A few other types are unavailable in browsers or aren't standardized in WASI hosts, such as support for sockets and low-level networking, and they had to be disabled. These types are therefore absent in SwiftWasm Foundation:
Type or module | Status |
---|---|
FoundationNetworking | ❌ Unavailable |
FileManager | ✅ Available after 6.0 |
Host | ✅ Partially available after 6.0 |
Notification | ✅ Available after 6.0 |
NotificationQueue | ❌ Unavailable |
NSKeyedArchiver | ✅ Available after 6.0 |
NSKeyedArchiverHelpers | ✅ Available after 6.0 |
NSKeyedCoderOldStyleArray | ✅ Available after 6.0 |
NSKeyedUnarchiver | ✅ Available after 6.0 |
NSNotification | ✅ Available after 6.0 |
NSSpecialValue | ✅ Available after 6.0 |
Port | ✅ Available after 6.0 |
PortMessage | ✅ Available after 6.0 |
Process | ❌ Unavailable |
ProcessInfo | ✅ Partially available after 5.7 |
PropertyListEncoder | ✅ Available after 6.0 |
RunLoop | ❌ Unavailable |
Stream | ✅ Partially available after 6.0 |
SocketPort | ❌ Unavailable |
Thread | ❌ Unavailable |
Timer | ❌ Unavailable |
UserDefaults | ✅ Available after 6.0 |
Related functions and properties on other types are also absent or disabled. We would like to make them available in the future as soon as possible, and we invite you to contribute and help us in achieving this goal!
Creating a Browser App
Currently, the Tokamak UI framework is one of the easiest ways to build a browser app with SwiftWasm. It tries to be compatible with the SwiftUI API as much as possible, which potentially allows you to share most if not all code between SwiftWasm and other platforms.
Requirements
Tokamak relies on the carton
development tool for development and testing.
While you can build Tokamak apps without carton
, that would require a lot of manual steps that are
already automated with carton
.
System Requirements
Installation
- Create a directory for your project and make it current:
mkdir MyApp && cd MyApp
- Initialize the project:
swift package init --type executable
- Add Tokamak and carton as dependencies to your
Package.swift
:
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [.macOS(.v11), .iOS(.v13)],
dependencies: [
.package(url: "https://github.com/TokamakUI/Tokamak", from: "0.11.0"),
.package(url: "https://github.com/swiftwasm/carton", from: "1.0.0"),
],
targets: [
.executableTarget(
name: "MyApp",
dependencies: [
.product(name: "TokamakShim", package: "Tokamak")
]),
]
)
- Add your first view to
Sources/main.swift
:
import TokamakDOM
@main
struct TokamakApp: App {
var body: some Scene {
WindowGroup("Tokamak App") {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
Text("Hello, world!")
}
}
- Build the project and start the development server,
swift run carton dev
can be kept running during development:
swift run carton dev
- Open http://127.0.0.1:8080/ in your browser to see the app
running. You can edit the app source code in your favorite editor and save it,
carton
will immediately rebuild the app and reload all browser tabs that have the app open.
You can also clone the Tokamak repository and run carton dev --product TokamakDemo
in its root directory. This will build the demo app that shows almost all of the currently
implemented APIs.
JavaScript interoperation
JavaScriptKit is a Swift framework to interact with JavaScript through WebAssembly.
You can use any JavaScript API from Swift with this library. Here's a quick example of JavaScriptKit usage in a browser app:
import JavaScriptKit
let document = JSObject.global.document
var divElement = document.createElement("div")
divElement.innerText = "Hello, world"
_ = document.body.appendChild(divElement)
You can also use JavaScriptKit in SwiftWasm apps integrated with Node.js, as there no assumptions that any browser API is present in the library.
JavaScriptKit consists of a runtime library package hosted on npm, and a SwiftPM package for the API on the Swift side. To integrate the JavaScript runtime automatically into your app, we recommend following the corresponding guide for browser apps in our book.
You can get more detailed JavaScriptKit documentation here.
Running async
functions in WebAssembly
On macOS, iOS, and Linux, libdispatch
-based executor is used by default, but libdispatch
is not supported in single-threaded WebAssembly environment.
However, there are still two global task executors available in SwiftWasm.
Cooperative Task Executor
Cooperative Task Executor
is the default task executor in SwiftWasm. It is a simple single-threaded cooperative task executor implemented in Swift Concurrency library.
If you are not familiar with "Cooperative" in concurrent programming term, see its definition for more details.
This executor has an event loop that dispatches tasks until no more tasks are enqueued, and exits immediately after all tasks are dispatched. Note that this executor won't yield control to the host environment during execution, so any host's async operation cannot call back to the Wasm execution.
This executor is suitable for WASI command line tools, or host-independent standalone applications.
// USAGE
// $ swiftc -target wasm32-unknown-wasi -parse-as-library main.swift -o main.wasm
// $ wasmtime main.wasm
@main
struct Main {
static func main() async throws {
print("Sleeping for 1 second... 😴")
try await Task.sleep(nanoseconds: 1_000_000_000)
print("Wake up! 😁")
}
}
JavaScript Event Loop-based Task Executor
JavaScript Event Loop-based Task Executor
is a task executor that cooperates with the JavaScript's event loop. It is provided by JavaScriptKit
, and you need to activate it explicitly by calling a predefined JavaScriptEventLoop.installGlobalExecutor()
function (see below for more details).
This executor also has its own event loop that dispatches tasks until no more tasks are enqueued synchronously. It yields control to the JavaScript side after all pending tasks are dispatched, so JavaScript program can call back to the executed Wasm module. After a task is resumed by callbacks from JavaScript, the executor starts its event loop again in the next microtask tick.
To enable this executor, you need to use JavaScriptEventLoop
module, which is provided as a part of JavaScriptKit
package.
- Ensure that you added
JavaScriptKit
dependency to yourPackage.swift
- Add
JavaScriptEventLoop
dependency to your targets that use this executor
.product(name: "JavaScriptEventLoop", package: "JavaScriptKit"),
- Import
JavaScriptEventLoop
and callJavaScriptEventLoop.installGlobalExecutor()
before spawning any tasks to activate the executor instead of the default cooperative executor.
Note that this executor is only available on JavaScript host environment.
See also JavaScriptKit
package README
for more details.
import JavaScriptEventLoop
import JavaScriptKit
JavaScriptEventLoop.installGlobalExecutor()
let document = JSObject.global.document
var asyncButtonElement = document.createElement("button")
_ = document.body.appendChild(asyncButtonElement)
asyncButtonElement.innerText = "Fetch Zen"
func printZen() async throws {
let fetch = JSObject.global.fetch.function!
let response = try await JSPromise(fetch("https://api.github.com/zen").object!)!.value
let text = try await JSPromise(response.text().object!)!.value
print(text)
}
asyncButtonElement.onclick = .object(JSClosure { _ in
Task {
do {
try await printZen()
} catch {
print(error)
}
}
return .undefined
})
Testing your app
You can write a test suite for your SwiftWasm app or library, or run an existing test suite
written for XCTest
if you port existing code to SwiftWasm. Your project has to have a
Package.swift
package manifest for this to work. We assume that you use SwiftPM to build your
project and that you have a working package manifest. Please follow our SwiftPM guide for new projects.
A simple test case
Let's assume you have an Example
target in your project that you'd like to test. Your
Package.swift
should also have a test suite target with a dependency on the library target. It
would probably look like this:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "Example",
products: [
.library(name: "Example", targets: ["Example"]),
],
targets: [
.target(name: "Example"),
.testTarget(name: "ExampleTests", dependencies: ["Example"]),
]
)
Now you should make sure there's Tests/ExampleTests
subdirectory in your project.
If you don't have any files in it yet, create ExampleTests.swift
in it:
import Example
import XCTest
final class ExampleTests: XCTestCase {
func testTrivial() {
XCTAssertEqual(text, "Hello, world")
}
}
This code assumes that your Example
defines some text
with "Hello, world"
value
for this test to pass. Your test functions should all start with test
, please see XCTest
documentation
for more details.
XCTest limitations in the SwiftWasm toolchain
As was mentioned in our section about Swift Foundation, multi-threading and
file system APIs are currently not available in SwiftWasm. This means that XCTestExpectation
and test hooks related to Bundle
(such as testBundleWillStart(_:)
and testBundleDidFinish(_:)
)
are not available in test suites compiled with SwiftWasm. If you have an existing test suite you're
porting to WebAssembly, you should use #if os(WASI)
directives to exclude places where you use
these APIs from compilation.
Building and running the test suite with SwiftPM
You can build your test suite by running this command in your terminal:
$ swift build --build-tests --triple wasm32-unknown-wasi
If you're used to running swift test
to run test suites for other Swift platforms, we have to
warn you that this won't work. swift test
doesn't know what WebAssembly environment you'd like to
use to run your tests. Because of this building tests and running them are two separate steps when
using SwiftPM
. After your tests are built, you can use a WASI-compatible host such as
wasmtime to run the test bundle:
$ wasmtime --dir . .build/wasm32-unknown-wasi/debug/ExamplePackageTests.wasm
(--dir .
is used to allow XCTest to find Bundle.main
resources placed alongside the executable file.)
As you can see, the produced test binary starts with the name of your package followed by
PackageTests.wasm
. It is located in the .build/debug
subdirectory, or in the .build/release
subdirectory when you build in release mode.
Building and running the test suite with carton
If you use carton
to develop and build your app, as described in our guide
for browser apps, just run swift run carton test
in the
root directory of your package. This will automatically build the test suite and run it with a WASI runtime for you.
Configuring Visual Studio Code with WebAssembly SDK
This guide will help you configure Visual Studio Code (VSCode) to use the Swift SDK for WebAssembly.
Note: This guide assumes you have already installed the Swift SDK for WebAssembly from the development snapshot release.
Prerequisites
- Visual Studio Code
- Swift for Visual Studio Code
- Swift Development Snapshot (
swift-DEVELOPMENT-SNAPSHOT-2024-09-04-a
or later) - Swift SDK for WebAssembly
Configure your SwiftPM package
- Open your Swift package in VSCode.
- Create a
.vscode/settings.json
with the following content:
{
"swift.path": "<path-to-swift-toolchain-from-swift.org>/usr/bin",
}
Note: Replace
<path-to-swift-toolchain-from-swift.org>
with the path to the development snapshot Swift toolchain you downloaded from swift.org/install.
- Create a
.sourcekit-lsp/config.json
with the following content:
{
"swiftPM": {
"swiftSDK": "<Swift SDK id>"
}
}
Note: Replace
<Swift SDK id>
with the Swift SDK id you installed using theswift sdk install
command. You can find the installed SDK id by runningswift sdk list
.
- Reload the VSCode window by pressing
Cmd + Shift + P
and selectingReload Window
.
That's it! You can now build and auto-complete your Swift package using the Swift SDK for WebAssembly.
Debugging
Debugging is one of the most important parts of application development. This section describes debugging tools compatible with SwiftWasm.
These tools are DWARF-based, so you need to build your application with DWARF sections enabled.
If you are using carton bundle
, you can use the --debug-info
flag to enable debugging with optimized application.
If you are using swift build
, it is enabled by default.
Chrome DevTools
When you are debugging a web browser application, Chrome DevTools is a good tool to use. It allows you to put breakpoints, step through at Swift source code level.
Please follow the steps below to configure Chrome DevTools for SwiftWasm:
- Install
C/C++ DevTools Support (DWARF)
extension in your Chrome - Enable
WebAssembly Debugging: Enable DWARF support
inExperiments
pane of DevTools settings
See the DevTools team's official introduction for more details about the extension.
Note that the function names in the stack trace are mangled. You can demangle them using swift demangle
command.
Unfortunately, variable inspection is unavailable since Swift depends on its own mechanisms to do that instead of DWARF's structure type feature. (See this thread for more details)
wasminspect
wasminspect can help in the investigation if the debugged binary does not rely on integration with JavaScript. We recommend splitting your packages into separate executable targets, most of which shouldn't assume the availability of JavaScript to make debugging easier.
Troubleshooting
These are some common issues you may run into while using SwiftWasm.
If you are having trouble that is not listed here, try searching for it in the SwiftWasm issue tracker. If you are still having trouble, please file an issue or contact us at the community Discord server.
RuntimeError: memory access out of bounds
If you encounter this error, there are 3 possible causes:
1. You are trying to access invalid memory in your code
In this case, you need to make sure which memory operations are invalid in your code by UnsafePointer
or C code.
2. You missed program initialization defined in WASI Application ABI.
If your application is used as a library, you need to follow WASI reactor ABI.
Please make sure that you followed it by reviewing the Exporting function guide
3. Stack overflow is occurring.
If you are using --stack-first
linker option (carton uses it by default), you can face RuntimeError: memory access out of bounds
error due to stack overflow.
You have two options to solve this issue:
-
Avoid recursive calls if possible.
-
Extend the stack size by linker option
-z stack-size=<size>
. The default stack size is 64KBswift build --triple wasm32-unknown-wasi -Xlinker -z -Xlinker stack-size=131072
-
Identify which function consumes a lof of stack space by some tools like wasm-stack-consumer
See also: LLVM Bugzilla – wasm32: Allow placing the stack before global data
fatal error: 'stdlib.h' file not found
If you encounter this error, please make sure that:
- You are using SwiftWasm toolchain (if you installed it as a toolchain, not as a Swift SDK)
- Check
which swift
and make sure it points to the SwiftWasm toolchain.
- Check
- You are using the correct target triple:
--triple wasm32-unknown-wasi --static-swift-stdlib
if you installed as a toolchain--swift-sdk wasm32-unknown-wasi
if you installed as a Swift SDK
`error: missing external dependency '.../usr/lib/swift/wasi/static-executable-args.lnk'
You may encounter this error while building with Swift SDK for WebAssembly and swiftc
driver command. Unfortunately, Swift SDK does not support building with swiftc
command yet, so you need to use swift build
Swift Package Manager command instead.
e.g. swift build --swift-sdk <SDK name>
See also: Compile a SwiftPM package to WebAssembly
Examples
This section shows you example usage of our toolchain.
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))
Exporting function for host environment
Swift 5.10 or earlier
You can expose a Swift function for host environment using special attribute and linker option.
// File name: lib.swift
@_cdecl("add")
func add(_ lhs: Int, _ rhs: Int) -> Int {
return lhs + rhs
}
You need to compile the Swift code with linker option --export
.
To call the exported function as a library multiple times, you need to:
- Compile it as a WASI reactor execution model.
The default execution model is command, so you need to pass
-mexec-model=reactor
to linker. - Call
_initialize
function before interacting with the instance.
$ swiftc \
-target wasm32-unknown-wasi \
-parse-as-library \
lib.swift -o lib.wasm \
-Xlinker --export=add \
-Xclang-linker -mexec-model=reactor
Then, you can use the exported function from host environment.
// File name: main.mjs
import { WASI, File, OpenFile, ConsoleStdout } from "@bjorn3/browser_wasi_shim";
import fs from "fs/promises";
// 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("lib.wasm");
// Instantiate the WebAssembly file
const { instance } = await WebAssembly.instantiate(wasmBinary, {
wasi_snapshot_preview1: wasi.wasiImport,
});
// Initialize the instance by following WASI reactor ABI
wasi.initialize(instance);
// Get the exported function
const addFn = instance.exports.add;
console.log("2 + 3 = " + addFn(2, 3))
If you use SwiftPM package, you can omit linker flag using clang's __atribute__
. Please see swiftwasm/JavaScriptKit#91 for more detail info
Swift 6.0 or later
If you use Swift 6.0 or later, you can use @_expose(wasm, "add")
and omit the --export
linker flag.
// File name: lib.swift
@_expose(wasm, "add")
@_cdecl("add") // This is still required to call the function with C ABI
func add(_ lhs: Int, _ rhs: Int) -> Int {
return lhs + rhs
}
Then you can compile the Swift code with the following command without --export
linker flag.
$ swiftc \
-target wasm32-unknown-wasi \
-parse-as-library \
lib.swift -o lib.wasm \
-Xclang-linker -mexec-model=reactor
Example Projects
You can learn more practical usage of our toolchain in swiftwasm/awesome-swiftwasm
Contribution Guide
Repositories
swiftwasm/swiftwasm-build
The main development repository for the SwiftWasm project. It contains the build script and patches for building the Swift compiler and standard library for WebAssembly. See the README for more information.
swiftwasm/icu4c-wasi
Build script and patches for building ICU project for WebAssembly. The required changes to build it were merged to the upstream repository.
How to build toolchain
This document describes how to build the toolchain for WebAssembly. This is just a quick guide, so if you want to know more about the toolchain, it might be good entry point to read continuous integration scripts. Or you can ask questions in GitHub issues or SwiftWasm Discord server (see the official website for the link).
1. Checkout the project source code.
$ mkdir swiftwasm-source
$ cd swiftwasm-source
$ git clone https://github.com/swiftwasm/swiftwasm-build.git
$ cd swiftwasm-build
$ ./tools/build/install-build-sdk.sh main
$ ./tools/git-swift-workspace --scheme main
2. Install required dependencies
- Please follow the upstream instruction
- (If you want to run test suite) Install
Wasmtime
If you are using macOS, please ensure that you don't have llvm
package installed via Homebrew.
3. Build the toolchain
./tools/build/build-toolchain.sh
This script will build the following components:
- Swift compiler that can compile Swift code to WebAssembly support
- Swift standard library and core libraries for WebAssembly
Build on Docker
You can also build the toolchain on Docker image used in CI. Note that you have already checked out the source code in the previous step.
$ docker volume create oss-swift-package
$ docker run --name swiftwasm-ci-buildbot \
-dit \
-w /home/build-user/ \
-v ./swiftwasm-source:/source \
-v oss-swift-package:/home/build-user \
ghcr.io/swiftwasm/swift-ci:main-ubuntu-20.04
$ docker exec swiftwasm-ci-buildbot /bin/bash -lc 'env; cp -r /source/* /home/build-user/; ./swiftwasm-build/tools/build/ci.sh main'
$ docker cp swiftwasm-ci-buildbot:/home/build-user/swift-wasm-DEVELOPMENT-SNAPSHOT-*-ubuntu-20.04.tar.gz .