WasmCompileArgs.h (17888B)
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 2021 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 #ifndef wasm_compile_args_h 20 #define wasm_compile_args_h 21 22 #include "mozilla/RefPtr.h" 23 #include "mozilla/SHA1.h" 24 #include "mozilla/TypedEnumBits.h" 25 26 #include "js/Utility.h" 27 #include "js/WasmFeatures.h" 28 #include "wasm/WasmBinaryTypes.h" 29 #include "wasm/WasmConstants.h" 30 #include "wasm/WasmShareable.h" 31 32 namespace js { 33 namespace wasm { 34 35 enum class Shareable { False, True }; 36 37 // Code can be compiled either with the Baseline compiler or the Ion compiler, 38 // and tier-variant data are tagged with the Tier value. 39 // 40 // A tier value is used to request tier-variant aspects of code, metadata, or 41 // linkdata. The tiers are normally explicit (Baseline and Ion); implicit tiers 42 // can be obtained through accessors on Code objects (eg, stableTier). 43 44 enum class Tier { 45 Baseline, 46 Debug = Baseline, 47 Optimized, 48 Serialized = Optimized 49 }; 50 51 static constexpr const char* ToString(Tier tier) { 52 switch (tier) { 53 case wasm::Tier::Baseline: 54 return "baseline"; 55 case wasm::Tier::Optimized: 56 return "optimized"; 57 default: 58 return "unknown"; 59 } 60 } 61 62 // Iterator over tiers present in a tiered data structure. 63 64 class Tiers { 65 Tier t_[2]; 66 uint32_t n_; 67 68 public: 69 explicit Tiers() { n_ = 0; } 70 explicit Tiers(Tier t) { 71 t_[0] = t; 72 n_ = 1; 73 } 74 explicit Tiers(Tier t, Tier u) { 75 MOZ_ASSERT(t != u); 76 t_[0] = t; 77 t_[1] = u; 78 n_ = 2; 79 } 80 81 Tier* begin() { return t_; } 82 Tier* end() { return t_ + n_; } 83 }; 84 85 struct BuiltinModuleIds { 86 BuiltinModuleIds() = default; 87 88 bool selfTest = false; 89 bool intGemm = false; 90 bool jsString = false; 91 bool jsStringConstants = false; 92 SharedChars jsStringConstantsNamespace; 93 94 bool hasNone() const { 95 return !selfTest && !intGemm && !jsString && !jsStringConstants; 96 } 97 }; 98 99 // Describes per-compilation settings that are controlled by an options bag 100 // passed to compilation and validation functions. (Nonstandard extension 101 // available under prefs.) 102 103 struct FeatureOptions { 104 FeatureOptions() 105 : disableOptimizingCompiler(false), 106 mozIntGemm(false), 107 isBuiltinModule(false), 108 jsStringBuiltins(false), 109 jsStringConstants(false) {} 110 111 // Whether we should try to disable our optimizing compiler. Only available 112 // with `IsSimdPrivilegedContext`. 113 bool disableOptimizingCompiler; 114 // Whether we enable the mozIntGemm builtin module. Only available with 115 // `IsSimdPrivilegedContext`. 116 bool mozIntGemm; 117 118 // Enables builtin module opcodes, only set in WasmBuiltinModule.cpp. 119 bool isBuiltinModule; 120 121 // Enable JS String builtins for this module. 122 bool jsStringBuiltins; 123 // Enable imported string constants for this module, only available if the 124 // feature is also enabled. 125 bool jsStringConstants; 126 SharedChars jsStringConstantsNamespace; 127 128 // Parse the compile options bag. 129 [[nodiscard]] bool init(JSContext* cx, HandleValue val); 130 }; 131 132 // Describes the features that control wasm compilation. 133 134 struct FeatureArgs { 135 FeatureArgs() 136 : 137 #define WASM_FEATURE(NAME, LOWER_NAME, ...) LOWER_NAME(false), 138 JS_FOR_WASM_FEATURES(WASM_FEATURE) 139 #undef WASM_FEATURE 140 sharedMemory(Shareable::False), 141 simd(false), 142 isBuiltinModule(false), 143 builtinModules() { 144 } 145 FeatureArgs(const FeatureArgs&) = default; 146 FeatureArgs& operator=(const FeatureArgs&) = default; 147 FeatureArgs(FeatureArgs&&) = default; 148 FeatureArgs& operator=(FeatureArgs&&) = default; 149 150 static FeatureArgs build(JSContext* cx, const FeatureOptions& options); 151 static FeatureArgs allEnabled() { 152 FeatureArgs args; 153 #define WASM_FEATURE(NAME, LOWER_NAME, ...) args.LOWER_NAME = true; 154 JS_FOR_WASM_FEATURES(WASM_FEATURE) 155 #undef WASM_FEATURE 156 args.sharedMemory = Shareable::True; 157 args.simd = true; 158 return args; 159 } 160 161 #define WASM_FEATURE(NAME, LOWER_NAME, ...) bool LOWER_NAME; 162 JS_FOR_WASM_FEATURES(WASM_FEATURE) 163 #undef WASM_FEATURE 164 165 Shareable sharedMemory; 166 bool simd; 167 // Whether this module is a wasm builtin module (see WasmBuiltinModule.h) and 168 // can contain special opcodes in function bodies. 169 bool isBuiltinModule; 170 // The set of builtin modules that are imported by this module. 171 BuiltinModuleIds builtinModules; 172 }; 173 174 // Observed feature usage for a compiled module. Intended to be used for use 175 // counters. 176 enum class FeatureUsage : uint8_t { 177 None = 0x0, 178 LegacyExceptions = 0x1, 179 ReturnCall = 0x2, 180 }; 181 182 using FeatureUsageVector = Vector<FeatureUsage, 0, SystemAllocPolicy>; 183 184 void SetUseCountersForFeatureUsage(JSContext* cx, JSObject* object, 185 FeatureUsage usage); 186 187 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(FeatureUsage); 188 189 // Describes the JS scripted caller of a request to compile a wasm module. 190 191 struct ScriptedCaller { 192 UniqueChars filename; // UTF-8 encoded 193 bool filenameIsURL; 194 uint32_t line; 195 196 ScriptedCaller() : filenameIsURL(false), line(0) {} 197 }; 198 199 // Describes the reasons we cannot compute compile args 200 201 enum class CompileArgsError { 202 OutOfMemory, 203 NoCompiler, 204 }; 205 206 // Describes all the parameters that control wasm compilation. 207 208 struct CompileArgs; 209 using MutableCompileArgs = RefPtr<CompileArgs>; 210 using SharedCompileArgs = RefPtr<const CompileArgs>; 211 212 struct CompileArgs : ShareableBase<CompileArgs> { 213 ScriptedCaller scriptedCaller; 214 UniqueChars sourceMapURL; 215 216 bool baselineEnabled; 217 bool ionEnabled; 218 bool debugEnabled; 219 bool forceTiering; 220 221 FeatureArgs features; 222 223 // CompileArgs has several constructors: 224 // 225 // - two through factory functions `build`/`buildAndReport`, which checks 226 // that flags are consistent with each other, and optionally reports any 227 // errors. 228 // - the 'buildForAsmJS' one, which uses the appropriate configuration for 229 // legacy asm.js code. 230 // - the 'buildForValidation' one, which takes just the features to enable 231 // and sets the compilers to a null state. 232 // - one that gives complete access to underlying fields. 233 // 234 // You should use the factory functions in general, unless you have a very 235 // good reason (i.e. no JSContext around and you know which flags have been 236 // used). 237 238 static SharedCompileArgs build(JSContext* cx, ScriptedCaller&& scriptedCaller, 239 const FeatureOptions& options, 240 CompileArgsError* error); 241 static SharedCompileArgs buildForAsmJS(ScriptedCaller&& scriptedCaller); 242 static SharedCompileArgs buildAndReport(JSContext* cx, 243 ScriptedCaller&& scriptedCaller, 244 const FeatureOptions& options, 245 bool reportOOM = false); 246 static SharedCompileArgs buildForValidation(const FeatureArgs& args); 247 248 explicit CompileArgs() 249 : scriptedCaller(), 250 baselineEnabled(false), 251 ionEnabled(false), 252 debugEnabled(false), 253 forceTiering(false) {} 254 }; 255 256 // CompilerEnvironment holds any values that will be needed to compute 257 // compilation parameters once the module's feature opt-in sections have been 258 // parsed. 259 // 260 // Subsequent to construction a computeParameters() call will compute the final 261 // compilation parameters, and the object can then be queried for their values. 262 263 struct CompileArgs; 264 class Decoder; 265 266 struct CompilerEnvironment { 267 // The object starts in one of two "initial" states; computeParameters moves 268 // it into the "computed" state. 269 enum State { InitialWithArgs, InitialWithModeTierDebug, Computed }; 270 271 State state_; 272 union { 273 // Value if the state_ == InitialWithArgs. 274 const CompileArgs* args_; 275 276 // Value in the other two states. 277 struct { 278 CompileMode mode_; 279 Tier tier_; 280 DebugEnabled debug_; 281 }; 282 }; 283 284 public: 285 // Retain a reference to the CompileArgs. A subsequent computeParameters() 286 // will compute all parameters from the CompileArgs and additional values. 287 explicit CompilerEnvironment(const CompileArgs& args); 288 289 // Save the provided values for mode, tier, and debug, and the initial value 290 // for gc/refTypes. A subsequent computeParameters() will compute the 291 // final value of gc/refTypes. 292 CompilerEnvironment(CompileMode mode, Tier tier, DebugEnabled debugEnabled); 293 294 // Compute any remaining compilation parameters. 295 void computeParameters(const ModuleMetadata& moduleMeta); 296 297 // Compute any remaining compilation parameters. Only use this method if 298 // the CompilerEnvironment was created with values for mode, tier, and 299 // debug. 300 void computeParameters(); 301 302 bool isComputed() const { return state_ == Computed; } 303 CompileMode mode() const { 304 MOZ_ASSERT(isComputed()); 305 return mode_; 306 } 307 CompileState initialState() const { 308 switch (mode()) { 309 case CompileMode::Once: 310 return CompileState::Once; 311 case CompileMode::EagerTiering: 312 return CompileState::EagerTier1; 313 case CompileMode::LazyTiering: 314 return CompileState::LazyTier1; 315 default: 316 MOZ_CRASH(); 317 } 318 } 319 Tier tier() const { 320 MOZ_ASSERT(isComputed()); 321 return tier_; 322 } 323 DebugEnabled debug() const { 324 MOZ_ASSERT(isComputed()); 325 return debug_; 326 } 327 bool debugEnabled() const { return debug() == DebugEnabled::True; } 328 }; 329 330 // A bytecode source is a wrapper around wasm bytecode that is to be compiled 331 // or validated. It has been pre-parsed into three regions: 332 // 1. 'env' - everything before the code section 333 // 2. 'code' - the code section 334 // 3. 'tail' - everything after the code section. 335 // 336 // The naming here matches the corresponding validation functions we have. This 337 // design comes from the requirements of streaming compilation which assembles 338 // separate buffers for each of these regions, and never constructs a single 339 // contiguous buffer. We use it for compiling contiguous buffers as well so that 340 // we have a single code path. 341 // 342 // If a module does not contain a code section (or is invalid and cannot be 343 // split into these regions), the bytecode source will only have an 'env' 344 // region. 345 // 346 // This class does not own any of the underlying buffers and only points to 347 // them. See BytecodeBuffer for that. 348 class BytecodeSource { 349 BytecodeSpan env_; 350 BytecodeSpan code_; 351 BytecodeSpan tail_; 352 353 size_t envOffset() const { return 0; } 354 size_t codeOffset() const { return envOffset() + envLength(); } 355 size_t tailOffset() const { return codeOffset() + codeLength(); } 356 357 size_t envLength() const { return env_.size(); } 358 size_t codeLength() const { return code_.size(); } 359 size_t tailLength() const { return tail_.size(); } 360 361 public: 362 // Create a bytecode source with no bytecode. 363 BytecodeSource() = default; 364 365 // Create a bytecode source from regions that have already been split. Does 366 // not do any validation. 367 BytecodeSource(const BytecodeSpan& envSpan, const BytecodeSpan& codeSpan, 368 const BytecodeSpan& tailSpan) 369 : env_(envSpan), code_(codeSpan), tail_(tailSpan) {} 370 371 // Parse a contiguous buffer into a bytecode source. This cannot fail because 372 // invalid modules will result in a bytecode source with only an 'env' region 373 // that further validation will reject. 374 BytecodeSource(const uint8_t* begin, size_t length); 375 376 // Copying and moving is allowed. 377 BytecodeSource(const BytecodeSource&) = default; 378 BytecodeSource& operator=(const BytecodeSource&) = default; 379 BytecodeSource(BytecodeSource&&) = default; 380 BytecodeSource& operator=(BytecodeSource&&) = default; 381 382 // The length in bytes of this module. 383 size_t length() const { return env_.size() + code_.size() + tail_.size(); } 384 385 // Whether we have a code section region or not. If there is no code section, 386 // then the tail region will be in the env region and must be parsed from 387 // there. 388 bool hasCodeSection() const { return code_.size() != 0; } 389 390 BytecodeRange envRange() const { 391 return BytecodeRange(envOffset(), envLength()); 392 } 393 BytecodeRange codeRange() const { 394 // Do not ask for the code range if we don't have a code section. 395 MOZ_ASSERT(hasCodeSection()); 396 return BytecodeRange(codeOffset(), codeLength()); 397 } 398 BytecodeRange tailRange() const { 399 // Do not ask for the tail range if we don't have a code section. Any 400 // contents that would be in the tail section will be in the env section, 401 // and the caller must use that section instead. 402 MOZ_ASSERT(hasCodeSection()); 403 return BytecodeRange(tailOffset(), tailLength()); 404 } 405 406 BytecodeSpan envSpan() const { return env_; } 407 BytecodeSpan codeSpan() const { 408 // Do not ask for the code span if we don't have a code section. 409 MOZ_ASSERT(hasCodeSection()); 410 return code_; 411 } 412 BytecodeSpan tailSpan() const { 413 // Do not ask for the tail span if we don't have a code section. Any 414 // contents that would be in the tail section will be in the env section, 415 // and the caller must use that section instead. 416 MOZ_ASSERT(hasCodeSection()); 417 return tail_; 418 } 419 BytecodeSpan getSpan(const BytecodeRange& range) const { 420 // Check if this range is within the env span 421 if (range.end <= codeOffset()) { 422 return range.toSpan(env_); 423 } 424 425 // Check if this range is within the code span 426 if (range.end <= tailOffset()) { 427 // The range cannot cross the span boundary 428 MOZ_RELEASE_ASSERT(range.start >= codeOffset()); 429 return range.relativeTo(codeRange()).toSpan(code_); 430 } 431 432 // Otherwise we must be within the tail span 433 // The range cannot cross the span boundary 434 MOZ_RELEASE_ASSERT(range.start >= tailOffset()); 435 return range.relativeTo(tailRange()).toSpan(tail_); 436 } 437 438 // Copy the contents of this buffer to the destination. The destination must 439 // be at least `this->length()` bytes. 440 void copyTo(uint8_t* dest) const { 441 memcpy(dest + envOffset(), env_.data(), env_.size()); 442 memcpy(dest + codeOffset(), code_.data(), code_.size()); 443 memcpy(dest + tailOffset(), tail_.data(), tail_.size()); 444 } 445 446 // Compute a SHA1 hash of the module. 447 void computeHash(mozilla::SHA1Sum::Hash* hash) const { 448 mozilla::SHA1Sum sha1Sum; 449 sha1Sum.update(env_.data(), env_.size()); 450 sha1Sum.update(code_.data(), code_.size()); 451 sha1Sum.update(tail_.data(), tail_.size()); 452 sha1Sum.finish(*hash); 453 } 454 }; 455 456 // A version of `BytecodeSource` that owns the underlying buffers for each 457 // region of bytecode. See the comment on `BytecodeSource` for interpretation 458 // of the different regions. 459 // 460 // The regions are allocated in separate vectors so that we can just hold onto 461 // the code section after we've finished compiling the module without having to 462 // split apart the bytecode buffer. 463 class BytecodeBuffer { 464 SharedBytes env_; 465 SharedBytes code_; 466 SharedBytes tail_; 467 BytecodeSource source_; 468 469 public: 470 // Create an empty buffer. 471 BytecodeBuffer() = default; 472 // Create a buffer from pre-parsed regions. 473 BytecodeBuffer(const ShareableBytes* env, const ShareableBytes* code, 474 const ShareableBytes* tail); 475 // Create a buffer from a source by allocating memory for each region. 476 [[nodiscard]] 477 static bool fromSource(const BytecodeSource& bytecodeSource, 478 BytecodeBuffer* bytecodeBuffer); 479 480 // Copying and moving is allowed, we just hold references to the underyling 481 // buffers. 482 BytecodeBuffer(const BytecodeBuffer&) = default; 483 BytecodeBuffer& operator=(const BytecodeBuffer&) = default; 484 BytecodeBuffer(BytecodeBuffer&&) = default; 485 BytecodeBuffer& operator=(BytecodeBuffer&&) = default; 486 487 // Get a bytecode source that points into our owned memory. 488 const BytecodeSource& source() const { return source_; } 489 490 // Grab a reference to the code section region, if any. 491 SharedBytes codeSection() const { return code_; } 492 }; 493 494 // Utility for passing either a bytecode buffer (which owns the bytecode) or 495 // just the source (which does not own the bytecode). 496 class BytecodeBufferOrSource { 497 union { 498 const BytecodeBuffer* buffer_; 499 BytecodeSource source_; 500 }; 501 bool hasBuffer_; 502 503 public: 504 BytecodeBufferOrSource() : source_(BytecodeSource()), hasBuffer_(false) {} 505 explicit BytecodeBufferOrSource(const BytecodeBuffer& buffer) 506 : buffer_(&buffer), hasBuffer_(true) {} 507 explicit BytecodeBufferOrSource(const BytecodeSource& source) 508 : source_(source), hasBuffer_(false) {} 509 510 BytecodeBufferOrSource(const BytecodeBufferOrSource&) = delete; 511 const BytecodeBufferOrSource& operator=(const BytecodeBufferOrSource&) = 512 delete; 513 514 ~BytecodeBufferOrSource() { 515 if (!hasBuffer_) { 516 source_.~BytecodeSource(); 517 } 518 } 519 520 bool hasBuffer() const { return hasBuffer_; } 521 const BytecodeBuffer& buffer() const { 522 MOZ_ASSERT(hasBuffer()); 523 return *buffer_; 524 } 525 const BytecodeSource& source() const { 526 if (hasBuffer_) { 527 return buffer_->source(); 528 } 529 return source_; 530 } 531 532 [[nodiscard]] bool getOrCreateBuffer(BytecodeBuffer* result) const { 533 if (hasBuffer()) { 534 *result = buffer(); 535 } 536 return BytecodeBuffer::fromSource(source(), result); 537 } 538 539 [[nodiscard]] SharedBytes getOrCreateCodeSection() const { 540 if (hasBuffer()) { 541 return buffer().codeSection(); 542 } 543 return ShareableBytes::fromSpan(source().codeSpan()); 544 } 545 }; 546 547 } // namespace wasm 548 } // namespace js 549 550 #endif // wasm_compile_args_h