🪟 Windows is supported alongside macOS and Linux.
Swift Wasmtime is a SwiftPM wrapper around the official Wasmtime C API. The package vendors Wasmtime C API libraries, exposes a small Swift 6 API, and keeps the C ownership rules explicit.
Project Status
This is a community build/test package, not an official Wasmtime distribution. It exists to explore a small, Swift-native API over Wasmtime while keeping usage from SwiftPM projects as straightforward as possible.
The current vendoring model is intentionally pragmatic and somewhat subpar:
Wasmtime is implemented in Rust and distributed to C consumers as prebuilt C API
artifacts, while SwiftPM does not currently have a first-class cross-platform
story for vendored Rust-built C ABI libraries. This package therefore vendors
the official Wasmtime C API artifacts where upstream publishes them. Apple
platforms are packaged as a Wasmtime.xcframework; Linux and Windows use
vendored platform libraries plus linker search paths selected by platform and
architecture. Ease of use from SwiftPM is important to this project, even though
the packaging tradeoff is not as clean as a native SwiftPM C/C++ source target.
Upstream Rust toolchain requirements apply when building Wasmtime itself from
source; normal Swift package consumers use the prebuilt C API artifacts vendored
here.
Current Scope
- Core runtime wrappers:
Config,Engine,Store,Module,ModuleImport,ModuleExport,ModuleExternType,Instance,InstancePre,Linker,Extern,Func,GlobalType,Global,TableElementKind,TableElement,TableType,Table,MemoryType,Memory,Value,Trap,WasmtimeError, andWasiConfig. - Swift 6 thread-safe surface:
EngineOptionsfor sendable engine configuration,WasiOptionsfor sendable WASI configuration, andWasmtimeRuntimefor actor-serialized store execution. - Config knobs for component model, core Wasm proposals, SIMD/relaxed SIMD, compilation strategy including Winch selection, Cranelift optimization, regalloc algorithm, flags/debug verification/NaN canonicalization, profiling strategy, target triples, trap handling, debug info, native unwind info, macOS Mach-port handling, parallel compilation, linear-memory reservation/guard sizing and copy-on-write initialization, fuel consumption, epoch interruption, and maximum Wasm stack size.
- Resource-control APIs for fuel, epoch deadlines, epoch interruption callbacks,
store resource limits, explicit store GC, and engine epoch increments, exposed
both through low-level
Store/Enginewrappers andWasmtimeRuntime. - Early component-model wrappers:
Component,ComponentLinker,ComponentInstance, andComponentFunctionfor compiling components, registering WASIp2/WASI HTTP host interfaces, instantiating components, and calling zero-parameter, zero-result component functions. - Module compilation from Wasm bytes,
Data, or WAT text via Wasmtime'swat2wasmC API, validation of Wasm bytes before compilation, shallow module cloning, compiled module serialization/deserialization artifacts, plus import/export type metadata inspection for functions, globals, tables, and memories. - Direct instantiation, linker instantiation, exported function lookup, checked
calls for
i32,i64,f32,f64, and SIMD-backedv128, function signature introspection, trap/error conversion with typedTrapCode, host-side trap/error helpers,WasmFrame/WasmTracediagnostics, and WASI configuration including arguments, environment, stdio files, stdin bytes, stdout/stderr callbacks, preopened directories, network inheritance, and IP name lookup. - Linear memory support through
MemoryType,Memory, exported memory lookup, safe copy-based memory reads/writes, memory growth, and actor-isolatedWasmtimeRuntimememory helpers. - Numeric scalar and
v128global support throughGlobalType,Global, exported global lookup, immutable and mutable global reads/writes, linker-defined globals, and actor-isolatedWasmtimeRuntimeglobal helpers. - Table support through
TableType,Table, exported table lookup, table size, table growth, linker-defined tables, function references, and nullfuncref/externrefelements, including actor-isolatedWasmtimeRuntimetable helpers. Arbitrary non-null hostexternrefpayloads are intentionally deferred until broader reference value modeling exists. - General extern lookup through
Instance.export(named:),Instance.export(at:),Instance.exports(),Extern, andLinker.get(store:module:name:). Functions, globals, tables, and memories have first-class wrappers today; tags and shared memories are reported by kind until their dedicated wrappers land. - Linker support for WASI registration, import shadowing, host functions,
store-bound functions, defining unknown imports as traps or default values,
store-bound globals and memories, defining an instantiated module namespace,
registering a module by name, cloning linker definitions, resolving a named
module's default function, instantiating modules through that linker, and
pre-instantiating modules with
InstancePrefor reuse across compatible stores. The remaining low-level extern definition surface for tags is not exposed yet. - Vendored Wasmtime version:
v45.0.2, including the upstream WASIp1fd_renumberleak fix for GHSA-3p27-qvp9-27qf /CVE-2026-54786. - Vendored platforms: macOS, iOS/iPadOS, and tvOS through
Wasmtime.xcframework, plus Linux and Windows onarm64/x86_64.
Apple Platform Status
SwiftPM consumers on macOS, iOS, iPadOS, and tvOS can depend on the package
directly, and SwiftPM will select the vendored Wasmtime.xcframework slice for
the target platform. The package does not build Wasmtime from Rust source inside
consumer apps.
macOS uses the official upstream Wasmtime C API release archives repackaged into the XCFramework. iOS, iPadOS, and tvOS support remains experimental.
On iOS, iPadOS, and tvOS, Engine creation is forced to Wasmtime's Pulley
interpreter (pulley64) so guest WebAssembly runs without native JIT
execution. This is the intended path for Apple's mobile platform restrictions,
but it means behavior and performance should be validated against the real app
workload rather than assumed from desktop or native-JIT Wasmtime.
The current Apple slices are:
- macOS:
arm64andx86_64 - iOS/iPadOS device:
arm64 - iOS/iPadOS simulator:
arm64andx86_64 - tvOS device:
arm64 - tvOS simulator:
arm64
iPadOS uses the iOS XCFramework slices. tvOS is more provisional than iOS and
iPadOS: the full test suite passes on the tvOS simulator and the package
cross-builds for tvOS device targets, but this project has not yet added a
physical Apple TV, TestFlight, or App Store validation gate. Wasmtime also does
not publish official iOS or tvOS C API artifacts for v45.0.2, so these slices
are built from source by this repository's vendoring script. watchOS is not
supported.
Runtime Safety
Prefer WasmtimeRuntime when values cross Swift concurrency domains. It keeps
store-bound Wasmtime handles inside an actor and serializes access to the store.
The lower-level wrappers mirror Wasmtime's C API more directly. Config,
Store, Instance, InstancePre, Linker, Func, WasiConfig, and
Caller are not Sendable; keep each store-bound object graph on one
serialized execution path and do not call into it concurrently.
Low-level handles should come from the same engine/store graph. For example, use
a Module with a Store created from the same Engine, and use store-bound
functions and instances only with the Store that owns them.
Module.deserialize(engine:serialized:), Module.deserialize(engine:data:),
and Module.deserializeFile(engine:path:) must only consume trusted artifacts
previously produced by Module.serialize() for a compatible Wasmtime engine,
version, target platform, and configuration. They are not safe APIs for loading
arbitrary user-controlled bytes.
InstancePre is created by Linker.instantiatePre(module:) after import
resolution has succeeded. It can instantiate into multiple compatible stores,
but each returned Instance is still bound to the store passed to
InstancePre.instantiate(store:).
Config and WasiConfig are consumed by Engine.init(config:) and
Store.setWasi(_:) respectively. After consumption, using the same object again
is a programmer error. Prefer EngineOptions and WasiOptions for reusable,
sendable configuration values.
The public API is still intentionally small and pre-release in spirit. Public
enums such as Value, ValueKind, WasmtimeError, and ExternKind may gain
cases as more Wasmtime C API surface is wrapped.
Trap diagnostics are exposed as Swift value snapshots. WasmFrame and
WasmTrace copy the useful metadata from Wasmtime frames, including function
indexes, offsets, and optional names, but they do not expose borrowed frame
handles or store-bound frame instance handles. This keeps diagnostic values
Sendable and safe to retain after the original trap or error has been
released.
On iOS, iPadOS, and tvOS, Engine creation always targets Wasmtime's Pulley
interpreter (pulley64) so guest code runs without native JIT execution.
Cranelift remains present in the vendored C API so this package can still
compile WAT/Wasm inputs into Pulley bytecode on device.
Importing From SwiftPM
Add the package dependency to your target. On macOS, iOS, iPadOS, and tvOS, the
package links the vendored Wasmtime.xcframework automatically.
On Linux and Windows, also point your target at the vendored Wasmtime library directory in SwiftPM's checkout. This mirrors the workaround used by downstream SwiftPM packages that need to stay inside SwiftPM without a system Wasmtime install.
SwiftPM may still warn about this package's own relative Vendor/... search
path when it is built as a dependency; the consumer target search path below is
the path that makes the final link step succeed for Linux and Windows.
// swift-tools-version: 6.3 import PackageDescription #if os(Linux) let wasmtimeOS = "linux" #elseif os(Windows) let wasmtimeOS = "windows" #else #error("this manual linker search path is only needed on Linux and Windows") #endif #if arch(arm64) let wasmtimeArch = "aarch64" #elseif arch(x86_64) let wasmtimeArch = "x86_64" #else #error("swift-wasmtime currently supports arm64 and x86_64") #endif let wasmtimeVersion = "v45.0.2" let wasmtimeLibraryPath = ".build/checkouts/swift-wasmtime/Vendor/Wasmtime/\(wasmtimeVersion)/\(wasmtimeArch)-\(wasmtimeOS)/lib" let package = Package( name: "MyPackage", dependencies: [ .package( url: "https://github.com/OpenCow42/swift-wasmtime.git", .upToNextMajor(from: "45.0.2") ), ], targets: [ .target( name: "MyTarget", dependencies: [ .product(name: "Wasmtime", package: "swift-wasmtime"), ], linkerSettings: [ .unsafeFlags(["-L", wasmtimeLibraryPath]), ] ), ], swiftLanguageModes: [.v6] )
For macOS, iOS, iPadOS, and tvOS consumer targets, omit the linkerSettings
block above.
Then import and use the Swift module:
import Wasmtime let engine = try Engine() let store = try Store(engine: engine) let module = try Module( engine: engine, wat: """ (module (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add)) """ ) let instance = try Instance(store: store, module: module) let add = try instance.exportedFunction(named: "add") let result = try add.call([.i32(20), .i32(22)])
For code that crosses Swift concurrency domains, prefer the actor runtime:
import Wasmtime let runtime = try WasmtimeRuntime() let instance = try await runtime.instantiate( wat: """ (module (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add)) """ ) let result = try await runtime.call("add", in: instance, arguments: [.i32(20), .i32(22)])
The actor runtime also exposes the package's WASI, linker, and early component workflows without leaking store-bound handles across concurrency domains:
let wasi = WasiOptions( arguments: ["guest.wasm"], environment: ["LOG": "debug"], standardInputBytes: Array("request body".utf8), standardOutputHandler: { output in print(String(decoding: output, as: UTF8.self)) return output.count } ) try await runtime.setWasi(wasi) let linked = try await runtime.instantiateWithLinker( module, defineWasi: true, defineUnknownImportsAsDefaultValues: false ) try await runtime.call("_start", in: linked) let hostBacked = try await runtime.instantiateWithLinker( module, hostFunctions: [ RuntimeHostFunction( module: "host", name: "double", parameters: [.i32], results: [.i32] ) { arguments in guard case .i32(let value) = arguments[0] else { throw WasmtimeError.api(message: "unexpected argument", exitStatus: nil) } return [.i32(value * 2)] }, ] )
Low-level Linker.defineFunction callbacks are @Sendable. The Caller
value passed to a host callback is only valid for that callback invocation;
copy any data you need from guest memory before returning.
let componentRuntime = try WasmtimeRuntime( options: EngineOptions(isComponentModelEnabled: true) ) let component = try await componentRuntime.compileComponent(wat: componentWat) let componentInstance = try await componentRuntime.instantiateComponent(component) try await componentRuntime.call("run", in: componentInstance)
Version Tags
Git release tags use SwiftPM-friendly semantic versions without a leading v.
For example, a package tag like 45.0.2 matches the vendored Wasmtime
v45.0.2 release. Upstream Wasmtime still uses v-prefixed tags, so scripts
and vendored paths keep the upstream spelling where they interact with
Bytecode Alliance release assets.
License
Swift Wasmtime is licensed under Apache-2.0 WITH LLVM-exception. The vendored
Wasmtime C API artifacts and headers are provided by the Bytecode Alliance
Wasmtime project under the same license. See LICENSE and
THIRD_PARTY_NOTICES.md.
Build And Test
For local development with Xcode on macOS, keep Swift and Clang module caches inside the workspace and run the normal SwiftPM test gate:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ CLANG_MODULE_CACHE_PATH="$PWD/.build/clang-module-cache" \ swift test
The macOS package path links Wasmtime through the vendored XCFramework, so it
does not require SwiftPM's sandbox to be disabled. If a nested automation or
outer sandbox blocks SwiftPM's own sandbox integration, add
--disable-sandbox as a local fallback.
iOS/iPadOS simulator test gate:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ CLANG_MODULE_CACHE_PATH="$PWD/.build/clang-module-cache" \ xcodebuild test \ -workspace .swiftpm/xcode/package.xcworkspace \ -scheme Wasmtime \ -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.5' \ -derivedDataPath .build/xcode-derived
tvOS simulator test gate:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ CLANG_MODULE_CACHE_PATH="$PWD/.build/clang-module-cache" \ xcodebuild test \ -workspace .swiftpm/xcode/package.xcworkspace \ -scheme Wasmtime \ -destination 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=26.5' \ -derivedDataPath .build/xcode-derived
Coverage:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
CLANG_MODULE_CACHE_PATH="$PWD/.build/clang-module-cache" \
scripts/test-coverage.shThe validation goal is 100% line coverage for Sources/Wasmtime.
scripts/test-coverage.sh enforces a 100% threshold by default and excludes the
vendored C headers, C shim, fixtures, and package plumbing. Lines marked
coverage:ignore are narrow defensive C-interop branches that cannot be reached
deterministically from Swift without faking Wasmtime allocation failure or
intentionally triggering a process abort.
Vendoring Wasmtime
To refresh the vendored C API artifacts:
scripts/vendor-wasmtime.sh v45.0.2
The script downloads release metadata from GitHub, reads the official asset
digests, downloads the upstream C API archives where Wasmtime publishes them,
verifies SHA256 checksums, copies headers, preserves the upstream license, and
stores platform libraries under Vendor/Wasmtime.
On macOS, the script also packages the official aarch64-macos and
x86_64-macos release archives into
Vendor/Wasmtime/v45.0.2/Wasmtime.xcframework; it does not keep separate
vendored macOS library directories outside the XCFramework.
Wasmtime does not publish iOS or tvOS C API archives for v45.0.2, so the
script downloads the matching source release, builds aarch64-apple-ios,
aarch64-apple-ios-sim, x86_64-apple-ios, aarch64-apple-tvos, and
aarch64-apple-tvos-sim static libraries with Xcode and Rust, and adds them to
the same XCFramework. The iPadOS build uses the iOS XCFramework slices.