vxx / vlib / v / gen / wasm / features.v
228 lines · 216 sloc · 6.85 KB · f05b0450b1493d16b6518e7cee11b1b09b39beb4
Raw
1// Copyright (c) 2023 l-m.dev. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module wasm
5
6// WebAssembly feature gating for the native backend.
7//
8// `wasm-opt` (Binaryen) and `wasm-validate` (WABT) accept *different* flag
9// spellings for the same features (e.g. Binaryen `--enable-exception-handling`
10// vs WABT `--enable-exceptions`; Binaryen `--enable-nontrapping-float-to-int`
11// vs WABT `--disable-saturating-float-to-int`). The logical feature set is
12// therefore defined once here and rendered into each tool's own vocabulary.
13//
14// The default build enables only the Safari-15 "floor" features; everything
15// else is opt-in via a `-d <name>` compile define. This keeps a default
16// `-os browser`/`-os wasi` build from silently emitting opcodes past the
17// baseline (the old `wasm-opt -all` enabled memory64/relaxed-SIMD/exnref/gc).
18
19// wasm_opt_min_version is the Binaryen version the toolchain pins
20// (see cmd/tools/install_binaryen.vsh). Used only for a soft warning.
21const wasm_opt_min_version = 112
22
23enum WasmFeature {
24 reference_types
25 bulk_memory
26 multivalue
27 sign_ext
28 mutable_globals
29 nontrapping_f2i
30 simd
31 gc
32 exception_handling
33 tail_call
34 threads
35 memory64
36 relaxed_simd
37 extended_const
38}
39
40// wasm_all_features lists every modelled feature, used to compute the WABT
41// validator argument set relative to its defaults.
42const wasm_all_features = [WasmFeature.reference_types, .bulk_memory, .multivalue, .sign_ext,
43 .mutable_globals, .nontrapping_f2i, .simd, .gc, .exception_handling, .tail_call, .threads,
44 .memory64, .relaxed_simd, .extended_const]
45
46// wasm_floor_features is the always-on Safari-15 baseline. It covers everything
47// the emitter produces today: ref.func/ref.null, memory.copy/fill/init,
48// multi-return, sign_extendN, the mutable __vsp/__heap_base globals, and
49// saturating (nontrapping) float->int casts.
50const wasm_floor_features = [WasmFeature.reference_types, .bulk_memory, .multivalue, .sign_ext,
51 .mutable_globals, .nontrapping_f2i]
52
53// wasm_optin_defines maps a `-d <name>` compile define to the feature it enables.
54const wasm_optin_defines = {
55 'wasm_gc': WasmFeature.gc
56 'wasm_exceptions': WasmFeature.exception_handling
57 'wasm_tail_call': WasmFeature.tail_call
58 'wasm_simd': WasmFeature.simd
59 'wasm_threads': WasmFeature.threads
60 'wasm_memory64': WasmFeature.memory64
61 'wasm_relaxed_simd': WasmFeature.relaxed_simd
62 'wasm_extended_const': WasmFeature.extended_const
63}
64
65struct FeatureFlag {
66 binaryen string // wasm-opt flag, e.g. '--enable-reference-types'
67 wabt string // WABT feature token, e.g. 'reference-types'
68 wabt_default_on bool // whether wasm-validate enables it without any flag
69}
70
71fn (f WasmFeature) flag() FeatureFlag {
72 return match f {
73 .reference_types {
74 FeatureFlag{
75 binaryen: '--enable-reference-types'
76 wabt: 'reference-types'
77 wabt_default_on: true
78 }
79 }
80 .bulk_memory {
81 FeatureFlag{
82 binaryen: '--enable-bulk-memory'
83 wabt: 'bulk-memory'
84 wabt_default_on: true
85 }
86 }
87 .multivalue {
88 FeatureFlag{
89 binaryen: '--enable-multivalue'
90 wabt: 'multi-value'
91 wabt_default_on: true
92 }
93 }
94 .sign_ext {
95 FeatureFlag{
96 binaryen: '--enable-sign-ext'
97 wabt: 'sign-extension'
98 wabt_default_on: true
99 }
100 }
101 .mutable_globals {
102 FeatureFlag{
103 binaryen: '--enable-mutable-globals'
104 wabt: 'mutable-globals'
105 wabt_default_on: true
106 }
107 }
108 .nontrapping_f2i {
109 FeatureFlag{
110 binaryen: '--enable-nontrapping-float-to-int'
111 wabt: 'saturating-float-to-int'
112 wabt_default_on: true
113 }
114 }
115 .simd {
116 FeatureFlag{
117 binaryen: '--enable-simd'
118 wabt: 'simd'
119 wabt_default_on: true
120 }
121 }
122 .gc {
123 FeatureFlag{
124 binaryen: '--enable-gc'
125 wabt: 'gc'
126 wabt_default_on: false
127 }
128 }
129 .exception_handling {
130 FeatureFlag{
131 binaryen: '--enable-exception-handling'
132 wabt: 'exceptions'
133 wabt_default_on: false
134 }
135 }
136 .tail_call {
137 FeatureFlag{
138 binaryen: '--enable-tail-call'
139 wabt: 'tail-call'
140 wabt_default_on: false
141 }
142 }
143 .threads {
144 FeatureFlag{
145 binaryen: '--enable-threads'
146 wabt: 'threads'
147 wabt_default_on: false
148 }
149 }
150 .memory64 {
151 FeatureFlag{
152 binaryen: '--enable-memory64'
153 wabt: 'memory64'
154 wabt_default_on: false
155 }
156 }
157 .relaxed_simd {
158 FeatureFlag{
159 binaryen: '--enable-relaxed-simd'
160 wabt: 'relaxed-simd'
161 wabt_default_on: false
162 }
163 }
164 .extended_const {
165 FeatureFlag{
166 binaryen: '--enable-extended-const'
167 wabt: 'extended-const'
168 wabt_default_on: false
169 }
170 }
171 }
172}
173
174// enabled_wasm_features returns the floor set plus any opt-in feature whose
175// `-d` define was passed on the command line.
176fn (g &Gen) enabled_wasm_features() []WasmFeature {
177 mut feats := wasm_floor_features.clone()
178 for define, feat in wasm_optin_defines {
179 if define in g.pref.compile_defines {
180 feats << feat
181 }
182 }
183 return apply_feature_implications(feats)
184}
185
186// apply_feature_implications expands `feats` with any feature implied by another.
187// Relaxed SIMD extends the SIMD/v128 feature, so requesting it must also enable
188// SIMD. Otherwise wabt_validate_args() emits `--disable-simd --enable-relaxed-simd`
189// and wasm-opt runs from `-mvp` without `--enable-simd`, so a `-d wasm_relaxed_simd`
190// build still fails under `-wasm-validate`/`-prod`.
191fn apply_feature_implications(feats []WasmFeature) []WasmFeature {
192 mut res := feats.clone()
193 if WasmFeature.relaxed_simd in res && WasmFeature.simd !in res {
194 res << .simd
195 }
196 return res
197}
198
199// binaryen_feature_flags renders the feature set into `wasm-opt` flags. It
200// starts from `-mvp` (all non-MVP features off) and enables only the allowlist,
201// so emitting an opcode outside the set makes wasm-opt itself fail rather than
202// silently optimise it through.
203fn binaryen_feature_flags(feats []WasmFeature) string {
204 mut flags := ['-mvp']
205 for feat in feats {
206 flags << feat.flag().binaryen
207 }
208 return flags.join(' ')
209}
210
211// wabt_validate_args renders the feature set into `wasm-validate` arguments,
212// relative to WABT's defaults: disable any default-on feature not in the set
213// (e.g. `--disable-simd` on a floor build), and enable any default-off feature
214// that is in the set. This gives the validator teeth - a stray above-floor
215// opcode fails validation instead of passing under WABT's permissive defaults.
216fn wabt_validate_args(feats []WasmFeature) []string {
217 mut args := []string{}
218 for feat in wasm_all_features {
219 ff := feat.flag()
220 enabled := feat in feats
221 if ff.wabt_default_on && !enabled {
222 args << '--disable-${ff.wabt}'
223 } else if !ff.wabt_default_on && enabled {
224 args << '--enable-${ff.wabt}'
225 }
226 }
227 return args
228}
229