WasmFeatures.cpp (11705B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * 4 * Copyright 2016 Mozilla Foundation 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 #include "wasm/WasmFeatures.h" 20 21 #include "jit/AtomicOperations.h" 22 #include "jit/JitContext.h" 23 #include "jit/JitOptions.h" 24 #include "js/Prefs.h" 25 #include "util/StringBuilder.h" 26 #include "vm/JSContext.h" 27 #include "vm/Realm.h" 28 #include "vm/StringType.h" 29 #include "wasm/WasmBaselineCompile.h" 30 #include "wasm/WasmIonCompile.h" 31 #include "wasm/WasmSignalHandlers.h" 32 33 using namespace js; 34 using namespace js::wasm; 35 using namespace js::jit; 36 37 // About the fuzzer intercession points: If fuzzing has been selected and only a 38 // single compiler has been selected then we will disable features that are not 39 // supported by that single compiler. This is strictly a concession to the 40 // fuzzer infrastructure. 41 42 static inline bool IsFuzzingIon(JSContext* cx) { 43 return IsFuzzing() && !cx->options().wasmBaseline() && 44 cx->options().wasmIon(); 45 } 46 47 // These functions read flags and apply fuzzing intercession policies. Never go 48 // directly to the flags in code below, always go via these accessors. 49 50 static inline bool WasmThreadsFlag(JSContext* cx) { 51 return cx->realm() && 52 cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled(); 53 } 54 55 #define WASM_FEATURE(NAME, ...) \ 56 static inline bool Wasm##NAME##Flag(JSContext* cx); 57 JS_FOR_WASM_FEATURES(WASM_FEATURE); 58 #undef WASM_FEATURE 59 60 #define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \ 61 FLAG_FORCE_ON, FLAG_FUZZ_ON, PREF) \ 62 static inline bool Wasm##NAME##Flag(JSContext* cx) { \ 63 if (!(COMPILE_PRED)) { \ 64 return false; \ 65 } \ 66 return ((FLAG_PRED) && JS::Prefs::wasm_##PREF()) || (FLAG_FORCE_ON); \ 67 } 68 JS_FOR_WASM_FEATURES(WASM_FEATURE); 69 #undef WASM_FEATURE 70 71 static inline bool WasmDebuggerActive(JSContext* cx) { 72 if (IsFuzzingIon(cx)) { 73 return false; 74 } 75 return cx->realm() && cx->realm()->debuggerObservesWasm(); 76 } 77 78 /* 79 * [SMDOC] Compiler and feature selection; compiler and feature availability. 80 * 81 * In order to make the computation of whether a wasm feature or wasm compiler 82 * is available predictable, we have established some rules, and implemented 83 * those rules. 84 * 85 * Code elsewhere should use the predicates below to test for features and 86 * compilers, it should never try to compute feature and compiler availability 87 * in other ways. 88 * 89 * At the outset, there is a set of selected compilers C containing at most one 90 * baseline compiler [*] and at most one optimizing compiler [**], and a set of 91 * selected features F. These selections come from defaults and from overrides 92 * by command line switches in the shell and javascript.option.wasm_X in the 93 * browser. Defaults for both features and compilers may be platform specific, 94 * for example, some compilers may not be available on some platforms because 95 * they do not support the architecture at all or they do not support features 96 * that must be enabled by default on the platform. 97 * 98 * [*] Currently we have only one, "baseline" aka "Rabaldr", but other 99 * implementations have additional baseline translators, eg from wasm 100 * bytecode to an internal code processed by an interpreter. 101 * 102 * [**] Currently we have only one, "ion" aka "Baldr". 103 * 104 * 105 * Compiler availability: 106 * 107 * The set of features F induces a set of available compilers A: these are the 108 * compilers that all support all the features in F. (Some of these compilers 109 * may not be in the set C.) 110 * 111 * The sets C and A are intersected, yielding a set of enabled compilers E. 112 * Notably, the set E may be empty, in which case wasm is effectively disabled 113 * (though the WebAssembly object is still present in the global environment). 114 * 115 * An important consequence is that selecting a feature that is not supported by 116 * a particular compiler disables that compiler completely -- there is no notion 117 * of a compiler being available but suddenly failing when an unsupported 118 * feature is used by a program. If a compiler is available, it supports all 119 * the features that have been selected. 120 * 121 * Equally important, a feature cannot be enabled by default on a platform if 122 * the feature is not supported by all the compilers we wish to have enabled by 123 * default on the platform. We MUST by-default disable features on a platform 124 * that are not supported by all the compilers on the platform. 125 * 126 * In a shell build, the testing functions wasmCompilersPresent, 127 * wasmCompileMode, and wasmIonDisabledByFeatures can be used to probe compiler 128 * availability and the reasons for a compiler being unavailable. 129 * 130 * 131 * Feature availability: 132 * 133 * A feature is available if it is selected and there is at least one available 134 * compiler that implements it. 135 * 136 * For example, --wasm-gc selects the GC feature, and if Baseline is available 137 * then the feature is available. 138 * 139 * In a shell build, there are per-feature testing functions (of the form 140 * wasmFeatureEnabled) to probe whether specific features are available. 141 */ 142 143 // Compiler availability predicates. These must be kept in sync with the 144 // feature predicates in the next section below. 145 // 146 // These can't call the feature predicates since the feature predicates call 147 // back to these predicates. So there will be a small amount of duplicated 148 // logic here, but as compilers reach feature parity that duplication will go 149 // away. 150 151 bool wasm::BaselineAvailable(JSContext* cx) { 152 if (!cx->options().wasmBaseline() || !BaselinePlatformSupport()) { 153 return false; 154 } 155 bool isDisabled = false; 156 MOZ_ALWAYS_TRUE(BaselineDisabledByFeatures(cx, &isDisabled)); 157 return !isDisabled; 158 } 159 160 bool wasm::IonAvailable(JSContext* cx) { 161 if (!cx->options().wasmIon() || !IonPlatformSupport()) { 162 return false; 163 } 164 bool isDisabled = false; 165 MOZ_ALWAYS_TRUE(IonDisabledByFeatures(cx, &isDisabled)); 166 return !isDisabled; 167 } 168 bool wasm::WasmCompilerForAsmJSAvailable(JSContext* cx) { 169 return IonAvailable(cx); 170 } 171 172 template <size_t ArrayLength> 173 static inline bool Append(JSStringBuilder* reason, const char (&s)[ArrayLength], 174 char* sep) { 175 if ((*sep && !reason->append(*sep)) || !reason->append(s)) { 176 return false; 177 } 178 *sep = ','; 179 return true; 180 } 181 182 bool wasm::BaselineDisabledByFeatures(JSContext* cx, bool* isDisabled, 183 JSStringBuilder* reason) { 184 // Baseline cannot be used if we are testing serialization. 185 bool testSerialization = WasmTestSerializationFlag(cx); 186 if (reason) { 187 char sep = 0; 188 if (testSerialization && !Append(reason, "testSerialization", &sep)) { 189 return false; 190 } 191 } 192 *isDisabled = testSerialization; 193 return true; 194 } 195 196 bool wasm::IonDisabledByFeatures(JSContext* cx, bool* isDisabled, 197 JSStringBuilder* reason) { 198 // Ion has no debugging support. 199 bool debug = WasmDebuggerActive(cx); 200 bool customPageSizes = WasmCustomPageSizesFlag(cx); 201 if (reason) { 202 char sep = 0; 203 if (debug && !Append(reason, "debug", &sep)) { 204 return false; 205 } 206 if (customPageSizes && !Append(reason, "custom-page-sizes", &sep)) { 207 return false; 208 } 209 } 210 *isDisabled = debug || customPageSizes; 211 return true; 212 } 213 214 bool wasm::AnyCompilerAvailable(JSContext* cx) { 215 return wasm::BaselineAvailable(cx) || wasm::IonAvailable(cx); 216 } 217 218 // Feature predicates. These must be kept in sync with the predicates in the 219 // section above. 220 // 221 // The meaning of these predicates is tricky: A predicate is true for a feature 222 // if the feature is enabled and/or compiled-in *and* we have *at least one* 223 // compiler that can support the feature. Subsequent compiler selection must 224 // ensure that only compilers that actually support the feature are used. 225 226 #define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, ...) \ 227 bool wasm::NAME##Available(JSContext* cx) { \ 228 return Wasm##NAME##Flag(cx) && (COMPILER_PRED); \ 229 } 230 JS_FOR_WASM_FEATURES(WASM_FEATURE) 231 #undef WASM_FEATURE 232 233 bool wasm::IsPrivilegedContext(JSContext* cx) { 234 // This may be slightly more lenient than we want in an ideal world, but it 235 // remains safe. 236 return cx->realm() && cx->realm()->principals() && 237 cx->realm()->principals()->isSystemOrAddonPrincipal(); 238 } 239 240 bool wasm::SimdAvailable(JSContext* cx) { 241 return js::jit::JitSupportsWasmSimd(); 242 } 243 244 bool wasm::ThreadsAvailable(JSContext* cx) { 245 return WasmThreadsFlag(cx) && AnyCompilerAvailable(cx); 246 } 247 248 bool wasm::HasPlatformSupport() { 249 #if !MOZ_LITTLE_ENDIAN() 250 return false; 251 #else 252 253 if (!HasJitBackend()) { 254 return false; 255 } 256 257 if (gc::SystemPageSize() > wasm::StandardPageSizeBytes) { 258 return false; 259 } 260 261 if (!JitOptions.supportsUnalignedAccesses) { 262 return false; 263 } 264 265 if (!jit::JitSupportsAtomics()) { 266 return false; 267 } 268 269 // Wasm threads require 8-byte lock-free atomics. 270 if (!jit::AtomicOperations::isLockfree8()) { 271 return false; 272 } 273 274 // Test only whether the compilers are supported on the hardware, not whether 275 // they are enabled. 276 return BaselinePlatformSupport() || IonPlatformSupport(); 277 #endif 278 } 279 280 bool wasm::HasSupport(JSContext* cx) { 281 // If the general wasm pref is on, it's on for everything. 282 bool prefEnabled = cx->options().wasm(); 283 // If the general pref is off, check trusted principals. 284 if (MOZ_UNLIKELY(!prefEnabled)) { 285 prefEnabled = cx->options().wasmForTrustedPrinciples() && cx->realm() && 286 cx->realm()->principals() && 287 cx->realm()->principals()->isSystemOrAddonPrincipal(); 288 } 289 // Do not check for compiler availability, as that may be run-time variant. 290 // For HasSupport() we want a stable answer depending only on prefs. 291 return prefEnabled && HasPlatformSupport() && EnsureFullSignalHandlers(cx); 292 } 293 294 bool wasm::StreamingCompilationAvailable(JSContext* cx) { 295 // This should match EnsureStreamSupport(). 296 return HasSupport(cx) && AnyCompilerAvailable(cx) && 297 cx->runtime()->offThreadPromiseState.ref().initialized() && 298 CanUseExtraThreads() && cx->runtime()->consumeStreamCallback && 299 cx->runtime()->reportStreamErrorCallback; 300 } 301 302 bool wasm::CodeCachingAvailable(JSContext* cx) { 303 // Fuzzilli breaks the out-of-process compilation mechanism, 304 // so we disable it permanently in those builds. 305 #ifdef FUZZING_JS_FUZZILLI 306 return false; 307 #else 308 309 // TODO(bug 1913109): lazy tiering doesn't support serialization 310 if (JS::Prefs::wasm_lazy_tiering() || JS::Prefs::wasm_lazy_tiering_for_gc()) { 311 return false; 312 } 313 314 // At the moment, we require Ion support for code caching. The main reason 315 // for this is that wasm::CompileAndSerialize() does not have access to 316 // information about which optimizing compiler it should use. See comments in 317 // CompileAndSerialize(), below. 318 return StreamingCompilationAvailable(cx) && IonAvailable(cx); 319 #endif 320 }