Skip to content

Commit eab7cae

Browse files
authored
Merge pull request #2590 from MaxDesiatov/maxd/webassembly-vision
A Vision for WebAssembly Support in Swift
2 parents 42d46f8 + 7264453 commit eab7cae

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

visions/webassembly.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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

Comments
 (0)