Most WebAssembly runtimes are built for servers and clouds: Rust or C++ cores, JIT compilers, throughput benchmarks. WasmKit comes from a different direction — it's a WebAssembly runtime written in pure Swift, from the SwiftWasm project, designed to be embedded in Swift applications. That direction turns out to matter more than it sounds, because WasmKit's most interesting property is where it can run: everywhere Swift runs, including the one major platform where JIT-based runtimes are blocked by both OS enforcement and platform policy — iOS.

What is WasmKit?

WasmKit is an open-source WebAssembly runtime written entirely in Swift, maintained by the SwiftWasm project. It executes WASM with a register-machine interpreter rather than a JIT (the repo's RegisterMachine.md documents the design), supports WASI Preview 1, and embeds in any Swift app as a plain SwiftPM dependency — including iOS apps, where JIT-based runtimes can't go.

It also ships with a small CLI, and since Swift 6.2 that CLI is bundled inside swift.org toolchains — if you've installed a recent Swift toolchain, you may already have a WebAssembly runtime on your machine. The embedding API is compact. Load a module, instantiate it with imports, call exports:

swift
import WasmKit

let engine = Engine()
let store = Store(engine: engine)
let module = try parseWasm(filePath: "module.wasm")
let instance = try module.instantiate(store: store, imports: imports)
let result = try instance.exports[function: "add"]?([.i32(2), .i32(3)])  // -> [Value]?

Host functions — the way sandboxed WASM calls back into your Swift — are closures you register into the imports, which makes the host/guest boundary feel like ordinary Swift instead of FFI (a deep dive on that boundary).

Why JIT WebAssembly runtimes can't run on iOS

On a server, an interpreter is a performance compromise. On iOS it's the only way to run WebAssembly at all: third-party apps cannot create writable-executable memory (outside narrow carve-outs like the EU's BrowserEngineKit entitlements), so a JIT — wasmtime's Cranelift, wasmer's LLVM backend — has nothing to compile into. An interpreter executes WASM as data: no code generation, no special entitlements. It's the same execution mechanism in-process JavaScript engines in App Store apps already use — JavaScriptCore via JSContext, Hermes, QuickJS all run without JIT. Whether downloaded code passes App Review is a separate policy question (the interpreted-code rules cover it), and it stays the developer's call.

How slow is an interpreted WebAssembly runtime?

Interpreted execution runs one to two orders of magnitude slower than JIT-compiled WASM, which itself trails native — a real cost worth stating plainly. WasmKit is the wrong tool for a database engine or a render loop. It's the right tool when the workload is event-driven logic and the constraint is where the code must run — inside an iOS app, inside a sandboxed plugin host, inside anything that forbids runtime code generation.

WasmKit vs wasmtime vs wasmer vs WAMR

RuntimeWritten inExecutionRuns inside an iOS app?
WasmKitSwiftinterpreteryes
wasmtimeRustCranelift JIT + Pulley portable interpreterinterpreter mode, in principle
wasmerRustLLVM/Cranelift JIT + interpreter backendsyes — via interpreter backends (since 5.0)
WAMRCinterpreter + optional JIT/AOTinterpreter mode, yes
wasm3Cinterpreter (project in maintenance mode)yes
WebKit's WASMC++JIT (in-browser)only as a web page

For a Swift codebase the comparison usually ends earlier than the table: the C and Rust options mean embedding a foreign runtime and hand-writing the bridge layer; WasmKit means package.dependencies += [...] and host functions that are Swift closures.

What people do with it

Three patterns show up repeatedly in WasmKit embedding:

Plugin systems. WASM has become a lingua franca for safe extensibility — run third-party or user-supplied code with no access to anything you didn't import into it (the same model Extism-style plugin frameworks build on). A Swift app gets that sandbox without leaving its language.

Portable logic. Compile a core once (Swift itself compiles to WASM now, as do Rust, Go, and friends), run it across platforms behind one boundary — a shared pricing engine, a validation library, a parser.

Code delivery on locked-down platforms. Interpreters are how OTA-update tooling lives with Apple's no-JIT constraint: Expo and CodePush ride JavaScript engines, Shorebird ships a modified Dart runtime for Flutter, and Patch compiles changed Swift to WebAssembly and runs it in WasmKit on-device — an interpreter being the one execution model that works without runtime code generation (the policy side is covered here).

The takeaway

WebAssembly's pitch was always "portable, sandboxed compute." The runtimes that dominate the conversation optimize the compute; WasmKit optimizes the portable and sandboxed — into places JITs can't follow, in a language the Apple ecosystem already speaks. What carries across all three patterns above is the same short list of properties: pure interpretation, capability-scoped imports, and a memory-safe implementation. If your Swift app needs to run code it didn't ship with — plugins, shared cores, hot fixes — it's the first thing to evaluate.


Related: WebAssembly host functions in Swift · Compiling Swift to WebAssembly · Running Swift async/await inside WebAssembly · Why you can't wasm-merge two Swift WASM modules