Swift already runs in the browser today. Using the WASI target with JavaScriptKit and browser-side WASI polyfills, the community has built projects ranging from ElementaryUI (a SwiftUI-inspired web framework) to real-time graphics with WebGPU. The WebAssembly vision document noted that "browser-specific use cases remain to be addressed in a separate document."
This pitch proposes adding the Emscripten target (wasm32-unknown-emscripten) to the Swift toolchain, complementing the existing WASI target. For new Swift projects targeting WebAssembly, WASI remains the recommended path as a standardized, portable system interface and the long-term direction for the ecosystem. Emscripten serves a different need: it provides an intermediate migration path for projects that depend on C/C++ libraries requiring existing POSIX APIs and/or Emscripten. It enables them to adopt Swift incrementally rather than waiting for those dependencies to be ported to WASI.
Motivation
Swift's existing WASI target, combined with browser-side WASI polyfills and JavaScriptKit, already enables Swift in the browser. However, a large class of projects depend on C/C++ libraries that have already been ported to Emscripten but not to WASI.
As a concrete example, SwiftGodot provides Swift bindings for the Godot game engine, which uses Emscripten for its web export. Swift modules compiled for WASI cannot link against Godot's Emscripten-built runtime due to ABI and libc++ incompatibilities — a dedicated Emscripten target would remove this barrier. Similarly, large LLVM-based tools like MLIR, clang-repl, and clangd have been compiled to run in the browser through Emscripten — Swift code that needs to interoperate with such projects would require matching the Emscripten ABI.
Emscripten's Library Ecosystem
Emscripten provides two categories of library support, both accessible to Swift through C interop.
Compatibility shims are built into Emscripten's system libraries and translate POSIX and standard C APIs into browser-compatible implementations: a musl-based libc, a virtual filesystem (WasmFS), POSIX threads via Web Workers, OpenGL ES → WebGL translation, OpenAL audio emulation, and bindings to browser APIs (Fetch, WebSocket, Web Audio, HTML5 Canvas and input events). Code that uses these standard APIs can often compile for Emscripten without modification.
Library ports are a curated collection of third-party C/C++ libraries pre-built for WebAssembly. Notable ports include SDL2 (with image, audio, font, and networking addons), SQLite 3, zlib, FreeType, HarfBuzz, libpng, libjpeg, and Bullet Physics. These are installed on demand by emcc when referenced via -sUSE_SDL=2 or similar options.
For example, a Swift project depending on SQLite and zlib can target wasm32-unknown-emscripten and have those dependencies satisfied automatically.
Relationship to WASI
WASI and Emscripten serve different but complementary roles in the WebAssembly ecosystem.
WASI provides a standardized, vendor-neutral system interface for WebAssembly modules. The upcoming WASI 0.3 specification, built on the Component Model, introduces first-class support for asynchronous I/O and will enable rich composability between modules written in different languages. This is the long-term direction for portable WebAssembly, and Swift's investment in WASI aligns with this trajectory.
Emscripten predates WASI and takes a different approach: rather than defining a new system interface, it emulates POSIX APIs by translating them into JavaScript calls at link time. This makes it exceptionally practical for porting existing C/C++ codebases to the browser — codebases that may use blocking I/O, threads, or other POSIX features that WASI does not yet fully support.
Shared Foundation
Emscripten and WASI are not entirely separate worlds. Emscripten implements a subset of the WASI Preview 1 (wasi_snapshot_preview1) import interface internally, using it as its own system call layer for clock operations, file descriptor management, and environment access. WASI Preview 1 is a stable, frozen specification — widely supported across runtimes and no longer receiving new features, with development effort focused on WASI 0.2 and 0.3. Its stability makes it a reliable shared foundation: both targets use the same low-level syscall ABI for these core operations, so Swift's platform-abstraction logic requires minimal divergence between them.
The Emscripten target is not intended to compete with or replace WASI. Rather, it provides a practical path for mixed Swift/C/C++ codebases in the browser today, while WASI 0.3 and the Component Model mature into the portable, composable solution for tomorrow. Developers with C/C++ dependencies can adopt Emscripten now and migrate components to WASI as the ecosystem evolves.
Current State
Experimental Emscripten target support has been implemented in a development branch of the Swift toolchain, behind the --build-emscripten-stdlib build flag. The implementation is not yet merged into the main branch. It includes:
- Target triple:
wasm32-unknown-emscripten, recognized by the Swift compiler and driver. - Standard library and runtime: Built and tested against the Emscripten sysroot, with platform-specific adaptations for command-line argument handling, single-threaded concurrency, and the
EmscriptenLibcplatform module. - Driver support: The Swift driver invokes
emcc(the Emscripten compiler driver) as the linker, with appropriate settings forGLOBAL_BASE,TABLE_BASE, and stack size that match the Swift runtime's expectations. - Test suite: Over 1,000 lit tests pass for the Emscripten target, covering the standard library, concurrency runtime, IRGen, AutoDiff, DebugInfo, and embedded Swift.
Asyncify: Bridging Synchronous and Asynchronous Worlds
A key challenge when bringing native code to the browser is that many C/C++ APIs are synchronous and blocking (file I/O, sleep, network requests), but the browser's single-threaded event loop cannot block. Emscripten addresses this with Asyncify, a compiler transform that can suspend and resume WebAssembly execution across asynchronous JavaScript calls.
With Asyncify enabled, a synchronous C call like emscripten_sleep(1000) is transparently transformed into an operation that yields control to the browser's event loop and resumes after the specified delay. This same mechanism extends to any JavaScript async operation wrapped with Emscripten's EM_ASYNC_JS macro.
For Swift, the concurrency story in the browser is handled differently. JavaScriptKit provides JavaScriptEventLoop, a Swift SerialExecutor that integrates directly with the browser's event loop by scheduling async tasks as JavaScript microtasks. This is the intended approach for Swift Concurrency on WebAssembly in the browser, and adapting it for the Emscripten target is discussed under Future Directions. Asyncify and JSPI (JavaScript Promise Integration, an emerging WebAssembly proposal for VM-level stack switching) remain useful for the C/C++ portions of a mixed-language project that need to call blocking APIs, but they are not part of the Swift concurrency model.
Proposed Scope
The proposed scope matches what has been implemented (see Current State above):
-
Toolchain integration: The
wasm32-unknown-emscriptentarget triple, withemccas the linker. Cross-compilation SDKs can be produced for this target. -
C interop: Swift code can import and use C/C++ libraries available through Emscripten's sysroot and ports system.
-
Swift SDK distribution: An Emscripten Swift SDK bundle following the same model as the existing WASI Swift SDK (SE-0387). Users install via
swift sdk installand build withswift build --swift-sdk <emscripten-sdk-id>, mirroring the WASI getting-started workflow.
Future Directions
JavaScriptKit for Emscripten
JavaScriptKit is the Swift community's library for bidirectional JavaScript interop from Swift WebAssembly modules. It currently targets WASI, using a custom javascript_kit WebAssembly import module that is resolved by a JavaScript runtime component.
JavaScriptKit's architecture is modular: the Swift-side code communicates through a well-defined set of functions imported from a javascript_kit WebAssembly import module, with the JavaScript side implementing these as a wasmImports object. This design is not inherently WASI-specific — the same import module mechanism works with any WebAssembly host, including Emscripten.
Adapting JavaScriptKit for Emscripten would require:
- Adding
wasm32-unknown-emscriptentarget detection in the build plugin. - Integrating the JavaScriptKit JavaScript runtime with Emscripten's module instantiation flow rather than standalone WASI instantiation.
- Adapting platform conditions from
#if os(WASI)to also cover#if os(Emscripten)where appropriate.
This would give Swift developers on the Emscripten target the same ergonomic JavaScript interop that WASI users already enjoy, while also benefiting from Emscripten's richer browser integration.
Multi-threaded Concurrency
Emscripten supports POSIX threads via Web Workers and SharedArrayBuffer. Enabling multi-threaded Swift Concurrency on Emscripten would allow the default concurrent executor to distribute work across web workers.
WASI 0.3 Migration Path
As WASI 0.3 stabilizes, projects using Emscripten can migrate individual components to WASI without rewriting core application logic — platform-specific integration code would need updating, but the Swift business logic remains the same.
Debugging
Emscripten produces DWARF debug information that browser developer tooling can consume. Improving the debugging experience for Swift on Emscripten — including source mapping, variable inspection, and integration with browser developer tools — is a natural follow-up to basic target support.
Embedded Swift for Emscripten
Enabling Embedded Swift for the wasm32-unknown-emscripten target would combine minimal binary sizes with Emscripten's library ports and POSIX shims. This is being explored and would benefit use cases where binary size is critical but access to Emscripten's C/C++ ecosystem is also needed.
Alternatives Considered
WASI-only approach
One alternative is to target only WASI and rely on browser-side WASI polyfills to run WASI modules in the browser. While this works for many cases, it does not provide access to Emscripten's library ecosystem. For projects with existing C/C++ dependencies built with Emscripten, the WASI-only approach requires porting each dependency individually.
Acknowledgements
This work builds on years of community effort on Swift for WebAssembly, including the SwiftWasm project and contributions from many individuals to the upstream Swift toolchain.