|
| 1 | +# A Vision for WebAssembly Support in Swift |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +WebAssembly (abbreviated [Wasm](https://webassembly.github.io/spec/core/intro/introduction.html#wasm)) is a virtual |
| 6 | +machine instruction set focused on portability, security, and high performance. It is vendor-neutral, designed and |
| 7 | +developed by [W3C](https://w3.org). An implementation of a WebAssembly virtual machine is usually called a |
| 8 | +*WebAssembly runtime*. |
| 9 | + |
| 10 | +One prominent spec-compliant implementation of a Wasm runtime in Swift is [WasmKit](https://github.com/swiftwasm/WasmKit). |
| 11 | +It is available as a Swift package, supports multiple host platforms, and has a simple API for interaction with guest |
| 12 | +Wasm modules. |
| 13 | + |
| 14 | +An application compiled to a Wasm module can run on any platform that has a Wasm runtime available. Despite its origins |
| 15 | +in the browser, it is a general-purpose technology that has use cases in client-side and server-side applications and |
| 16 | +services. WebAssembly support in Swift makes the language more appealing in those settings, and also brings it to the |
| 17 | +browser where it previously wasn't available at all[^1]. It facilitates a broader adoption of Swift in more environments |
| 18 | +and contexts. |
| 19 | + |
| 20 | +The WebAssembly instruction set has useful properties from a security perspective, as it has no interrupts or |
| 21 | +peripheral access instructions. Access to the underlying system is always done by calling explicitly imported |
| 22 | +functions, implementations for which are provided by an imported WebAssembly module or a WebAssembly runtime itself. |
| 23 | +The runtime has full control over interactions of the virtual machine with the outside world. |
| 24 | + |
| 25 | +WebAssembly code and data live in completely separate address spaces, with all executable code in a given module loaded |
| 26 | +and validated by the runtime upfront. Combined with the lack of "jump to address" and a limited set of control flow |
| 27 | +instructions that require explicit labels in the same function body, this makes a certain class of attacks impossible to |
| 28 | +execute in a correctly implemented spec-compliant WebAssembly runtime. |
| 29 | + |
| 30 | +### WebAssembly System Interface and the Component Model |
| 31 | + |
| 32 | +The WebAssembly virtual machine has no in-built support for I/O; instead, a Wasm module's access to I/O is dependent |
| 33 | +entirely upon the runtime that executes it. |
| 34 | + |
| 35 | +A standardized set of APIs implemented by a Wasm runtime for interaction with the host operating system is called |
| 36 | +[WebAssembly System Interface (WASI)](https://wasi.dev). [WASI libc](https://github.com/WebAssembly/wasi-libc) is a |
| 37 | +layer on top of WASI that Swift apps compiled to Wasm can already use thanks to C interop. The current implementation |
| 38 | +of Swift stdlib and runtime for `wasm32-unknown-wasi` triple is based on this C library. It is important for WASI |
| 39 | +support in Swift to be as complete as possible to ensure portability of Swift code in the broader Wasm ecosystem. |
| 40 | + |
| 41 | +In the last few years, the W3C WebAssembly Working Group considered multiple proposals for improving the WebAssembly |
| 42 | +[type system](https://github.com/webassembly/interface-types) and |
| 43 | +[module linking](https://github.com/webassembly/module-linking). These were later subsumed into a combined |
| 44 | +[Component Model](https://component-model.bytecodealliance.org) proposal thanks to the ongoing work on |
| 45 | +[WASI Preview 2](https://github.com/WebAssembly/WASI/blob/main/wasip2/README.md), which served as playground for |
| 46 | +the new design. |
| 47 | + |
| 48 | +The Component Model defines these core concepts: |
| 49 | + |
| 50 | +- A *component* is a composable container for one or more WebAssembly modules that have a predefined interface; |
| 51 | +- *WebAssembly Interface Types (WIT) language* allows defining contracts between components; |
| 52 | +- *Canonical ABI* is an ABI for types defined by WIT and used by component interfaces in the Component Model. |
| 53 | + |
| 54 | +Preliminary support for WIT has been implemented in |
| 55 | +[the `wit-tool` subcommand](https://github.com/swiftwasm/WasmKit/blob/0.0.3/Sources/WITTool/WITTool.swift) of the |
| 56 | +WasmKit CLI. Users of this tool can generate `.wit` files from Swift declarations, and vice versa: Swift bindings from |
| 57 | +`.wit` files. |
| 58 | + |
| 59 | +## Use Cases |
| 60 | + |
| 61 | +We can't anticipate every possible application Swift developers are going to create with Wasm, but we can provide a few |
| 62 | +examples of its possible adoption in the Swift toolchain itself. To quote |
| 63 | +[a GSoC 2024 idea](https://www.swift.org/gsoc2024/#building-swift-macros-with-webassembly): |
| 64 | + |
| 65 | +> WebAssembly could provide a way to build Swift macros into binaries that can be distributed and run anywhere, |
| 66 | +> eliminating the need to rebuild them continually. |
| 67 | +
|
| 68 | +This can be applicable not only to Swift macros, but also for the evaluation of SwiftPM manifests and plugins. |
| 69 | + |
| 70 | +In the context of Swift developer tools, arbitrary code execution during build time can be virtualized with Wasm. |
| 71 | +While Swift macros, SwiftPM manifests, and plugins are sandboxed on Darwin platforms, with Wasm we can provide stronger |
| 72 | +security guarantees on other platforms that have a compatible Wasm runtime available. |
| 73 | + |
| 74 | +The WebAssembly instruction set is designed with performance in mind. A WebAssembly module can be JIT-compiled or |
| 75 | +compiled on a client machine to an optimized native binary ahead of time. With recently accepted proposals to the Wasm |
| 76 | +specification it now supports features such as SIMD, atomics, multi-threading, and more. A WebAssembly runtime can |
| 77 | +generate a restricted subset of native binary code that implements these features with little performance overhead. |
| 78 | + |
| 79 | +Adoption of Wasm in developer tools does not imply unavoidable performance overhead. With security guarantees that |
| 80 | +virtualization brings, there's no longer a need to spawn a separate process for each Swift compiler and SwiftPM |
| 81 | +plugin/manifest invocation. Virtualized Wasm binaries can run in the host process of a Wasm runtime, removing the |
| 82 | +overhead of new process setup and IPC infrastructure. |
| 83 | + |
| 84 | +## Goals |
| 85 | + |
| 86 | +As of March 2024 all patches necessary for basic Wasm and WASI Preview 1 support have been merged to the Swift |
| 87 | +toolchain and core libraries. Based on this, we propose a high-level roadmap for WebAssembly support and adoption in the Swift |
| 88 | +ecosystem: |
| 89 | + |
| 90 | +1. Make it easier to evaluate and adopt Wasm with increased API coverage for this platform in the Swift core libraries. |
| 91 | + Main prerequisite for that is setting up CI jobs for those libraries that run tests for WASI and also Embedded Wasm, where |
| 92 | + possible. As a virtualized embeddable platform, not all system APIs are always available or easy to port to WASI. For example, |
| 93 | + multi-threading, file system access, networking and localization need special support in Wasm runtimes and a certain amount of |
| 94 | + consideration from a developer adopting these APIs. |
| 95 | + |
| 96 | +2. Improve support for cross-compilation in Swift and SwiftPM. We can simplify versioning, installation, and overall |
| 97 | + management of Swift SDKs for cross-compilation in general, which is beneficial not only for WebAssembly, but for all |
| 98 | + platforms. |
| 99 | + |
| 100 | +3. Continue work on Wasm Component Model support in Swift as the Component Model proposal is stabilized. Ensure |
| 101 | + that future versions of WASI are available to Swift developers targeting Wasm. |
| 102 | + |
| 103 | +4. Make interoperability with Wasm components as smooth as C and C++ interop already is for Swift. With a formal |
| 104 | + specification for Canonical ABI progressing, this will become more achievable with time. This includes consuming |
| 105 | + components from, and building components with Swift. |
| 106 | + |
| 107 | +5. Improve debugging experience of Swift code compiled to Wasm. While rudimentary support for debugging |
| 108 | + exists in some Wasm runtimes, we aim to improve it and, where possible, make it as good as debugging Swift code |
| 109 | + compiled to other platforms. |
| 110 | + |
| 111 | +### Proposed Language Features |
| 112 | + |
| 113 | +In our work on Wasm support in Swift, we experimented with a few function attributes that could be considered |
| 114 | +as pitches and eventually Swift Evolution proposals, if the community is interested in their wider adoption. |
| 115 | +These attributes allow easier interoperation between Swift code and other Wasm modules linked with it by a Wasm |
| 116 | +runtime. |
| 117 | + |
| 118 | +## Platform-specific Considerations |
| 119 | + |
| 120 | +### Debugging |
| 121 | + |
| 122 | +Debugging Wasm modules is challenging because Wasm does not expose ways to introspect and control the execution of |
| 123 | +a Wasm module instance, so a debugger cannot be built on top of Wasm itself. Special support from the Wasm execution |
| 124 | +engine is necessary for debugging. |
| 125 | + |
| 126 | +The current state of debugging tools in the Wasm ecosystem is not as mature as other platforms, but there are two |
| 127 | +main directions: |
| 128 | + |
| 129 | +1. [LLDB debugger with Wasm runtime](https://github.com/llvm/llvm-project/pull/77949) supporting GDB Remote Serial Protocol; |
| 130 | +2. [Wasm runtime with a built-in debugger](https://book.swiftwasm.org/getting-started/debugging.html#enhanced-dwarf-extension-for-swift). |
| 131 | + |
| 132 | +The first approach provides an almost equivalent experience to existing debugging workflows on other platforms. It |
| 133 | +can utilize LLDB's Swift support, remote metadata inspection, and serialized Swift module information. However, since |
| 134 | +Wasm is a Harvard architecture and has no way to allocate executable memory space at runtime, implementing expression |
| 135 | +evaluation with JIT in user space is challenging. In other words, GDB stub in Wasm engines need tricky implementations |
| 136 | +or need to extend the GDB Remote Serial Protocol. |
| 137 | + |
| 138 | +The second approach embeds the debugger within the Wasm engine. In scenarios where the Wasm engine is embedded as a |
| 139 | +guest in another host engine (e.g. within a Web Browser), this approach allows seamless debugging experiences with the |
| 140 | +host language by integrating with the host debugger. For example, in cases where JavaScript and Wasm call frames |
| 141 | +are interleaved, the debugger works well in both contexts without switching tools. Debugging tools like Chrome DevTools |
| 142 | +can use DWARF information embedded in Wasm file to provide debugging support. However, supporting Swift-specific |
| 143 | +metadata information and JIT-based expression evaluation will require integrating LLDB's Swift plugin with these |
| 144 | +debuggers in some way. |
| 145 | + |
| 146 | +In summary, debugging in the browser and outside of the browser context are sufficiently different activities to |
| 147 | +require separate implementation approaches. |
| 148 | + |
| 149 | +### Multi-threading and Concurrency |
| 150 | + |
| 151 | +WebAssembly has [atomic operations in the instruction set](https://github.com/WebAssembly/threads) (only sequential |
| 152 | +consistency is supported), but it does not have a built-in way to create threads. Instead, it relies on the host |
| 153 | +environment to provide multi-threading support. This means that multi-threading in Wasm is dependent on the Wasm runtime |
| 154 | +that executes a module. There are two proposals to standardize ways to create threads in Wasm: |
| 155 | + |
| 156 | +(1) [wasi-threads](https://github.com/WebAssembly/wasi-threads), which is already supported by some toolchains, |
| 157 | +runtimes, and libraries but has been superseded; |
| 158 | + |
| 159 | +(2) The new [shared-everything-threads](https://github.com/WebAssembly/shared-everything-threads) proposal is still |
| 160 | +in the early stages, but is expected to be the future of multi-threading in Wasm. |
| 161 | + |
| 162 | +Swift currently supports two threading models in Wasm: single-threaded (`wasm32-unknown-wasi`) and multi-threaded |
| 163 | +using wasi-threads (`wasm32-unknown-wasip1-threads`). Despite the latter supporting multi-threading, Swift Concurrency |
| 164 | +defaults to a cooperative single-threaded executor due to the lack of wasi-threads support in libdispatch. Preparing |
| 165 | +for the shared-everything-threads proposal is crucial to ensure that Swift Concurrency can adapt to future |
| 166 | +multi-threading standards in Wasm. |
| 167 | + |
| 168 | +### 64-bit address space |
| 169 | + |
| 170 | +WebAssembly currently uses a 32-bit address space, but [64-bit address space](https://github.com/WebAssembly/memory64/) |
| 171 | +proposal is already in the implementation phase. |
| 172 | + |
| 173 | +Swift supports 64-bit pointers on other platforms where available, however WebAssembly is the first platform where |
| 174 | +relative reference from data to code is not allowed. Alternative solutions like image-base relative addressing or |
| 175 | +"small code model" for fitting 64-bit pointer in 32-bit are unavailable, at least for now. This means that we need |
| 176 | +cooperation from the WebAssembly toolchain side or different memory layout in Swift metadata to support 64-bit linear |
| 177 | +memory support in WebAssembly. |
| 178 | + |
| 179 | +### Shared libraries |
| 180 | + |
| 181 | +There are two approaches to using shared libraries in the WebAssembly ecosystem: |
| 182 | + |
| 183 | +1. [Emscripten-style dynamic linking](https://emscripten.org/docs/compiling/Dynamic-Linking.html) |
| 184 | +2. [Component Model-based "ahead-of-time" linking](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Linking.md) |
| 185 | + |
| 186 | +Emscripten-style dynamic linking is a traditional way to use shared libraries in WebAssembly, where the host |
| 187 | +environment provides non-standard dynamic loading capabilities. |
| 188 | + |
| 189 | +The latter approach cannot fully replace the former, as it is unable to handle dynamic loading of shared libraries at |
| 190 | +runtime, but it is more portable way to distribute programs linked with shared libraries, as it does not require the |
| 191 | +host environment to provide any special capabilities except for Component Model support. |
| 192 | + |
| 193 | +Support for shared libraries in Swift means ensuring that Swift programs can be compiled in |
| 194 | +position-independent code mode and linked with shared libraries by following the corresponding dynamic linking ABI. |
| 195 | + |
| 196 | +[^1]: We aim to address browser-specific use cases in a separate future document. |
0 commit comments