iOS does not let ordinary apps compile and execute code at runtime. No JIT, no mmap-and-jump, none of the tricks a browser or a server process can lean on. If you are building a portable WASM capability system and one of your placement targets is a native iOS app, that single platform restriction rules out most WASM runtimes by default. You need an interpreter, not a compiler, and you need it to run inside Apple's sandbox rules without asking for an entitlement you will not get.
We spent the last stretch of work answering a narrower question than "does Traverse run on iOS": can a Swift application call into a Rust static library running the wasmi interpreter, have the host enforce memory and fuel limits on the guest module, and do all of that on a physical iPhone with no Xcode errors and a UI that stays responsive afterward. The answer is yes.
wasmi interpreter layers. Running the digest-pinned Traverse runtime and real application manifests on top of this is later work, tracked below.
Why a native host, not a JIT
Most WASM engines get their speed from a JIT: translate WASM bytecode to native machine code the first time a function runs, then execute that native code on every call after. That is fast, and it is exactly the class of runtime code generation that iOS's code-signing and sandbox model exists to prevent outside of a handful of first-party exceptions like WKWebView's JavaScriptCore. A Traverse capability host embedded in a third-party app does not get that exception.
wasmi sidesteps the problem by staying an interpreter. It walks the WASM bytecode directly instead of generating and executing native instructions at runtime, so it needs nothing from the platform beyond the ability to run ordinary compiled code. That makes it slower than a JIT for CPU-bound workloads, and it makes it viable to ship inside an App Store binary today. For a capability host whose job is to run host-governed business logic under a contract, not to win a benchmark, that trade is the right one.
The architecture we proved
The full delivery path has more layers than the ones we tested. Here is where the proof stops and where the later work picks up:
The Swift app talks to the XCFramework, which calls a deliberately narrow C-compatible surface in the Rust static library, which drives wasmi. Nothing in that chain is speculative anymore; all four layers built, linked, and ran on a physical device. The digest-pinned runtime, application manifests, and independently packaged capability WASM sit on top of that foundation and are the next things to wire in, not things we are claiming today.
Host-owned limits, not guest promises
A capability that is packaged separately and might run on any of several host platforms cannot be trusted to police its own resource usage. If the host does not enforce a ceiling, a buggy or hostile guest module will happily grow its memory without bound or spin forever in a loop, and on a phone that turns into a crash or a frozen UI rather than a server that a load balancer quietly cycles. So the limits live in the host, not the guest, and the guest cannot opt out of them.
The host configures a fixed limit before the guest module runs. When a guest tries to grow past it, wasmi rejects the growth instead of allocating past the boundary.
Execution is metered in fuel units set by the host. A guest that never terminates on its own runs out of fuel and stops, deterministically, without a wall-clock watchdog racing the guest.
What we verified
| Proof | Result |
|---|---|
| Rust static-library build | Built for iPhone, Apple Silicon iOS simulator, and Apple Silicon macOS. |
| Swift interop | Swift imported the XCFramework and called the Rust bridge on macOS. |
| Memory control | Growth past a configured 64 KiB host limit stopped with GrowthOperationLimited. |
| Execution control | A non-terminating module stopped with OutOfFuel. |
| Physical iOS run | The proof app launched with no Xcode errors, displayed success, and remained responsive. |
Hands-on: build and run it yourself
Everything below runs against the #769 branch and worktree. If you are building a portable capability system and iOS is one of your targets, this is the fastest way to see the current state for yourself.
1. Build the XCFramework
This produces target/apple/TraverseSwiftHost.xcframework, with slices for an iPhone device, the Apple Silicon simulator, and Apple Silicon macOS.
2. Run the macOS smoke test
This compiles a small Swift program that imports the XCFramework and calls straight into the Rust static library.
3. Run the resource-control tests
Expect three passing tests, covering the host API version, memory-growth rejection, and fuel exhaustion.
4. Test on a physical iPhone or iPad
- Create a new SwiftUI iOS App in Xcode named
TraverseSwiftHostProof. - Set your development team and a unique bundle identifier.
- Drag
target/apple/TraverseSwiftHost.xcframeworkinto the project. - Add it under Frameworks, Libraries, and Embedded Content as Do Not Embed — it is static.
- Replace the application entry point with the code below.
import SwiftUI import TraverseSwiftHost @main struct TraverseSwiftHostProofApp: App { init() { precondition(traverse_swift_host_abi_version() == 1) precondition(traverse_swift_host_memory_limit_fixture() == 0) precondition(traverse_swift_host_fuel_limit_fixture() == 0) } var body: some Scene { WindowGroup { Text("Traverse Swift host proof passed") } } }
- Connect and unlock the device, trust the Mac if prompted, select it in Xcode, and press Run.
- Record the device model, iOS version, Xcode version, commit SHA, the visible success message, and whether the app stayed responsive after the fixtures ran.
What the device test actually proves
The two fixtures deliberately hand the interpreter a hostile input each. What matters is not that they fail — it's that the app regains control cleanly both times, without a watchdog racing the guest or leaving it running in the background.
Swift calls the memory-limit fixture. The host asks wasmi to grow memory past the configured ceiling. wasmi returns GrowthOperationLimited. The host reports success — the fixture was stopped safely, not crashed into.
Swift calls the fuel-limit fixture. The host runs an infinite loop with a finite fuel budget. wasmi returns OutOfFuel. The host reports success — execution terminated on schedule, not on a timeout.
What's left before this ships
- Select
wasmias the engine through a successor ADR. - Resolve the scoped C-ABI governance follow-up (#771).
- Implement the complete Swift embedder SDK (#647).
- Run the digest-pinned Traverse runtime and real application manifests on top of this host.
- Validate declared capability profiles and cross-host conformance against the other placement targets.
None of that changes what this proof establishes: native iOS execution is now an implementable Traverse and UMA delivery path, not a speculative engine choice. If you're building a portable WASM capability system and iOS was the platform you weren't sure how to reach, this is the branch to go read.