1
1
---
2
2
layout : post
3
- title : " WebAssembly targets and new on-by- default features"
3
+ title : " WebAssembly targets: change in default target- features"
4
4
author : Alex Crichton
5
+ team : The Compiler Team <https://www.rust-lang.org/governance/teams/compiler>
5
6
---
6
7
7
8
The Rust compiler has [ recently upgraded to using LLVM 19] [ llvm19 ] and this
8
- change accompanies some updates to WebAssembly targets of the Rust compiler.
9
- Nightly Rust, what will be come Rust 1.82 on 2024-10-17, reflects all of these
10
- changes and can be used for testing.
9
+ change accompanies some updates to the default set of target features enabled
10
+ for WebAssembly targets of the Rust compiler. Nightly Rust today, which will
11
+ become Rust 1.82 on 2024-10-17, reflects all of these changes and can be
12
+ used for testing.
11
13
12
- WebAssembly is an evolving standard where new features are being added over time
13
- through a [ proposals process] [ proposals ] . As WebAssembly proposals reach
14
+ WebAssembly is an evolving standard where extensions are being added over
15
+ time through a [ proposals process] [ proposals ] . WebAssembly proposals reach
14
16
maturity, get merged into the specification itself, get implemented in engines,
15
- and remains this way for quite some time then producer toolchains (e.g. LLVM)
16
- are going to update to include these new proposals by default. In LLVM 19 this
17
- has happened with the [ multi-value and reference-types proposals] [ llvmenable ] .
18
- These are now enabled by default in LLVM and transitively means that it's
19
- enabled by default for Rust as well.
17
+ and remain this way for quite some time before producer toolchains (e.g. LLVM)
18
+ update to ** enable these sufficiently-mature proposals by default** . In LLVM 19
19
+ this has happened with the [ multi-value and reference-types
20
+ proposals] [ llvmenable ] for the LLVM/Rust target features ` multivalue ` and
21
+ ` reference-types ` . These are now enabled by default in LLVM and transitively
22
+ means that it's enabled by default for Rust as well.
20
23
21
24
WebAssembly targets for Rust now [ have improved
22
25
documentation] ( https://github.com/rust-lang/rust/pull/128511 ) about WebAssembly
23
- features and disabling them, and this post is going to review these changes and
24
- go into depth about what's changing in LLVM.
26
+ proposals and their corresponding target features. This post is going to review
27
+ these changes and go into depth about what's changing in LLVM.
28
+
29
+ ## WebAssembly Proposals and Compiler Target Features
30
+
31
+ WebAssembly proposals are the formal means by which the WebAssembly standard
32
+ itself is evolved over time. Most proposals need toolchain integration in one
33
+ form or another, for example new flags in LLVM or the Rust compiler. The
34
+ ` -Ctarget-feature=... ` mechanism is used to implement this today. This is a
35
+ signal to LLVM and the Rust compiler which WebAssembly proposals are enabled or
36
+ disabled.
37
+
38
+ There is a loose coupling between the name of a proposal (often the name of the
39
+ github repository of the proposal) and the feature name LLVM/Rust use. For
40
+ example there is the [ ` multi-value `
41
+ proposal] ( https://github.com/webAssembly/multi-value ) but a ` multivalue `
42
+ feature.
43
+
44
+ The lifecycle of the implementation of a feature in Rust/LLVM typically looks
45
+ like:
46
+
47
+ 1 . A new WebAssembly proposal is created in a new repository, for example
48
+ WebAssembly/foo.
49
+ 2 . Eventually Rust/LLVM implement the proposal under ` -Ctarget-feature=+foo `
50
+ 3 . Eventually the upstream proposal is merged into the specification, and
51
+ WebAssembly/foo becomes an archived repository
52
+ 4 . Rust/LLVM enable the ` -Ctarget-feature=+foo ` feature by default but typically
53
+ retain the ability to disable it as well.
54
+
55
+ The ` reference-types ` and ` multivalue ` target features in Rust are at step (4)
56
+ here now and this post is explaining the consequences of doing so.
25
57
26
58
## Enabling Reference Types by Default
27
59
@@ -31,7 +63,9 @@ new concepts to WebAssembly, notably the `externref` type which is a
31
63
host-defined GC resource that WebAssembly cannot access but can pass around.
32
64
Rust does not have support for the WebAssembly ` externref ` type and LLVM 19 does
33
65
not change that. WebAssembly modules produced from Rust will continue to not use
34
- the ` externref ` type nor have a means of being able to do so.
66
+ the ` externref ` type nor have a means of being able to do so. This may be
67
+ enabled in the future, but it will mostly likely only be done on an opt-in basis
68
+ and will not affect preexisting code by default.
35
69
36
70
Also included in the reference-types proposal, however, was the ability to have
37
71
multiple WebAssembly tables in a single module. In the original version of the
@@ -47,9 +81,9 @@ was encoded with a fixed zero byte in its instruction (required to be exactly
47
81
0x00). This fixed zero byte was relaxed to a 32-bit [ LEB] to indicate which
48
82
table the ` call_indirect ` instruction was using. For those unfamiliar [ LEB] is a
49
83
way of encoding multi-byte integers in a smaller number of bytes for smaller
50
- integers. For example the integer 0 can be encoded as ` 0x00 ` with a [ LEB ] .
51
- [ LEB] s are flexible to additionally allow "overlong" encodings so the integer 0
52
- can additionally be encoded as ` 0x80 0x00 ` .
84
+ integers. For example the 32-gbit integer 0 can be encoded as ` 0x00 ` with a
85
+ [ LEB] . [ LEB ] s are flexible to additionally allow "overlong" encodings so the
86
+ integer 0 can additionally be encoded as ` 0x80 0x00 ` .
53
87
54
88
LLVM's support of separate compilation of source code to a WebAssembly binary
55
89
means that when an object file is emitted it does not know the final index of
@@ -60,29 +94,30 @@ over-long [LEB] of the form `0x80 0x80 0x80 0x80 0x00` which is the maximal
60
94
length of a 32-bit [ LEB] . This [ LEB] is then filled in by the linker with a
61
95
relocation to the actual table index that is used by the final module.
62
96
63
- When putting all of this together it means that LLVM 19, which has
64
- reference-types enabled by default, then any WebAssembly module with an indirect
65
- function call (which is almost always the case for Rust code) will produce a
66
- WebAssembly binary that cannot be decoded by engines and tooling that do not
67
- support the reference-types proposal. It is expected that this change will have
68
- a low impact due to the age of the reference-types proposal and breadth of
69
- implementation in engines. Given the multitude of WebAssembly engines, however,
70
- it's recommended that any WebAssembly users test out Nightly Rust and see if
71
- the produced module still runs on the engine of choice.
97
+ When putting all of this together, it means that with LLVM 19, which has
98
+ the ` reference-types ` feature enabled by default, any WebAssembly module with an
99
+ indirect function call (which is almost always the case for Rust code) will
100
+ produce a WebAssembly binary that cannot be decoded by engines and tooling that
101
+ do not support the reference-types proposal. It is expected that this change
102
+ will have a low impact due to the age of the reference-types proposal and
103
+ breadth of implementation in engines. Given the multitude of WebAssembly
104
+ engines, however, it's recommended that any WebAssembly users test out
105
+ Nightly Rust and see if the produced module still runs on the engine of
106
+ choice.
72
107
73
108
### LLVM, Rust, and Multiple Tables
74
109
75
- One interesting point worth mentioning is that despite reference-types enabling
76
- multiple tables in WebAssembly modules this is not actually taken advantage of
77
- at this time by either LLVM or Rust. WebAssembly modules emitted will still have
78
- at most one table of functions. This means that the over-long 5-byte encoding of
79
- index 0 as ` 0x80 0x80 0x80 0x80 0x00 ` is not actually necessary at this time.
80
- LLD, LLVM's linker for WebAssembly, wants to process all [ LEB ] relocations in a
81
- similar manner which currently forces this 5-byte encoding of zero. For example
82
- when a function calls another function the ` call ` instruction encodes the target
83
- function index as a 5-byte [ LEB] which is filled in by the linker. There is
84
- quite often more than one function so the 5-byte encoding enables all possible
85
- function indices to be encoded.
110
+ One interesting point worth mentioning is that despite the reference-types
111
+ proposal enabling multiple tables in WebAssembly modules this is not actually
112
+ taken advantage of at this time by either LLVM or Rust. WebAssembly modules
113
+ emitted will still have at most one table of functions. This means that the
114
+ over-long 5-byte encoding of index 0 as ` 0x80 0x80 0x80 0x80 0x00 ` is not
115
+ actually necessary at this time. LLD, LLVM's linker for WebAssembly, wants to
116
+ process all [ LEB ] relocations in a similar manner which currently forces this
117
+ 5-byte encoding of zero. For example when a function calls another function the
118
+ ` call ` instruction encodes the target function index as a 5-byte [ LEB] which is
119
+ filled in by the linker. There is quite often more than one function so the
120
+ 5-byte encoding enables all possible function indices to be encoded.
86
121
87
122
In the future LLVM might start using multiple tables as well. For example LLVM
88
123
may have a mode in the future where there's a table-per-function type instead of
@@ -99,18 +134,19 @@ single byte.
99
134
100
135
## Enabling Multi-Value by Default
101
136
102
- The second feature enabled by default in LLVM 19 is multi-value . The
137
+ The second feature enabled by default in LLVM 19 is ` multivalue ` . The
103
138
[ multi-value proposal to WebAssembly] [ multi-value ] enables functions to have
104
139
more than one return value for example. WebAssembly instructions are
105
140
additionally allowed to have more than one return value as well. This proposal
106
141
is one of the first to get merged into the WebAssembly specification after the
107
142
original MVP and has been implemented in many engines for quite some time.
108
143
109
144
The consequences of enabling this feature by default in LLVM are more minor for
110
- Rust, however, than enabling reference-types by default. LLVM's default ABI for
111
- WebAssembly code is not changing even when multi-value is enabled. Additionally
112
- Rust's ABI is not changing either and continues to match LLVM's. Despite this
113
- though the change has the possibility of still affecting Nightly users of Rust.
145
+ Rust, however, than enabling the ` reference-types ` feature by default. LLVM's
146
+ default C ABI for WebAssembly code is not changing even when ` multivalue ` is
147
+ enabled. Additionally Rust's ` extern "C" ` ABI is not changing either and
148
+ continues to match LLVM's. Despite this though the change has the possibility
149
+ of still affecting Nightly users of Rust.
114
150
115
151
Rust for some time has supported an ` extern "wasm" ` ABI on Nightly which was an
116
152
experimental means of exposing the ability of defining a function in Rust which
@@ -128,6 +164,30 @@ Supporting WebAssembly multi-return functions in Rust is a broader topic than
128
164
this post can cover, but at this time it's an area that's ripe for contribution
129
165
from suitably motivated contributors.
130
166
167
+ ### Aside: ABI Stability and WebAssembly
168
+
169
+ While on the topics of ABIs and the ` multi-value ` feature it's perhaps worth
170
+ also going over a bit what ABIs mean for WebAssembly. Currently there's
171
+ effectively only one ABI implemented by LLVM and the Rust compiler meaning that
172
+ at the WebAssembly module level ` extern "C" ` and ` extern "Rust" ` are
173
+ more-or-less the same. Despite this though there's no need for this to be true.
174
+
175
+ The ` extern "C" ` ABI, what C code uses by default as well, is difficult to
176
+ change because stability is often required across different compiler versions.
177
+ For example WebAssembly code compiled with LLVM 18 might be expected to work
178
+ with code compiled by LLVM 20. This means that changing the ABI is a daunting
179
+ task that requires version fields, explicit markers, etc, to help prevent
180
+ mismatches.
181
+
182
+ The ` extern "Rust" ` ABI, however, is entirely within the control of the Rust
183
+ compiler and can change from version to version. Just because it happens to work
184
+ like ` extern "C" ` today doesn't mean it will continue to do so in the future. A
185
+ great example of this is that when the ` multi-value ` feature is enabled the
186
+ ` extern "Rust" ` ABI could be redefined to use the multiple-return-values that
187
+ WebAssembly would then support. This would enable much more efficient returns of
188
+ values larger than 64-bits. Implementing this would require support in LLVM
189
+ though which is not currently present.
190
+
131
191
## Enabling Future Proposals to WebAssembly
132
192
133
193
This is not the first time that a WebAssembly proposal has gone from
@@ -150,7 +210,8 @@ by Nightly Rust and LLVM 19 then your options are:
150
210
either be done on your engine's repository, the Rust repository, or the
151
211
WebAssembly
152
212
[ tool-conventions] ( https://github.com/WebAssembly/tool-conventions )
153
- repository.
213
+ repository. It's recommended to first search to confirm there isn't already an
214
+ open issue though.
154
215
* Recompile your code with features disabled, more on this in the next section.
155
216
156
217
The general assumption behind enabling new features by default is that it's a
0 commit comments