tor-browser

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

ObjLiteral.h (25566B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sw=2 et tw=0 ft=c:
      3 *
      4 * This Source Code Form is subject to the terms of the Mozilla Public
      5 * License, v. 2.0. If a copy of the MPL was not distributed with this
      6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      7 
      8 #ifndef frontend_ObjLiteral_h
      9 #define frontend_ObjLiteral_h
     10 
     11 #include "mozilla/BloomFilter.h"  // mozilla::BitBloomFilter
     12 #include "mozilla/Span.h"
     13 
     14 #include "frontend/ParserAtom.h"  // ParserAtomsTable, TaggedParserAtomIndex, ParserAtom
     15 #include "js/AllocPolicy.h"
     16 #include "js/Value.h"
     17 #include "js/Vector.h"
     18 #include "util/EnumFlags.h"
     19 #include "vm/BytecodeUtil.h"
     20 #include "vm/Opcodes.h"
     21 
     22 /*
     23 * [SMDOC] ObjLiteral (Object Literal) Handling
     24 * ============================================
     25 *
     26 * The `ObjLiteral*` family of classes defines an infastructure to handle
     27 * object literals as they are encountered at parse time and translate them
     28 * into objects or shapes that are attached to the bytecode.
     29 *
     30 * The object-literal "instructions", whose opcodes are defined in
     31 * `ObjLiteralOpcode` below, each specify one key (atom property name, or
     32 * numeric index) and one value. An `ObjLiteralWriter` buffers a linear
     33 * sequence of such instructions, along with a side-table of atom references.
     34 * The writer stores a compact binary format that is then interpreted by the
     35 * `ObjLiteralReader` to construct an object or shape according to the
     36 * instructions.
     37 *
     38 * This may seem like an odd dance: create an intermediate data structure that
     39 * specifies key/value pairs, then later build the object/shape. Why not just do
     40 * so directly, as we parse? In fact, we used to do this. However, for several
     41 * good reasons, we want to avoid allocating or touching GC things at all
     42 * *during* the parse. We thus use a sequence of ObjLiteral instructions as an
     43 * intermediate data structure to carry object literal contents from parse to
     44 * the time at which we *can* allocate GC things.
     45 *
     46 * (The original intent was to allow for ObjLiteral instructions to actually be
     47 * invoked by a new JS opcode, JSOp::ObjLiteral, thus replacing the more
     48 * general opcode sequences sometimes generated to fill in objects and removing
     49 * the need to attach actual objects to JSOp::Object or JSOp::NewObject.
     50 * However, this was far too invasive and led to performance regressions, so
     51 * currently ObjLiteral only carries literals as far as the end of the parse
     52 * pipeline, when all GC things are allocated.)
     53 *
     54 * ObjLiteral data structures are used to represent object literals whenever
     55 * they are "compatible". See
     56 * BytecodeEmitter::isPropertyListObjLiteralCompatible for the precise
     57 * conditions; in brief, we can represent object literals with "primitive"
     58 * (numeric, boolean, string, null/undefined) values, and "normal"
     59 * (non-computed) object names. We can also represent arrays with the same
     60 * value restrictions. We cannot represent nested objects. We use ObjLiteral in
     61 * two different ways:
     62 *
     63 * - To build a template shape, when we can support the property keys but not
     64 *   the property values.
     65 * - To build the actual result object, when we support the property keys and
     66 *   the values and this is a JSOp::Object case (see below).
     67 *
     68 * Design and Performance Considerations
     69 * -------------------------------------
     70 *
     71 * As a brief overview, there are a number of opcodes that allocate objects:
     72 *
     73 * - JSOp::NewInit allocates a new empty `{}` object.
     74 *
     75 * - JSOp::NewObject, with a shape as an argument (held by the script data
     76 *   side-tables), allocates a new object with the given `shape` (property keys)
     77 *   and `undefined` property values.
     78 *
     79 * - JSOp::Object, with an object as argument, instructs the runtime to
     80 *   literally return the object argument as the result. This is thus only an
     81 *   "allocation" in the sense that the object was originally allocated when
     82 *   the script data / bytecode was created. It is only used when we know for
     83 *   sure that the script, and this program point within the script, will run
     84 *   *once*. (See the `treatAsRunOnce` flag on JSScript.)
     85 *
     86 * An operation occurs in a "singleton context", according to the parser, if it
     87 * will only ever execute once. In particular, this happens when (i) the script
     88 * is a "run-once" script, which is usually the case for e.g. top-level scripts
     89 * of web-pages (they run on page load, but no function or handle wraps or
     90 * refers to the script so it can't be invoked again), and (ii) the operation
     91 * itself is not within a loop or function in that run-once script.
     92 *
     93 * When we encounter an object literal, we decide which opcode to use, and we
     94 * construct the ObjLiteral and the bytecode using its result appropriately:
     95 *
     96 * - If in a singleton context, and if we support the values, we use
     97 *   JSOp::Object and we build the ObjLiteral instructions with values.
     98 * - Otherwise, if we support the keys but not the values, or if we are not
     99 *   in a singleton context, we use JSOp::NewObject. In this case, the initial
    100 *   opcode only creates an object with empty values, so BytecodeEmitter then
    101 *   generates bytecode to set the values appropriately.
    102 * - Otherwise, we generate JSOp::NewInit and bytecode to add properties one at
    103 *   a time. This will always work, but is the slowest and least
    104 *   memory-efficient option.
    105 */
    106 
    107 namespace js {
    108 
    109 class FrontendContext;
    110 class JSONPrinter;
    111 class LifoAlloc;
    112 
    113 namespace frontend {
    114 struct CompilationAtomCache;
    115 struct CompilationStencil;
    116 class StencilXDR;
    117 }  // namespace frontend
    118 
    119 // Object-literal instruction opcodes. An object literal is constructed by a
    120 // straight-line sequence of these ops, each adding one property to the
    121 // object.
    122 enum class ObjLiteralOpcode : uint8_t {
    123  INVALID = 0,
    124 
    125  ConstValue = 1,  // numeric types only.
    126  ConstString = 2,
    127  Null = 3,
    128  Undefined = 4,
    129  True = 5,
    130  False = 6,
    131 
    132  MAX = False,
    133 };
    134 
    135 // The kind of GC thing constructed by the ObjLiteral framework and stored in
    136 // the script data.
    137 enum class ObjLiteralKind : uint8_t {
    138  // Construct an ArrayObject from a list of dense elements.
    139  Array,
    140 
    141  // Construct an ArrayObject (the call site object) for a tagged template call
    142  // from a list of dense elements for the cooked array followed by the dense
    143  // elements for the `.raw` array.
    144  CallSiteObj,
    145 
    146  // Construct a PlainObject from a list of property keys/values.
    147  Object,
    148 
    149  // Construct a PlainObject Shape from a list of property keys.
    150  Shape,
    151 
    152  // Invalid sentinel value. Must be the last enum value.
    153  Invalid
    154 };
    155 
    156 // Flags that are associated with a sequence of object-literal instructions.
    157 // (These become bitflags by wrapping with EnumSet below.)
    158 enum class ObjLiteralFlag : uint8_t {
    159  // If set, this object contains index property, or duplicate non-index
    160  // property.
    161  // This flag is valid only if the ObjLiteralKind is not Array.
    162  HasIndexOrDuplicatePropName = 1 << 0,
    163 
    164  // Note: at most 6 flags are currently supported. See ObjLiteralKindAndFlags.
    165 };
    166 
    167 using ObjLiteralFlags = EnumFlags<ObjLiteralFlag>;
    168 
    169 // Helper class to encode ObjLiteralKind and ObjLiteralFlags in a single byte.
    170 class ObjLiteralKindAndFlags {
    171  uint8_t bits_ = 0;
    172 
    173  static constexpr size_t KindBits = 3;
    174  static constexpr size_t KindMask = BitMask(KindBits);
    175 
    176  static_assert(size_t(ObjLiteralKind::Invalid) <= KindMask,
    177                "ObjLiteralKind needs more bits");
    178 
    179 public:
    180  ObjLiteralKindAndFlags() = default;
    181 
    182  ObjLiteralKindAndFlags(ObjLiteralKind kind, ObjLiteralFlags flags)
    183      : bits_(size_t(kind) | (flags.toRaw() << KindBits)) {
    184    MOZ_ASSERT(this->kind() == kind);
    185    MOZ_ASSERT(this->flags() == flags);
    186  }
    187 
    188  ObjLiteralKind kind() const { return ObjLiteralKind(bits_ & KindMask); }
    189  ObjLiteralFlags flags() const {
    190    ObjLiteralFlags res;
    191    res.setRaw(bits_ >> KindBits);
    192    return res;
    193  }
    194 
    195  uint8_t toRaw() const { return bits_; }
    196  void setRaw(uint8_t bits) { bits_ = bits; }
    197 };
    198 
    199 inline bool ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op) {
    200  return op == ObjLiteralOpcode::ConstValue;
    201 }
    202 
    203 inline bool ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op) {
    204  return op == ObjLiteralOpcode::ConstString;
    205 }
    206 
    207 struct ObjLiteralReaderBase;
    208 
    209 // Property name (as TaggedParserAtomIndex) or an integer index.  Only used for
    210 // object-type literals; array literals do not require the index (the sequence
    211 // is always dense, with no holes, so the index is implicit). For the latter
    212 // case, we have a `None` placeholder.
    213 struct ObjLiteralKey {
    214 private:
    215  uint32_t value_;
    216 
    217  enum ObjLiteralKeyType {
    218    None,
    219    AtomIndex,
    220    ArrayIndex,
    221  };
    222 
    223  ObjLiteralKeyType type_;
    224 
    225  ObjLiteralKey(uint32_t value, ObjLiteralKeyType ty)
    226      : value_(value), type_(ty) {}
    227 
    228 public:
    229  ObjLiteralKey() : ObjLiteralKey(0, None) {}
    230  ObjLiteralKey(uint32_t value, bool isArrayIndex)
    231      : ObjLiteralKey(value, isArrayIndex ? ArrayIndex : AtomIndex) {}
    232  ObjLiteralKey(const ObjLiteralKey& other) = default;
    233 
    234  static ObjLiteralKey fromPropName(frontend::TaggedParserAtomIndex atomIndex) {
    235    return ObjLiteralKey(atomIndex.rawData(), false);
    236  }
    237  static ObjLiteralKey fromArrayIndex(uint32_t index) {
    238    return ObjLiteralKey(index, true);
    239  }
    240  static ObjLiteralKey none() { return ObjLiteralKey(); }
    241 
    242  bool isNone() const { return type_ == None; }
    243  bool isAtomIndex() const { return type_ == AtomIndex; }
    244  bool isArrayIndex() const { return type_ == ArrayIndex; }
    245 
    246  frontend::TaggedParserAtomIndex getAtomIndex() const {
    247    MOZ_ASSERT(isAtomIndex());
    248    return frontend::TaggedParserAtomIndex::fromRaw(value_);
    249  }
    250  uint32_t getArrayIndex() const {
    251    MOZ_ASSERT(isArrayIndex());
    252    return value_;
    253  }
    254 
    255  uint32_t rawIndex() const { return value_; }
    256 };
    257 
    258 struct ObjLiteralWriterBase {
    259 protected:
    260  friend struct ObjLiteralReaderBase;  // for access to mask and shift.
    261  static const uint32_t ATOM_INDEX_MASK = 0x7fffffff;
    262  // If set, the atom index field is an array index, not an atom index.
    263  static const uint32_t INDEXED_PROP = 0x80000000;
    264 
    265 public:
    266  using CodeVector = Vector<uint8_t, 64, js::SystemAllocPolicy>;
    267 
    268 protected:
    269  CodeVector code_;
    270 
    271 public:
    272  ObjLiteralWriterBase() = default;
    273 
    274  uint32_t curOffset() const { return code_.length(); }
    275 
    276 private:
    277  [[nodiscard]] bool pushByte(FrontendContext* fc, uint8_t data) {
    278    if (!code_.append(data)) {
    279      js::ReportOutOfMemory(fc);
    280      return false;
    281    }
    282    return true;
    283  }
    284 
    285  [[nodiscard]] bool prepareBytes(FrontendContext* fc, size_t len,
    286                                  uint8_t** p) {
    287    size_t offset = code_.length();
    288    if (!code_.growByUninitialized(len)) {
    289      js::ReportOutOfMemory(fc);
    290      return false;
    291    }
    292    *p = &code_[offset];
    293    return true;
    294  }
    295 
    296  template <typename T>
    297  [[nodiscard]] bool pushRawData(FrontendContext* fc, T data) {
    298    uint8_t* p = nullptr;
    299    if (!prepareBytes(fc, sizeof(T), &p)) {
    300      return false;
    301    }
    302    memcpy(p, &data, sizeof(T));
    303    return true;
    304  }
    305 
    306 protected:
    307  [[nodiscard]] bool pushOpAndName(FrontendContext* fc, ObjLiteralOpcode op,
    308                                   ObjLiteralKey key) {
    309    uint8_t opdata = static_cast<uint8_t>(op);
    310    uint32_t data = key.rawIndex() | (key.isArrayIndex() ? INDEXED_PROP : 0);
    311    return pushByte(fc, opdata) && pushRawData(fc, data);
    312  }
    313 
    314  [[nodiscard]] bool pushValueArg(FrontendContext* fc, const JS::Value& value) {
    315    MOZ_ASSERT(value.isNumber() || value.isNullOrUndefined() ||
    316               value.isBoolean());
    317    uint64_t data = value.asRawBits();
    318    return pushRawData(fc, data);
    319  }
    320 
    321  [[nodiscard]] bool pushAtomArg(FrontendContext* fc,
    322                                 frontend::TaggedParserAtomIndex atomIndex) {
    323    return pushRawData(fc, atomIndex.rawData());
    324  }
    325 };
    326 
    327 // An object-literal instruction writer. This class, held by the bytecode
    328 // emitter, keeps a sequence of object-literal instructions emitted as object
    329 // literal expressions are parsed. It allows the user to 'begin' and 'end'
    330 // straight-line sequences, returning the offsets for this range of instructions
    331 // within the writer.
    332 struct ObjLiteralWriter : private ObjLiteralWriterBase {
    333 public:
    334  ObjLiteralWriter() = default;
    335 
    336  void clear() { code_.clear(); }
    337 
    338  using CodeVector = typename ObjLiteralWriterBase::CodeVector;
    339 
    340  bool checkForDuplicatedNames(FrontendContext* fc);
    341  mozilla::Span<const uint8_t> getCode() const { return code_; }
    342  ObjLiteralKind getKind() const { return kind_; }
    343  ObjLiteralFlags getFlags() const { return flags_; }
    344  uint32_t getPropertyCount() const { return propertyCount_; }
    345 
    346  void beginArray(JSOp op) {
    347    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
    348    MOZ_ASSERT(op == JSOp::Object);
    349    kind_ = ObjLiteralKind::Array;
    350  }
    351  void beginCallSiteObj(JSOp op) {
    352    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
    353    MOZ_ASSERT(op == JSOp::CallSiteObj);
    354    kind_ = ObjLiteralKind::CallSiteObj;
    355  }
    356  void beginObject(JSOp op) {
    357    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
    358    MOZ_ASSERT(op == JSOp::Object);
    359    kind_ = ObjLiteralKind::Object;
    360  }
    361  void beginShape(JSOp op) {
    362    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SHAPE);
    363    MOZ_ASSERT(op == JSOp::NewObject);
    364    kind_ = ObjLiteralKind::Shape;
    365  }
    366 
    367  bool setPropName(frontend::ParserAtomsTable& parserAtoms,
    368                   const frontend::TaggedParserAtomIndex propName) {
    369    // Only valid in object-mode.
    370    setPropNameNoDuplicateCheck(parserAtoms, propName);
    371 
    372    if (flags_.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
    373      return true;
    374    }
    375 
    376    // OK to early return if we've already discovered a potential duplicate.
    377    if (mightContainDuplicatePropertyNames_) {
    378      return true;
    379    }
    380 
    381    // Check bloom filter for duplicate, and add if not already represented.
    382    if (propNamesFilter_.mightContain(propName.rawData())) {
    383      mightContainDuplicatePropertyNames_ = true;
    384    } else {
    385      propNamesFilter_.add(propName.rawData());
    386    }
    387    return true;
    388  }
    389  void setPropNameNoDuplicateCheck(
    390      frontend::ParserAtomsTable& parserAtoms,
    391      const frontend::TaggedParserAtomIndex propName) {
    392    MOZ_ASSERT(kind_ == ObjLiteralKind::Object ||
    393               kind_ == ObjLiteralKind::Shape);
    394    parserAtoms.markUsedByStencil(propName, frontend::ParserAtom::Atomize::Yes);
    395    nextKey_ = ObjLiteralKey::fromPropName(propName);
    396  }
    397  void setPropIndex(uint32_t propIndex) {
    398    MOZ_ASSERT(kind_ == ObjLiteralKind::Object);
    399    MOZ_ASSERT(propIndex <= ATOM_INDEX_MASK);
    400    nextKey_ = ObjLiteralKey::fromArrayIndex(propIndex);
    401    flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
    402  }
    403  void beginDenseArrayElements() {
    404    MOZ_ASSERT(kind_ == ObjLiteralKind::Array ||
    405               kind_ == ObjLiteralKind::CallSiteObj);
    406    // Dense array element sequences do not use the keys; the indices are
    407    // implicit.
    408    nextKey_ = ObjLiteralKey::none();
    409  }
    410 
    411  [[nodiscard]] bool propWithConstNumericValue(FrontendContext* fc,
    412                                               const JS::Value& value) {
    413    MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
    414    propertyCount_++;
    415    MOZ_ASSERT(value.isNumber());
    416    return pushOpAndName(fc, ObjLiteralOpcode::ConstValue, nextKey_) &&
    417           pushValueArg(fc, value);
    418  }
    419  [[nodiscard]] bool propWithAtomValue(
    420      FrontendContext* fc, frontend::ParserAtomsTable& parserAtoms,
    421      const frontend::TaggedParserAtomIndex value) {
    422    MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
    423    propertyCount_++;
    424    parserAtoms.markUsedByStencil(value, frontend::ParserAtom::Atomize::No);
    425    return pushOpAndName(fc, ObjLiteralOpcode::ConstString, nextKey_) &&
    426           pushAtomArg(fc, value);
    427  }
    428  [[nodiscard]] bool propWithNullValue(FrontendContext* fc) {
    429    MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
    430    propertyCount_++;
    431    return pushOpAndName(fc, ObjLiteralOpcode::Null, nextKey_);
    432  }
    433  [[nodiscard]] bool propWithUndefinedValue(FrontendContext* fc) {
    434    propertyCount_++;
    435    return pushOpAndName(fc, ObjLiteralOpcode::Undefined, nextKey_);
    436  }
    437  [[nodiscard]] bool propWithTrueValue(FrontendContext* fc) {
    438    MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
    439    propertyCount_++;
    440    return pushOpAndName(fc, ObjLiteralOpcode::True, nextKey_);
    441  }
    442  [[nodiscard]] bool propWithFalseValue(FrontendContext* fc) {
    443    MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
    444    propertyCount_++;
    445    return pushOpAndName(fc, ObjLiteralOpcode::False, nextKey_);
    446  }
    447 
    448  static bool arrayIndexInRange(int32_t i) {
    449    return i >= 0 && static_cast<uint32_t>(i) <= ATOM_INDEX_MASK;
    450  }
    451 
    452 #if defined(DEBUG) || defined(JS_JITSPEW)
    453  void dump() const;
    454  void dump(JSONPrinter& json,
    455            const frontend::CompilationStencil* stencil) const;
    456  void dumpFields(JSONPrinter& json,
    457                  const frontend::CompilationStencil* stencil) const;
    458 #endif
    459 
    460 private:
    461  // Set to true if we've found possible duplicate names while building.
    462  // This field is placed next to `flags_` field, to reduce padding.
    463  bool mightContainDuplicatePropertyNames_ = false;
    464 
    465  ObjLiteralKind kind_ = ObjLiteralKind::Invalid;
    466  ObjLiteralFlags flags_;
    467  ObjLiteralKey nextKey_;
    468  uint32_t propertyCount_ = 0;
    469 
    470  // Duplicate property names detection is performed in the following way:
    471  //   * while emitting code, add each property names with
    472  //     `propNamesFilter_`
    473  //   * if possible duplicate property name is detected, set
    474  //     `mightContainDuplicatePropertyNames_` to true
    475  //   * in `checkForDuplicatedNames` method,
    476  //     if `mightContainDuplicatePropertyNames_` is true,
    477  //     check the duplicate property names with `HashSet`, and if it exists,
    478  //     set HasIndexOrDuplicatePropName flag.
    479  mozilla::BitBloomFilter<12, frontend::TaggedParserAtomIndex> propNamesFilter_;
    480 };
    481 
    482 struct ObjLiteralReaderBase {
    483 private:
    484  mozilla::Span<const uint8_t> data_;
    485  size_t cursor_;
    486 
    487  [[nodiscard]] bool readByte(uint8_t* b) {
    488    if (cursor_ + 1 > data_.Length()) {
    489      return false;
    490    }
    491    *b = *data_.From(cursor_).data();
    492    cursor_ += 1;
    493    return true;
    494  }
    495 
    496  [[nodiscard]] bool readBytes(size_t size, const uint8_t** p) {
    497    if (cursor_ + size > data_.Length()) {
    498      return false;
    499    }
    500    *p = data_.From(cursor_).data();
    501    cursor_ += size;
    502    return true;
    503  }
    504 
    505  template <typename T>
    506  [[nodiscard]] bool readRawData(T* data) {
    507    const uint8_t* p = nullptr;
    508    if (!readBytes(sizeof(T), &p)) {
    509      return false;
    510    }
    511    memcpy(data, p, sizeof(T));
    512    return true;
    513  }
    514 
    515 public:
    516  explicit ObjLiteralReaderBase(mozilla::Span<const uint8_t> data)
    517      : data_(data), cursor_(0) {}
    518 
    519  [[nodiscard]] bool readOpAndKey(ObjLiteralOpcode* op, ObjLiteralKey* key) {
    520    uint8_t opbyte;
    521    if (!readByte(&opbyte)) {
    522      return false;
    523    }
    524    if (MOZ_UNLIKELY(opbyte > static_cast<uint8_t>(ObjLiteralOpcode::MAX))) {
    525      return false;
    526    }
    527    *op = static_cast<ObjLiteralOpcode>(opbyte);
    528 
    529    uint32_t data;
    530    if (!readRawData(&data)) {
    531      return false;
    532    }
    533    bool isArray = data & ObjLiteralWriterBase::INDEXED_PROP;
    534    uint32_t rawIndex = data & ~ObjLiteralWriterBase::INDEXED_PROP;
    535    *key = ObjLiteralKey(rawIndex, isArray);
    536    return true;
    537  }
    538 
    539  [[nodiscard]] bool readValueArg(JS::Value* value) {
    540    uint64_t data;
    541    if (!readRawData(&data)) {
    542      return false;
    543    }
    544    *value = JS::Value::fromRawBits(data);
    545    return true;
    546  }
    547 
    548  [[nodiscard]] bool readAtomArg(frontend::TaggedParserAtomIndex* atomIndex) {
    549    return readRawData(atomIndex->rawDataRef());
    550  }
    551 
    552  size_t cursor() const { return cursor_; }
    553 };
    554 
    555 // A single object-literal instruction, creating one property on an object.
    556 struct ObjLiteralInsn {
    557 private:
    558  ObjLiteralOpcode op_;
    559  ObjLiteralKey key_;
    560  union Arg {
    561    explicit Arg(uint64_t raw_) : raw(raw_) {}
    562 
    563    JS::Value constValue;
    564    frontend::TaggedParserAtomIndex atomIndex;
    565    uint64_t raw;
    566  } arg_;
    567 
    568 public:
    569  ObjLiteralInsn() : op_(ObjLiteralOpcode::INVALID), arg_(0) {}
    570  ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key)
    571      : op_(op), key_(key), arg_(0) {
    572    MOZ_ASSERT(!hasConstValue());
    573    MOZ_ASSERT(!hasAtomIndex());
    574  }
    575  ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, const JS::Value& value)
    576      : op_(op), key_(key), arg_(0) {
    577    MOZ_ASSERT(hasConstValue());
    578    MOZ_ASSERT(!hasAtomIndex());
    579    arg_.constValue = value;
    580  }
    581  ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key,
    582                 frontend::TaggedParserAtomIndex atomIndex)
    583      : op_(op), key_(key), arg_(0) {
    584    MOZ_ASSERT(!hasConstValue());
    585    MOZ_ASSERT(hasAtomIndex());
    586    arg_.atomIndex = atomIndex;
    587  }
    588  ObjLiteralInsn(const ObjLiteralInsn& other) : ObjLiteralInsn() {
    589    *this = other;
    590  }
    591  ObjLiteralInsn& operator=(const ObjLiteralInsn& other) {
    592    op_ = other.op_;
    593    key_ = other.key_;
    594    arg_.raw = other.arg_.raw;
    595    return *this;
    596  }
    597 
    598  bool isValid() const {
    599    return op_ > ObjLiteralOpcode::INVALID && op_ <= ObjLiteralOpcode::MAX;
    600  }
    601 
    602  ObjLiteralOpcode getOp() const {
    603    MOZ_ASSERT(isValid());
    604    return op_;
    605  }
    606  const ObjLiteralKey& getKey() const {
    607    MOZ_ASSERT(isValid());
    608    return key_;
    609  }
    610 
    611  bool hasConstValue() const {
    612    MOZ_ASSERT(isValid());
    613    return ObjLiteralOpcodeHasValueArg(op_);
    614  }
    615  bool hasAtomIndex() const {
    616    MOZ_ASSERT(isValid());
    617    return ObjLiteralOpcodeHasAtomArg(op_);
    618  }
    619 
    620  JS::Value getConstValue() const {
    621    MOZ_ASSERT(isValid());
    622    MOZ_ASSERT(hasConstValue());
    623    return arg_.constValue;
    624  }
    625  frontend::TaggedParserAtomIndex getAtomIndex() const {
    626    MOZ_ASSERT(isValid());
    627    MOZ_ASSERT(hasAtomIndex());
    628    return arg_.atomIndex;
    629  };
    630 };
    631 
    632 // A reader that parses a sequence of object-literal instructions out of the
    633 // encoded form.
    634 struct ObjLiteralReader : private ObjLiteralReaderBase {
    635 public:
    636  explicit ObjLiteralReader(mozilla::Span<const uint8_t> data)
    637      : ObjLiteralReaderBase(data) {}
    638 
    639  [[nodiscard]] bool readInsn(ObjLiteralInsn* insn) {
    640    ObjLiteralOpcode op;
    641    ObjLiteralKey key;
    642    if (!readOpAndKey(&op, &key)) {
    643      return false;
    644    }
    645    if (ObjLiteralOpcodeHasValueArg(op)) {
    646      JS::Value value;
    647      if (!readValueArg(&value)) {
    648        return false;
    649      }
    650      *insn = ObjLiteralInsn(op, key, value);
    651      return true;
    652    }
    653    if (ObjLiteralOpcodeHasAtomArg(op)) {
    654      frontend::TaggedParserAtomIndex atomIndex;
    655      if (!readAtomArg(&atomIndex)) {
    656        return false;
    657      }
    658      *insn = ObjLiteralInsn(op, key, atomIndex);
    659      return true;
    660    }
    661    *insn = ObjLiteralInsn(op, key);
    662    return true;
    663  }
    664 };
    665 
    666 // A class to modify the code, while keeping the structure.
    667 struct ObjLiteralModifier : private ObjLiteralReaderBase {
    668  mozilla::Span<uint8_t> mutableData_;
    669 
    670 public:
    671  explicit ObjLiteralModifier(mozilla::Span<uint8_t> data)
    672      : ObjLiteralReaderBase(data), mutableData_(data) {}
    673 
    674 private:
    675  // Map `atom` with `map`, and write to `atomCursor` of `mutableData_`.
    676  template <typename MapT>
    677  void mapOneAtom(MapT map, frontend::TaggedParserAtomIndex atom,
    678                  size_t atomCursor) {
    679    auto atomIndex = map(atom);
    680    memcpy(mutableData_.data() + atomCursor, atomIndex.rawDataRef(),
    681           sizeof(frontend::TaggedParserAtomIndex));
    682  }
    683 
    684  // Map atoms in single instruction.
    685  // Return true if it successfully maps.
    686  // Return false if there's no more instruction.
    687  template <typename MapT>
    688  bool mapInsnAtom(MapT map) {
    689    ObjLiteralOpcode op;
    690    ObjLiteralKey key;
    691 
    692    size_t opCursor = cursor();
    693    if (!readOpAndKey(&op, &key)) {
    694      return false;
    695    }
    696    if (key.isAtomIndex()) {
    697      static constexpr size_t OpLength = 1;
    698      size_t atomCursor = opCursor + OpLength;
    699      mapOneAtom(map, key.getAtomIndex(), atomCursor);
    700    }
    701 
    702    if (ObjLiteralOpcodeHasValueArg(op)) {
    703      JS::Value value;
    704      if (!readValueArg(&value)) {
    705        return false;
    706      }
    707    } else if (ObjLiteralOpcodeHasAtomArg(op)) {
    708      size_t atomCursor = cursor();
    709 
    710      frontend::TaggedParserAtomIndex atomIndex;
    711      if (!readAtomArg(&atomIndex)) {
    712        return false;
    713      }
    714 
    715      mapOneAtom(map, atomIndex, atomCursor);
    716    }
    717 
    718    return true;
    719  }
    720 
    721 public:
    722  // Map TaggedParserAtomIndex inside the code in place, with given function.
    723  template <typename MapT>
    724  void mapAtom(MapT map) {
    725    while (mapInsnAtom(map)) {
    726    }
    727  }
    728 };
    729 
    730 class ObjLiteralStencil {
    731  friend class frontend::StencilXDR;
    732 
    733  // CompilationStencil::clone has to update the code pointer.
    734  friend struct frontend::CompilationStencil;
    735 
    736  mozilla::Span<uint8_t> code_;
    737  ObjLiteralKindAndFlags kindAndFlags_;
    738  uint32_t propertyCount_ = 0;
    739 
    740 public:
    741  ObjLiteralStencil() = default;
    742 
    743  ObjLiteralStencil(uint8_t* code, size_t length, ObjLiteralKind kind,
    744                    const ObjLiteralFlags& flags, uint32_t propertyCount)
    745      : code_(mozilla::Span(code, length)),
    746        kindAndFlags_(kind, flags),
    747        propertyCount_(propertyCount) {}
    748 
    749  JS::GCCellPtr create(JSContext* cx,
    750                       const frontend::CompilationAtomCache& atomCache) const;
    751 
    752  mozilla::Span<const uint8_t> code() const { return code_; }
    753  ObjLiteralKind kind() const { return kindAndFlags_.kind(); }
    754  ObjLiteralFlags flags() const { return kindAndFlags_.flags(); }
    755  uint32_t propertyCount() const { return propertyCount_; }
    756 
    757 #ifdef DEBUG
    758  bool isContainedIn(const LifoAlloc& alloc) const;
    759 #endif
    760 
    761 #if defined(DEBUG) || defined(JS_JITSPEW)
    762  void dump() const;
    763  void dump(JSONPrinter& json,
    764            const frontend::CompilationStencil* stencil) const;
    765  void dumpFields(JSONPrinter& json,
    766                  const frontend::CompilationStencil* stencil) const;
    767 
    768 #endif
    769 };
    770 
    771 }  // namespace js
    772 #endif  // frontend_ObjLiteral_h