| 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 | } |
| 229 | |