tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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