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