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