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