|
| 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. |
| 4 | +module 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. |
| 21 | +const wasm_opt_min_version = 112 |
| 22 | + |
| 23 | +enum 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. |
| 42 | +const 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. |
| 50 | +const 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. |
| 54 | +const 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 | + |
| 65 | +struct 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 | + |
| 71 | +fn (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. |
| 176 | +fn (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`. |
| 191 | +fn 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. |
| 203 | +fn 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. |
| 216 | +fn 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 | +} |
0 commit comments