1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 2 * vim: set ts=8 sts=4 et sw=4 tw=99: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/ObjectGroup.h" 8 9 #include "jshashutil.h" 10 #include "jsobj.h" 11 12 #include "gc/Marking.h" 13 #include "gc/Policy.h" 14 #include "gc/StoreBuffer.h" 15 #include "gc/Zone.h" 16 #include "js/CharacterEncoding.h" 17 #include "vm/ArrayObject.h" 18 #include "vm/Shape.h" 19 #include "vm/TaggedProto.h" 20 #include "vm/UnboxedObject.h" 21 22 #include "jsobjinlines.h" 23 24 #include "vm/UnboxedObject-inl.h" 25 26 using namespace js; 27 28 using mozilla::DebugOnly; 29 using mozilla::PodZero; 30 31 ///////////////////////////////////////////////////////////////////// 32 // ObjectGroup 33 ///////////////////////////////////////////////////////////////////// 34 35 ObjectGroup::ObjectGroup(const Class* clasp, TaggedProto proto, JSCompartment* comp, 36 ObjectGroupFlags initialFlags) 37 { 38 PodZero(this); 39 40 /* Windows may not appear on prototype chains. */ 41 MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject())); 42 MOZ_ASSERT(JS::StringIsASCII(clasp->name)); 43 44 this->clasp_ = clasp; 45 this->proto_ = proto; 46 this->compartment_ = comp; 47 this->flags_ = initialFlags; 48 49 setGeneration(zone()->types.generation); 50 } 51 52 void 53 ObjectGroup::finalize(FreeOp* fop) 54 { 55 if (newScriptDontCheckGeneration()) 56 newScriptDontCheckGeneration()->clear(); 57 fop->delete_(newScriptDontCheckGeneration()); 58 fop->delete_(maybeUnboxedLayoutDontCheckGeneration()); 59 if (maybePreliminaryObjectsDontCheckGeneration()) 60 maybePreliminaryObjectsDontCheckGeneration()->clear(); 61 fop->delete_(maybePreliminaryObjectsDontCheckGeneration()); 62 } 63 64 void 65 ObjectGroup::setProtoUnchecked(TaggedProto proto) 66 { 67 proto_ = proto; 68 MOZ_ASSERT_IF(proto_.isObject() && proto_.toObject()->isNative(), 69 proto_.toObject()->isDelegate()); 70 } 71 72 void 73 ObjectGroup::setProto(TaggedProto proto) 74 { 75 MOZ_ASSERT(singleton()); 76 setProtoUnchecked(proto); 77 } 78 79 size_t 80 ObjectGroup::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const 81 { 82 size_t n = 0; 83 if (TypeNewScript* newScript = newScriptDontCheckGeneration()) 84 n += newScript->sizeOfIncludingThis(mallocSizeOf); 85 if (UnboxedLayout* layout = maybeUnboxedLayoutDontCheckGeneration()) 86 n += layout->sizeOfIncludingThis(mallocSizeOf); 87 return n; 88 } 89 90 void 91 ObjectGroup::setAddendum(AddendumKind kind, void* addendum, bool writeBarrier /* = true */) 92 { 93 MOZ_ASSERT(!needsSweep()); 94 MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT)); 95 96 if (writeBarrier) { 97 // Manually trigger barriers if we are clearing new script or 98 // preliminary object information. Other addendums are immutable. 99 switch (addendumKind()) { 100 case Addendum_PreliminaryObjects: 101 PreliminaryObjectArrayWithTemplate::writeBarrierPre(maybePreliminaryObjects()); 102 break; 103 case Addendum_NewScript: 104 TypeNewScript::writeBarrierPre(newScript()); 105 break; 106 case Addendum_None: 107 break; 108 default: 109 MOZ_ASSERT(addendumKind() == kind); 110 } 111 } 112 113 flags_ &= ~OBJECT_FLAG_ADDENDUM_MASK; 114 flags_ |= kind << OBJECT_FLAG_ADDENDUM_SHIFT; 115 addendum_ = addendum; 116 } 117 118 /* static */ bool 119 ObjectGroup::useSingletonForClone(JSFunction* fun) 120 { 121 if (!fun->isInterpreted()) 122 return false; 123 124 if (fun->isArrow()) 125 return false; 126 127 if (fun->isSingleton()) 128 return false; 129 130 /* 131 * When a function is being used as a wrapper for another function, it 132 * improves precision greatly to distinguish between different instances of 133 * the wrapper; otherwise we will conflate much of the information about 134 * the wrapped functions. 135 * 136 * An important example is the Class.create function at the core of the 137 * Prototype.js library, which looks like: 138 * 139 * var Class = { 140 * create: function() { 141 * return function() { 142 * this.initialize.apply(this, arguments); 143 * } 144 * } 145 * }; 146 * 147 * Each instance of the innermost function will have a different wrapped 148 * initialize method. We capture this, along with similar cases, by looking 149 * for short scripts which use both .apply and arguments. For such scripts, 150 * whenever creating a new instance of the function we both give that 151 * instance a singleton type and clone the underlying script. 152 */ 153 154 uint32_t begin, end; 155 if (fun->hasScript()) { 156 if (!fun->nonLazyScript()->isLikelyConstructorWrapper()) 157 return false; 158 begin = fun->nonLazyScript()->sourceStart(); 159 end = fun->nonLazyScript()->sourceEnd(); 160 } else { 161 if (!fun->lazyScript()->isLikelyConstructorWrapper()) 162 return false; 163 begin = fun->lazyScript()->begin(); 164 end = fun->lazyScript()->end(); 165 } 166 167 return end - begin <= 100; 168 } 169 170 /* static */ bool 171 ObjectGroup::useSingletonForNewObject(JSContext* cx, JSScript* script, jsbytecode* pc) 172 { 173 /* 174 * Make a heuristic guess at a use of JSOP_NEW that the constructed object 175 * should have a fresh group. We do this when the NEW is immediately 176 * followed by a simple assignment to an object's .prototype field. 177 * This is designed to catch common patterns for subclassing in JS: 178 * 179 * function Super() { ... } 180 * function Sub1() { ... } 181 * function Sub2() { ... } 182 * 183 * Sub1.prototype = new Super(); 184 * Sub2.prototype = new Super(); 185 * 186 * Using distinct groups for the particular prototypes of Sub1 and 187 * Sub2 lets us continue to distinguish the two subclasses and any extra 188 * properties added to those prototype objects. 189 */ 190 if (script->isGenerator()) 191 return false; 192 if (JSOp(*pc) != JSOP_NEW) 193 return false; 194 pc += JSOP_NEW_LENGTH; 195 if (JSOp(*pc) == JSOP_SETPROP) { 196 if (script->getName(pc) == cx->names().prototype) 197 return true; 198 } 199 return false; 200 } 201 202 /* static */ bool 203 ObjectGroup::useSingletonForAllocationSite(JSScript* script, jsbytecode* pc, JSProtoKey key) 204 { 205 // The return value of this method can either be tested like a boolean or 206 // passed to a NewObject method. 207 JS_STATIC_ASSERT(GenericObject == 0); 208 209 /* 210 * Objects created outside loops in global and eval scripts should have 211 * singleton types. For now this is only done for plain objects, but not 212 * typed arrays or normal arrays. 213 */ 214 215 if (script->functionNonDelazifying() && !script->treatAsRunOnce()) 216 return GenericObject; 217 218 if (key != JSProto_Object) 219 return GenericObject; 220 221 // All loops in the script will have a try note indicating their boundary. 222 223 if (!script->hasTrynotes()) 224 return SingletonObject; 225 226 unsigned offset = script->pcToOffset(pc); 227 228 JSTryNote* tn = script->trynotes()->vector; 229 JSTryNote* tnlimit = tn + script->trynotes()->length; 230 for (; tn < tnlimit; tn++) { 231 if (tn->kind != JSTRY_FOR_IN && tn->kind != JSTRY_FOR_OF && tn->kind != JSTRY_LOOP) 232 continue; 233 234 unsigned startOffset = script->mainOffset() + tn->start; 235 unsigned endOffset = startOffset + tn->length; 236 237 if (offset >= startOffset && offset < endOffset) 238 return GenericObject; 239 } 240 241 return SingletonObject; 242 } 243 244 /* static */ bool 245 ObjectGroup::useSingletonForAllocationSite(JSScript* script, jsbytecode* pc, const Class* clasp) 246 { 247 return useSingletonForAllocationSite(script, pc, JSCLASS_CACHED_PROTO_KEY(clasp)); 248 } 249 250 ///////////////////////////////////////////////////////////////////// 251 // JSObject 252 ///////////////////////////////////////////////////////////////////// 253 254 bool 255 JSObject::shouldSplicePrototype(JSContext* cx) 256 { 257 /* 258 * During bootstrapping, if inference is enabled we need to make sure not 259 * to splice a new prototype in for Function.prototype or the global 260 * object if their __proto__ had previously been set to null, as this 261 * will change the prototype for all other objects with the same type. 262 */ 263 if (staticPrototype() != nullptr) 264 return false; 265 return isSingleton(); 266 } 267 268 bool 269 JSObject::splicePrototype(JSContext* cx, const Class* clasp, Handle<TaggedProto> proto) 270 { 271 MOZ_ASSERT(cx->compartment() == compartment()); 272 273 RootedObject self(cx, this); 274 275 /* 276 * For singleton groups representing only a single JSObject, the proto 277 * can be rearranged as needed without destroying type information for 278 * the old or new types. 279 */ 280 MOZ_ASSERT(self->isSingleton()); 281 282 // Windows may not appear on prototype chains. 283 MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject())); 284 285 if (proto.isObject() && !proto.toObject()->setDelegate(cx)) 286 return false; 287 288 // Force type instantiation when splicing lazy group. 289 RootedObjectGroup group(cx, self->getGroup(cx)); 290 if (!group) 291 return false; 292 RootedObjectGroup protoGroup(cx, nullptr); 293 if (proto.isObject()) { 294 protoGroup = proto.toObject()->getGroup(cx); 295 if (!protoGroup) 296 return false; 297 } 298 299 group->setClasp(clasp); 300 group->setProto(proto); 301 return true; 302 } 303 304 /* static */ ObjectGroup* 305 JSObject::makeLazyGroup(JSContext* cx, HandleObject obj) 306 { 307 MOZ_ASSERT(obj->hasLazyGroup()); 308 MOZ_ASSERT(cx->compartment() == obj->compartment()); 309 310 /* De-lazification of functions can GC, so we need to do it up here. */ 311 if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) { 312 RootedFunction fun(cx, &obj->as<JSFunction>()); 313 if (!fun->getOrCreateScript(cx)) 314 return nullptr; 315 } 316 317 // Find flags which need to be specified immediately on the object. 318 // Don't track whether singletons are packed. 319 ObjectGroupFlags initialFlags = OBJECT_FLAG_SINGLETON | OBJECT_FLAG_NON_PACKED; 320 321 if (obj->isIteratedSingleton()) 322 initialFlags |= OBJECT_FLAG_ITERATED; 323 324 if (obj->isIndexed()) 325 initialFlags |= OBJECT_FLAG_SPARSE_INDEXES; 326 327 if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() > INT32_MAX) 328 initialFlags |= OBJECT_FLAG_LENGTH_OVERFLOW; 329 330 Rooted<TaggedProto> proto(cx, obj->taggedProto()); 331 ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, obj->getClass(), proto, 332 initialFlags); 333 if (!group) 334 return nullptr; 335 336 AutoEnterAnalysis enter(cx); 337 338 /* Fill in the type according to the state of this object. */ 339 340 if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) 341 group->setInterpretedFunction(&obj->as<JSFunction>()); 342 343 obj->group_ = group; 344 345 return group; 346 } 347 348 /* static */ bool 349 JSObject::setNewGroupUnknown(JSContext* cx, const js::Class* clasp, JS::HandleObject obj) 350 { 351 ObjectGroup::setDefaultNewGroupUnknown(cx, clasp, obj); 352 return obj->setFlags(cx, BaseShape::NEW_GROUP_UNKNOWN); 353 } 354 355 ///////////////////////////////////////////////////////////////////// 356 // ObjectGroupCompartment NewTable 357 ///////////////////////////////////////////////////////////////////// 358 359 /* 360 * Entries for the per-compartment set of groups which are the default 361 * types to use for some prototype. An optional associated object is used which 362 * allows multiple groups to be created with the same prototype. The 363 * associated object may be a function (for types constructed with 'new') or a 364 * type descriptor (for typed objects). These entries are also used for the set 365 * of lazy groups in the compartment, which use a null associated object 366 * (though there are only a few of these per compartment). 367 */ 368 struct ObjectGroupCompartment::NewEntry 369 { 370 ReadBarrieredObjectGroup group; 371 372 // Note: This pointer is only used for equality and does not need a read barrier. 373 JSObject* associated; 374 375 NewEntry(ObjectGroup* group, JSObject* associated) 376 : group(group), associated(associated) 377 {} 378 379 struct Lookup { 380 const Class* clasp; 381 TaggedProto proto; 382 JSObject* associated; 383 384 Lookup(const Class* clasp, TaggedProto proto, JSObject* associated) 385 : clasp(clasp), proto(proto), associated(associated) 386 {} 387 388 bool hasAssocId() const { 389 return !associated || associated->zone()->hasUniqueId(associated); 390 } 391 392 bool ensureAssocId() const { 393 uint64_t unusedId; 394 return !associated || 395 associated->zoneFromAnyThread()->getUniqueId(associated, &unusedId); 396 } 397 398 uint64_t getAssocId() const { 399 return associated ? associated->zone()->getUniqueIdInfallible(associated) : 0; 400 } 401 }; 402 403 static bool hasHash(const Lookup& l) { 404 return l.proto.hasUniqueId() && l.hasAssocId(); 405 } 406 407 static bool ensureHash(const Lookup& l) { 408 return l.proto.ensureUniqueId() && l.ensureAssocId(); 409 } 410 411 static inline HashNumber hash(const Lookup& lookup) { 412 MOZ_ASSERT(lookup.proto.hasUniqueId()); 413 MOZ_ASSERT(lookup.hasAssocId()); 414 HashNumber hash = uintptr_t(lookup.clasp); 415 hash = mozilla::RotateLeft(hash, 4) ^ Zone::UniqueIdToHash(lookup.proto.uniqueId()); 416 hash = mozilla::RotateLeft(hash, 4) ^ Zone::UniqueIdToHash(lookup.getAssocId()); 417 return hash; 418 } 419 420 static inline bool match(const ObjectGroupCompartment::NewEntry& key, const Lookup& lookup) { 421 TaggedProto proto = key.group.unbarrieredGet()->proto().unbarrieredGet(); 422 JSObject* assoc = key.associated; 423 MOZ_ASSERT(proto.hasUniqueId()); 424 MOZ_ASSERT_IF(assoc, assoc->zone()->hasUniqueId(assoc)); 425 MOZ_ASSERT(lookup.proto.hasUniqueId()); 426 MOZ_ASSERT(lookup.hasAssocId()); 427 428 if (lookup.clasp && key.group.unbarrieredGet()->clasp() != lookup.clasp) 429 return false; 430 if (proto.uniqueId() != lookup.proto.uniqueId()) 431 return false; 432 return !assoc || assoc->zone()->getUniqueIdInfallible(assoc) == lookup.getAssocId(); 433 } 434 435 static void rekey(NewEntry& k, const NewEntry& newKey) { k = newKey; } 436 437 bool needsSweep() { 438 return (IsAboutToBeFinalized(&group) || 439 (associated && IsAboutToBeFinalizedUnbarriered(&associated))); 440 } 441 }; 442 443 namespace js { 444 template <> 445 struct FallibleHashMethods<ObjectGroupCompartment::NewEntry> 446 { 447 template <typename Lookup> static bool hasHash(Lookup&& l) { 448 return ObjectGroupCompartment::NewEntry::hasHash(mozilla::Forward<Lookup>(l)); 449 } 450 template <typename Lookup> static bool ensureHash(Lookup&& l) { 451 return ObjectGroupCompartment::NewEntry::ensureHash(mozilla::Forward<Lookup>(l)); 452 } 453 }; 454 } // namespace js 455 456 class ObjectGroupCompartment::NewTable : public JS::WeakCache<js::GCHashSet<NewEntry, NewEntry, 457 SystemAllocPolicy>> 458 { 459 using Table = js::GCHashSet<NewEntry, NewEntry, SystemAllocPolicy>; 460 using Base = JS::WeakCache<Table>; 461 462 public: 463 explicit NewTable(Zone* zone) : Base(zone, Table()) {} 464 }; 465 466 /* static */ ObjectGroup* 467 ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp, 468 TaggedProto proto, JSObject* associated) 469 { 470 MOZ_ASSERT_IF(associated, proto.isObject()); 471 MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); 472 473 // A null lookup clasp is used for 'new' groups with an associated 474 // function. The group starts out as a plain object but might mutate into an 475 // unboxed plain object. 476 MOZ_ASSERT_IF(!clasp, !!associated); 477 478 AutoEnterAnalysis enter(cx); 479 480 ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.defaultNewTable; 481 482 if (!table) { 483 table = cx->new_<ObjectGroupCompartment::NewTable>(cx->zone()); 484 if (!table || !table->init()) { 485 js_delete(table); 486 table = nullptr; 487 ReportOutOfMemory(cx); 488 return nullptr; 489 } 490 } 491 492 if (associated && !associated->is<TypeDescr>()) { 493 MOZ_ASSERT(!clasp); 494 if (associated->is<JSFunction>()) { 495 496 // Canonicalize new functions to use the original one associated with its script. 497 JSFunction* fun = &associated->as<JSFunction>(); 498 if (fun->hasScript()) 499 associated = fun->nonLazyScript()->functionNonDelazifying(); 500 else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin()) 501 associated = fun->lazyScript()->functionNonDelazifying(); 502 else 503 associated = nullptr; 504 505 // If we have previously cleared the 'new' script information for this 506 // function, don't try to construct another one. 507 if (associated && associated->wasNewScriptCleared()) 508 associated = nullptr; 509 510 } else { 511 associated = nullptr; 512 } 513 514 if (!associated) 515 clasp = &PlainObject::class_; 516 } 517 518 if (proto.isObject() && !proto.toObject()->isDelegate()) { 519 RootedObject protoObj(cx, proto.toObject()); 520 if (!protoObj->setDelegate(cx)) 521 return nullptr; 522 523 // Objects which are prototypes of one another should be singletons, so 524 // that their type information can be tracked more precisely. Limit 525 // this group change to plain objects, to avoid issues with other types 526 // of singletons like typed arrays. 527 if (protoObj->is<PlainObject>() && !protoObj->isSingleton()) { 528 if (!JSObject::changeToSingleton(cx->asJSContext(), protoObj)) 529 return nullptr; 530 } 531 } 532 533 ObjectGroupCompartment::NewTable::AddPtr p = 534 table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, associated)); 535 if (p) { 536 ObjectGroup* group = p->group; 537 MOZ_ASSERT_IF(clasp, group->clasp() == clasp); 538 MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_ || 539 group->clasp() == &UnboxedPlainObject::class_); 540 MOZ_ASSERT(group->proto() == proto); 541 return group; 542 } 543 544 ObjectGroupFlags initialFlags = 0; 545 if (proto.isDynamic() || (proto.isObject() && proto.toObject()->isNewGroupUnknown())) 546 initialFlags = OBJECT_FLAG_DYNAMIC_MASK; 547 548 Rooted<TaggedProto> protoRoot(cx, proto); 549 ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, clasp ? clasp : &PlainObject::class_, 550 protoRoot, initialFlags); 551 if (!group) 552 return nullptr; 553 554 if (!table->add(p, ObjectGroupCompartment::NewEntry(group, associated))) { 555 ReportOutOfMemory(cx); 556 return nullptr; 557 } 558 559 if (associated) { 560 if (associated->is<JSFunction>()) { 561 if (!TypeNewScript::make(cx->asJSContext(), group, &associated->as<JSFunction>())) 562 return nullptr; 563 } else { 564 group->setTypeDescr(&associated->as<TypeDescr>()); 565 } 566 } 567 568 /* 569 * Some builtin objects have slotful native properties baked in at 570 * creation via the Shape::{insert,get}initialShape mechanism. Since 571 * these properties are never explicitly defined on new objects, update 572 * the type information for them here. 573 */ 574 575 const JSAtomState& names = cx->names(); 576 577 if (clasp == &RegExpObject::class_) { 578 AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type()); 579 } else if (clasp == &StringObject::class_) { 580 AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type()); 581 } else if (ErrorObject::isErrorClass((clasp))) { 582 AddTypePropertyId(cx, group, nullptr, NameToId(names.fileName), TypeSet::StringType()); 583 AddTypePropertyId(cx, group, nullptr, NameToId(names.lineNumber), TypeSet::Int32Type()); 584 AddTypePropertyId(cx, group, nullptr, NameToId(names.columnNumber), TypeSet::Int32Type()); 585 AddTypePropertyId(cx, group, nullptr, NameToId(names.stack), TypeSet::StringType()); 586 } 587 588 return group; 589 } 590 591 /* static */ ObjectGroup* 592 ObjectGroup::lazySingletonGroup(ExclusiveContext* cx, const Class* clasp, TaggedProto proto) 593 { 594 MOZ_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment()); 595 596 ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.lazyTable; 597 598 if (!table) { 599 table = cx->new_<ObjectGroupCompartment::NewTable>(cx->zone()); 600 if (!table || !table->init()) { 601 ReportOutOfMemory(cx); 602 js_delete(table); 603 table = nullptr; 604 return nullptr; 605 } 606 } 607 608 ObjectGroupCompartment::NewTable::AddPtr p = 609 table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, nullptr)); 610 if (p) { 611 ObjectGroup* group = p->group; 612 MOZ_ASSERT(group->lazy()); 613 614 return group; 615 } 616 617 AutoEnterAnalysis enter(cx); 618 619 Rooted<TaggedProto> protoRoot(cx, proto); 620 ObjectGroup* group = 621 ObjectGroupCompartment::makeGroup(cx, clasp, protoRoot, 622 OBJECT_FLAG_SINGLETON | OBJECT_FLAG_LAZY_SINGLETON); 623 if (!group) 624 return nullptr; 625 626 if (!table->add(p, ObjectGroupCompartment::NewEntry(group, nullptr))) { 627 ReportOutOfMemory(cx); 628 return nullptr; 629 } 630 631 return group; 632 } 633 634 /* static */ void 635 ObjectGroup::setDefaultNewGroupUnknown(JSContext* cx, const Class* clasp, HandleObject obj) 636 { 637 // If the object already has a new group, mark that group as unknown. 638 ObjectGroupCompartment::NewTable* table = cx->compartment()->objectGroups.defaultNewTable; 639 if (table) { 640 Rooted<TaggedProto> taggedProto(cx, TaggedProto(obj)); 641 auto lookup = ObjectGroupCompartment::NewEntry::Lookup(clasp, taggedProto, nullptr); 642 auto p = table->lookup(lookup); 643 if (p) 644 MarkObjectGroupUnknownProperties(cx, p->group); 645 } 646 } 647 648 #ifdef DEBUG 649 /* static */ bool 650 ObjectGroup::hasDefaultNewGroup(JSObject* proto, const Class* clasp, ObjectGroup* group) 651 { 652 ObjectGroupCompartment::NewTable* table = proto->compartment()->objectGroups.defaultNewTable; 653 654 if (table) { 655 auto lookup = ObjectGroupCompartment::NewEntry::Lookup(clasp, TaggedProto(proto), nullptr); 656 auto p = table->lookup(lookup); 657 return p && p->group == group; 658 } 659 return false; 660 } 661 #endif /* DEBUG */ 662 663 inline const Class* 664 GetClassForProtoKey(JSProtoKey key) 665 { 666 switch (key) { 667 case JSProto_Null: 668 case JSProto_Object: 669 return &PlainObject::class_; 670 case JSProto_Array: 671 return &ArrayObject::class_; 672 673 case JSProto_Number: 674 return &NumberObject::class_; 675 case JSProto_Boolean: 676 return &BooleanObject::class_; 677 case JSProto_String: 678 return &StringObject::class_; 679 case JSProto_Symbol: 680 return &SymbolObject::class_; 681 case JSProto_RegExp: 682 return &RegExpObject::class_; 683 684 case JSProto_Int8Array: 685 case JSProto_Uint8Array: 686 case JSProto_Int16Array: 687 case JSProto_Uint16Array: 688 case JSProto_Int32Array: 689 case JSProto_Uint32Array: 690 case JSProto_Float32Array: 691 case JSProto_Float64Array: 692 case JSProto_Uint8ClampedArray: 693 return &TypedArrayObject::classes[key - JSProto_Int8Array]; 694 695 case JSProto_ArrayBuffer: 696 return &ArrayBufferObject::class_; 697 698 case JSProto_SharedArrayBuffer: 699 return &SharedArrayBufferObject::class_; 700 701 case JSProto_DataView: 702 return &DataViewObject::class_; 703 704 default: 705 MOZ_CRASH("Bad proto key"); 706 } 707 } 708 709 /* static */ ObjectGroup* 710 ObjectGroup::defaultNewGroup(JSContext* cx, JSProtoKey key) 711 { 712 RootedObject proto(cx); 713 if (key != JSProto_Null && !GetBuiltinPrototype(cx, key, &proto)) 714 return nullptr; 715 return defaultNewGroup(cx, GetClassForProtoKey(key), TaggedProto(proto.get())); 716 } 717 718 ///////////////////////////////////////////////////////////////////// 719 // ObjectGroupCompartment ArrayObjectTable 720 ///////////////////////////////////////////////////////////////////// 721 722 struct ObjectGroupCompartment::ArrayObjectKey : public DefaultHasher<ArrayObjectKey> 723 { 724 TypeSet::Type type; 725 726 ArrayObjectKey() 727 : type(TypeSet::UndefinedType()) 728 {} 729 730 explicit ArrayObjectKey(TypeSet::Type type) 731 : type(type) 732 {} 733 734 static inline uint32_t hash(const ArrayObjectKey& v) { 735 return v.type.raw(); 736 } 737 738 static inline bool match(const ArrayObjectKey& v1, const ArrayObjectKey& v2) { 739 return v1.type == v2.type; 740 } 741 742 bool operator==(const ArrayObjectKey& other) { 743 return type == other.type; 744 } 745 746 bool operator!=(const ArrayObjectKey& other) { 747 return !(*this == other); 748 } 749 750 bool needsSweep() { 751 MOZ_ASSERT(type.isUnknown() || !type.isSingleton()); 752 if (!type.isUnknown() && type.isGroup()) { 753 ObjectGroup* group = type.groupNoBarrier(); 754 if (IsAboutToBeFinalizedUnbarriered(&group)) 755 return true; 756 if (group != type.groupNoBarrier()) 757 type = TypeSet::ObjectType(group); 758 } 759 return false; 760 } 761 }; 762 763 static inline bool 764 NumberTypes(TypeSet::Type a, TypeSet::Type b) 765 { 766 return (a.isPrimitive(JSVAL_TYPE_INT32) || a.isPrimitive(JSVAL_TYPE_DOUBLE)) 767 && (b.isPrimitive(JSVAL_TYPE_INT32) || b.isPrimitive(JSVAL_TYPE_DOUBLE)); 768 } 769 770 /* 771 * As for GetValueType, but requires object types to be non-singletons with 772 * their default prototype. These are the only values that should appear in 773 * arrays and objects whose type can be fixed. 774 */ 775 static inline TypeSet::Type 776 GetValueTypeForTable(const Value& v) 777 { 778 TypeSet::Type type = TypeSet::GetValueType(v); 779 MOZ_ASSERT(!type.isSingleton()); 780 return type; 781 } 782 783 /* static */ JSObject* 784 ObjectGroup::newArrayObject(ExclusiveContext* cx, 785 const Value* vp, size_t length, 786 NewObjectKind newKind, NewArrayKind arrayKind) 787 { 788 MOZ_ASSERT(newKind != SingletonObject); 789 790 // If we are making a copy on write array, don't try to adjust the group as 791 // getOrFixupCopyOnWriteObject will do this before any objects are copied 792 // from this one. 793 if (arrayKind == NewArrayKind::CopyOnWrite) { 794 ArrayObject* obj = NewDenseCopiedArray(cx, length, vp, nullptr, newKind); 795 if (!obj || !ObjectElements::MakeElementsCopyOnWrite(cx, obj)) 796 return nullptr; 797 return obj; 798 } 799 800 // Get a type which captures all the elements in the array to be created. 801 Rooted<TypeSet::Type> elementType(cx, TypeSet::UnknownType()); 802 if (arrayKind != NewArrayKind::UnknownIndex && length != 0) { 803 elementType = GetValueTypeForTable(vp[0]); 804 for (unsigned i = 1; i < length; i++) { 805 TypeSet::Type ntype = GetValueTypeForTable(vp[i]); 806 if (ntype != elementType) { 807 if (NumberTypes(elementType, ntype)) { 808 elementType = TypeSet::DoubleType(); 809 } else { 810 elementType = TypeSet::UnknownType(); 811 break; 812 } 813 } 814 } 815 } 816 817 ObjectGroupCompartment::ArrayObjectTable*& table = 818 cx->compartment()->objectGroups.arrayObjectTable; 819 820 if (!table) { 821 table = cx->new_<ObjectGroupCompartment::ArrayObjectTable>(); 822 if (!table || !table->init()) { 823 ReportOutOfMemory(cx); 824 js_delete(table); 825 table = nullptr; 826 return nullptr; 827 } 828 } 829 830 ObjectGroupCompartment::ArrayObjectKey key(elementType); 831 DependentAddPtr<ObjectGroupCompartment::ArrayObjectTable> p(cx, *table, key); 832 833 RootedObjectGroup group(cx); 834 if (p) { 835 group = p->value(); 836 } else { 837 RootedObject proto(cx); 838 if (!GetBuiltinPrototype(cx, JSProto_Array, &proto)) 839 return nullptr; 840 Rooted<TaggedProto> taggedProto(cx, TaggedProto(proto)); 841 group = ObjectGroupCompartment::makeGroup(cx, &ArrayObject::class_, taggedProto); 842 if (!group) 843 return nullptr; 844 845 AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType); 846 847 if (elementType != TypeSet::UnknownType()) { 848 // Keep track of the initial objects we create with this type. 849 // If the initial ones have a consistent shape and property types, we 850 // will try to use an unboxed layout for the group. 851 PreliminaryObjectArrayWithTemplate* preliminaryObjects = 852 cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr); 853 if (!preliminaryObjects) 854 return nullptr; 855 group->setPreliminaryObjects(preliminaryObjects); 856 } 857 858 if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group)) 859 return nullptr; 860 } 861 862 // The type of the elements being added will already be reflected in type 863 // information, but make sure when creating an unboxed array that the 864 // common element type is suitable for the unboxed representation. 865 ShouldUpdateTypes updateTypes = ShouldUpdateTypes::DontUpdate; 866 if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length)) 867 return nullptr; 868 if (group->maybePreliminaryObjects()) 869 group->maybePreliminaryObjects()->maybeAnalyze(cx, group); 870 if (group->maybeUnboxedLayout()) { 871 switch (group->unboxedLayout().elementType()) { 872 case JSVAL_TYPE_BOOLEAN: 873 if (elementType != TypeSet::BooleanType()) 874 updateTypes = ShouldUpdateTypes::Update; 875 break; 876 case JSVAL_TYPE_INT32: 877 if (elementType != TypeSet::Int32Type()) 878 updateTypes = ShouldUpdateTypes::Update; 879 break; 880 case JSVAL_TYPE_DOUBLE: 881 if (elementType != TypeSet::Int32Type() && elementType != TypeSet::DoubleType()) 882 updateTypes = ShouldUpdateTypes::Update; 883 break; 884 case JSVAL_TYPE_STRING: 885 if (elementType != TypeSet::StringType()) 886 updateTypes = ShouldUpdateTypes::Update; 887 break; 888 case JSVAL_TYPE_OBJECT: 889 if (elementType != TypeSet::NullType() && !elementType.get().isObjectUnchecked()) 890 updateTypes = ShouldUpdateTypes::Update; 891 break; 892 default: 893 MOZ_CRASH(); 894 } 895 } 896 897 return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes); 898 } 899 900 // Try to change the group of |source| to match that of |target|. 901 static bool 902 GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target) 903 { 904 MOZ_ASSERT(source->group() != target->group()); 905 906 if (!target->is<ArrayObject>() && !target->is<UnboxedArrayObject>()) 907 return true; 908 909 if (target->group()->maybePreliminaryObjects()) { 910 bool force = IsInsideNursery(source); 911 target->group()->maybePreliminaryObjects()->maybeAnalyze(cx, target->group(), force); 912 } 913 914 if (target->is<ArrayObject>()) { 915 ObjectGroup* sourceGroup = source->group(); 916 917 if (source->is<UnboxedArrayObject>()) { 918 Shape* shape = target->as<ArrayObject>().lastProperty(); 919 if (!UnboxedArrayObject::convertToNativeWithGroup(cx, source, target->group(), shape)) 920 return false; 921 } else if (source->is<ArrayObject>()) { 922 source->setGroup(target->group()); 923 } else { 924 return true; 925 } 926 927 if (sourceGroup->maybePreliminaryObjects()) 928 sourceGroup->maybePreliminaryObjects()->unregisterObject(source); 929 if (target->group()->maybePreliminaryObjects()) 930 target->group()->maybePreliminaryObjects()->registerNewObject(source); 931 932 for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) { 933 Value v = source->as<ArrayObject>().getDenseElement(i); 934 AddTypePropertyId(cx, source->group(), source, JSID_VOID, v); 935 } 936 937 return true; 938 } 939 940 if (target->is<UnboxedArrayObject>()) { 941 if (!source->is<UnboxedArrayObject>()) 942 return true; 943 if (source->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_INT32) 944 return true; 945 if (target->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_DOUBLE) 946 return true; 947 948 return source->as<UnboxedArrayObject>().convertInt32ToDouble(cx, target->group()); 949 } 950 951 return true; 952 } 953 954 static bool 955 SameGroup(JSObject* first, JSObject* second) 956 { 957 return first->group() == second->group(); 958 } 959 960 // When generating a multidimensional array of literals, such as 961 // [[1,2],[3,4],[5.5,6.5]], try to ensure that each element of the array has 962 // the same group. This is mainly important when the elements might have 963 // different native vs. unboxed layouts, or different unboxed layouts, and 964 // accessing the heterogenous layouts from JIT code will be much slower than 965 // if they were homogenous. 966 // 967 // To do this, with each new array element we compare it with one of the 968 // previous ones, and try to mutate the group of the new element to fit that 969 // of the old element. If this isn't possible, the groups for all old elements 970 // are mutated to fit that of the new element. 971 bool 972 js::CombineArrayElementTypes(ExclusiveContext* cx, JSObject* newObj, 973 const Value* compare, size_t ncompare) 974 { 975 if (!ncompare || !compare[0].isObject()) 976 return true; 977 978 JSObject* oldObj = &compare[0].toObject(); 979 if (SameGroup(oldObj, newObj)) 980 return true; 981 982 if (!GiveObjectGroup(cx, newObj, oldObj)) 983 return false; 984 985 if (SameGroup(oldObj, newObj)) 986 return true; 987 988 if (!GiveObjectGroup(cx, oldObj, newObj)) 989 return false; 990 991 if (SameGroup(oldObj, newObj)) { 992 for (size_t i = 1; i < ncompare; i++) { 993 if (compare[i].isObject() && !SameGroup(&compare[i].toObject(), newObj)) { 994 if (!GiveObjectGroup(cx, &compare[i].toObject(), newObj)) 995 return false; 996 } 997 } 998 } 999 1000 return true; 1001 } 1002 1003 // Similarly to CombineArrayElementTypes, if we are generating an array of 1004 // plain objects with a consistent property layout, such as 1005 // [{p:[1,2]},{p:[3,4]},{p:[5.5,6.5]}], where those plain objects in 1006 // turn have arrays as their own properties, try to ensure that a consistent 1007 // group is given to each array held by the same property of the plain objects. 1008 bool 1009 js::CombinePlainObjectPropertyTypes(ExclusiveContext* cx, JSObject* newObj, 1010 const Value* compare, size_t ncompare) 1011 { 1012 if (!ncompare || !compare[0].isObject()) 1013 return true; 1014 1015 JSObject* oldObj = &compare[0].toObject(); 1016 if (!SameGroup(oldObj, newObj)) 1017 return true; 1018 1019 if (newObj->is<PlainObject>()) { 1020 if (newObj->as<PlainObject>().lastProperty() != oldObj->as<PlainObject>().lastProperty()) 1021 return true; 1022 1023 for (size_t slot = 0; slot < newObj->as<PlainObject>().slotSpan(); slot++) { 1024 Value newValue = newObj->as<PlainObject>().getSlot(slot); 1025 Value oldValue = oldObj->as<PlainObject>().getSlot(slot); 1026 1027 if (!newValue.isObject() || !oldValue.isObject()) 1028 continue; 1029 1030 JSObject* newInnerObj = &newValue.toObject(); 1031 JSObject* oldInnerObj = &oldValue.toObject(); 1032 1033 if (SameGroup(oldInnerObj, newInnerObj)) 1034 continue; 1035 1036 if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj)) 1037 return false; 1038 1039 if (SameGroup(oldInnerObj, newInnerObj)) 1040 continue; 1041 1042 if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj)) 1043 return false; 1044 1045 if (SameGroup(oldInnerObj, newInnerObj)) { 1046 for (size_t i = 1; i < ncompare; i++) { 1047 if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) { 1048 Value otherValue = compare[i].toObject().as<PlainObject>().getSlot(slot); 1049 if (otherValue.isObject() && !SameGroup(&otherValue.toObject(), newInnerObj)) { 1050 if (!GiveObjectGroup(cx, &otherValue.toObject(), newInnerObj)) 1051 return false; 1052 } 1053 } 1054 } 1055 } 1056 } 1057 } else if (newObj->is<UnboxedPlainObject>()) { 1058 const UnboxedLayout& layout = newObj->as<UnboxedPlainObject>().layout(); 1059 const int32_t* traceList = layout.traceList(); 1060 if (!traceList) 1061 return true; 1062 1063 uint8_t* newData = newObj->as<UnboxedPlainObject>().data(); 1064 uint8_t* oldData = oldObj->as<UnboxedPlainObject>().data(); 1065 1066 for (; *traceList != -1; traceList++) {} 1067 traceList++; 1068 for (; *traceList != -1; traceList++) { 1069 JSObject* newInnerObj = *reinterpret_cast<JSObject**>(newData + *traceList); 1070 JSObject* oldInnerObj = *reinterpret_cast<JSObject**>(oldData + *traceList); 1071 1072 if (!newInnerObj || !oldInnerObj || SameGroup(oldInnerObj, newInnerObj)) 1073 continue; 1074 1075 if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj)) 1076 return false; 1077 1078 if (SameGroup(oldInnerObj, newInnerObj)) 1079 continue; 1080 1081 if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj)) 1082 return false; 1083 1084 if (SameGroup(oldInnerObj, newInnerObj)) { 1085 for (size_t i = 1; i < ncompare; i++) { 1086 if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) { 1087 uint8_t* otherData = compare[i].toObject().as<UnboxedPlainObject>().data(); 1088 JSObject* otherInnerObj = *reinterpret_cast<JSObject**>(otherData + *traceList); 1089 if (otherInnerObj && !SameGroup(otherInnerObj, newInnerObj)) { 1090 if (!GiveObjectGroup(cx, otherInnerObj, newInnerObj)) 1091 return false; 1092 } 1093 } 1094 } 1095 } 1096 } 1097 } 1098 1099 return true; 1100 } 1101 1102 ///////////////////////////////////////////////////////////////////// 1103 // ObjectGroupCompartment PlainObjectTable 1104 ///////////////////////////////////////////////////////////////////// 1105 1106 struct ObjectGroupCompartment::PlainObjectKey 1107 { 1108 jsid* properties; 1109 uint32_t nproperties; 1110 1111 struct Lookup { 1112 IdValuePair* properties; 1113 uint32_t nproperties; 1114 1115 Lookup(IdValuePair* properties, uint32_t nproperties) 1116 : properties(properties), nproperties(nproperties) 1117 {} 1118 }; 1119 1120 static inline HashNumber hash(const Lookup& lookup) { 1121 return (HashNumber) (HashId(lookup.properties[lookup.nproperties - 1].id) ^ 1122 lookup.nproperties); 1123 } 1124 1125 static inline bool match(const PlainObjectKey& v, const Lookup& lookup) { 1126 if (lookup.nproperties != v.nproperties) 1127 return false; 1128 for (size_t i = 0; i < lookup.nproperties; i++) { 1129 if (lookup.properties[i].id != v.properties[i]) 1130 return false; 1131 } 1132 return true; 1133 } 1134 1135 bool needsSweep() { 1136 for (unsigned i = 0; i < nproperties; i++) { 1137 if (gc::IsAboutToBeFinalizedUnbarriered(&properties[i])) 1138 return true; 1139 } 1140 return false; 1141 } 1142 }; 1143 1144 struct ObjectGroupCompartment::PlainObjectEntry 1145 { 1146 ReadBarrieredObjectGroup group; 1147 ReadBarrieredShape shape; 1148 TypeSet::Type* types; 1149 1150 bool needsSweep(unsigned nproperties) { 1151 if (IsAboutToBeFinalized(&group)) 1152 return true; 1153 if (IsAboutToBeFinalized(&shape)) 1154 return true; 1155 for (unsigned i = 0; i < nproperties; i++) { 1156 MOZ_ASSERT(!types[i].isSingleton()); 1157 if (types[i].isGroup()) { 1158 ObjectGroup* group = types[i].groupNoBarrier(); 1159 if (IsAboutToBeFinalizedUnbarriered(&group)) 1160 return true; 1161 if (group != types[i].groupNoBarrier()) 1162 types[i] = TypeSet::ObjectType(group); 1163 } 1164 } 1165 return false; 1166 } 1167 }; 1168 1169 static bool 1170 CanShareObjectGroup(IdValuePair* properties, size_t nproperties) 1171 { 1172 // Don't reuse groups for objects containing indexed properties, which 1173 // might end up as dense elements. 1174 for (size_t i = 0; i < nproperties; i++) { 1175 uint32_t index; 1176 if (IdIsIndex(properties[i].id, &index)) 1177 return false; 1178 } 1179 return true; 1180 } 1181 1182 static bool 1183 AddPlainObjectProperties(ExclusiveContext* cx, HandlePlainObject obj, 1184 IdValuePair* properties, size_t nproperties) 1185 { 1186 RootedId propid(cx); 1187 RootedValue value(cx); 1188 1189 for (size_t i = 0; i < nproperties; i++) { 1190 propid = properties[i].id; 1191 value = properties[i].value; 1192 if (!NativeDefineProperty(cx, obj, propid, value, nullptr, nullptr, JSPROP_ENUMERATE)) 1193 return false; 1194 } 1195 1196 return true; 1197 } 1198 1199 PlainObject* 1200 js::NewPlainObjectWithProperties(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties, 1201 NewObjectKind newKind) 1202 { 1203 gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties); 1204 RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, newKind)); 1205 if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties)) 1206 return nullptr; 1207 return obj; 1208 } 1209 1210 /* static */ JSObject* 1211 ObjectGroup::newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties, 1212 NewObjectKind newKind) 1213 { 1214 // Watch for simple cases where we don't try to reuse plain object groups. 1215 if (newKind == SingletonObject || nproperties == 0 || nproperties >= PropertyTree::MAX_HEIGHT) 1216 return NewPlainObjectWithProperties(cx, properties, nproperties, newKind); 1217 1218 ObjectGroupCompartment::PlainObjectTable*& table = 1219 cx->compartment()->objectGroups.plainObjectTable; 1220 1221 if (!table) { 1222 table = cx->new_<ObjectGroupCompartment::PlainObjectTable>(); 1223 if (!table || !table->init()) { 1224 ReportOutOfMemory(cx); 1225 js_delete(table); 1226 table = nullptr; 1227 return nullptr; 1228 } 1229 } 1230 1231 ObjectGroupCompartment::PlainObjectKey::Lookup lookup(properties, nproperties); 1232 ObjectGroupCompartment::PlainObjectTable::Ptr p = table->lookup(lookup); 1233 1234 if (!p) { 1235 if (!CanShareObjectGroup(properties, nproperties)) 1236 return NewPlainObjectWithProperties(cx, properties, nproperties, newKind); 1237 1238 RootedObject proto(cx); 1239 if (!GetBuiltinPrototype(cx, JSProto_Object, &proto)) 1240 return nullptr; 1241 1242 Rooted<TaggedProto> tagged(cx, TaggedProto(proto)); 1243 RootedObjectGroup group(cx, ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, 1244 tagged)); 1245 if (!group) 1246 return nullptr; 1247 1248 gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties); 1249 RootedPlainObject obj(cx, NewObjectWithGroup<PlainObject>(cx, group, 1250 allocKind, TenuredObject)); 1251 if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties)) 1252 return nullptr; 1253 1254 // Don't make entries with duplicate property names, which will show up 1255 // here as objects with fewer properties than we thought we were 1256 // adding to the object. In this case, reset the object's group to the 1257 // default (which will have unknown properties) so that the group we 1258 // just created will be collected by the GC. 1259 if (obj->slotSpan() != nproperties) { 1260 ObjectGroup* group = defaultNewGroup(cx, obj->getClass(), obj->taggedProto()); 1261 if (!group) 1262 return nullptr; 1263 obj->setGroup(group); 1264 return obj; 1265 } 1266 1267 // Keep track of the initial objects we create with this type. 1268 // If the initial ones have a consistent shape and property types, we 1269 // will try to use an unboxed layout for the group. 1270 PreliminaryObjectArrayWithTemplate* preliminaryObjects = 1271 cx->new_<PreliminaryObjectArrayWithTemplate>(obj->lastProperty()); 1272 if (!preliminaryObjects) 1273 return nullptr; 1274 group->setPreliminaryObjects(preliminaryObjects); 1275 preliminaryObjects->registerNewObject(obj); 1276 1277 ScopedJSFreePtr<jsid> ids(group->zone()->pod_calloc<jsid>(nproperties)); 1278 if (!ids) { 1279 ReportOutOfMemory(cx); 1280 return nullptr; 1281 } 1282 1283 ScopedJSFreePtr<TypeSet::Type> types( 1284 group->zone()->pod_calloc<TypeSet::Type>(nproperties)); 1285 if (!types) { 1286 ReportOutOfMemory(cx); 1287 return nullptr; 1288 } 1289 1290 for (size_t i = 0; i < nproperties; i++) { 1291 ids[i] = properties[i].id; 1292 types[i] = GetValueTypeForTable(obj->getSlot(i)); 1293 AddTypePropertyId(cx, group, nullptr, IdToTypeId(ids[i]), types[i]); 1294 } 1295 1296 ObjectGroupCompartment::PlainObjectKey key; 1297 key.properties = ids; 1298 key.nproperties = nproperties; 1299 MOZ_ASSERT(ObjectGroupCompartment::PlainObjectKey::match(key, lookup)); 1300 1301 ObjectGroupCompartment::PlainObjectEntry entry; 1302 entry.group.set(group); 1303 entry.shape.set(obj->lastProperty()); 1304 entry.types = types; 1305 1306 ObjectGroupCompartment::PlainObjectTable::AddPtr np = table->lookupForAdd(lookup); 1307 if (!table->add(np, key, entry)) { 1308 ReportOutOfMemory(cx); 1309 return nullptr; 1310 } 1311 1312 ids.forget(); 1313 types.forget(); 1314 1315 return obj; 1316 } 1317 1318 RootedObjectGroup group(cx, p->value().group); 1319 1320 // Watch for existing groups which now use an unboxed layout. 1321 if (group->maybeUnboxedLayout()) { 1322 MOZ_ASSERT(group->unboxedLayout().properties().length() == nproperties); 1323 return UnboxedPlainObject::createWithProperties(cx, group, newKind, properties); 1324 } 1325 1326 // Update property types according to the properties we are about to add. 1327 // Do this before we do anything which can GC, which might move or remove 1328 // this table entry. 1329 if (!group->unknownProperties()) { 1330 for (size_t i = 0; i < nproperties; i++) { 1331 TypeSet::Type type = p->value().types[i]; 1332 TypeSet::Type ntype = GetValueTypeForTable(properties[i].value); 1333 if (ntype == type) 1334 continue; 1335 if (ntype.isPrimitive(JSVAL_TYPE_INT32) && 1336 type.isPrimitive(JSVAL_TYPE_DOUBLE)) 1337 { 1338 // The property types already reflect 'int32'. 1339 } else { 1340 if (ntype.isPrimitive(JSVAL_TYPE_DOUBLE) && 1341 type.isPrimitive(JSVAL_TYPE_INT32)) 1342 { 1343 // Include 'double' in the property types to avoid the update below later. 1344 p->value().types[i] = TypeSet::DoubleType(); 1345 } 1346 AddTypePropertyId(cx, group, nullptr, IdToTypeId(properties[i].id), ntype); 1347 } 1348 } 1349 } 1350 1351 RootedShape shape(cx, p->value().shape); 1352 1353 if (group->maybePreliminaryObjects()) 1354 newKind = TenuredObject; 1355 1356 gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties); 1357 RootedPlainObject obj(cx, NewObjectWithGroup<PlainObject>(cx, group, allocKind, 1358 newKind)); 1359 1360 if (!obj || !obj->setLastProperty(cx, shape)) 1361 return nullptr; 1362 1363 for (size_t i = 0; i < nproperties; i++) 1364 obj->setSlot(i, properties[i].value); 1365 1366 if (group->maybePreliminaryObjects()) { 1367 group->maybePreliminaryObjects()->registerNewObject(obj); 1368 group->maybePreliminaryObjects()->maybeAnalyze(cx, group); 1369 } 1370 1371 return obj; 1372 } 1373 1374 ///////////////////////////////////////////////////////////////////// 1375 // ObjectGroupCompartment AllocationSiteTable 1376 ///////////////////////////////////////////////////////////////////// 1377 1378 struct ObjectGroupCompartment::AllocationSiteKey : public DefaultHasher<AllocationSiteKey> { 1379 ReadBarrieredScript script; 1380 1381 uint32_t offset : 24; 1382 JSProtoKey kind : 8; 1383 1384 ReadBarrieredObject proto; 1385 1386 static const uint32_t OFFSET_LIMIT = (1 << 23); 1387 1388 AllocationSiteKey(JSScript* script_, uint32_t offset_, JSProtoKey kind_, JSObject* proto_) 1389 : script(script_), offset(offset_), kind(kind_), proto(proto_) 1390 { 1391 MOZ_ASSERT(offset_ < OFFSET_LIMIT); 1392 } 1393 1394 AllocationSiteKey(const AllocationSiteKey& key) 1395 : script(key.script), 1396 offset(key.offset), 1397 kind(key.kind), 1398 proto(key.proto) 1399 { } 1400 1401 AllocationSiteKey(AllocationSiteKey&& key) 1402 : script(mozilla::Move(key.script)), 1403 offset(key.offset), 1404 kind(key.kind), 1405 proto(mozilla::Move(key.proto)) 1406 { } 1407 1408 void operator=(AllocationSiteKey&& key) { 1409 script = mozilla::Move(key.script); 1410 offset = key.offset; 1411 kind = key.kind; 1412 proto = mozilla::Move(key.proto); 1413 } 1414 1415 static inline uint32_t hash(AllocationSiteKey key) { 1416 return uint32_t(size_t(key.script->offsetToPC(key.offset)) ^ key.kind ^ 1417 MovableCellHasher<JSObject*>::hash(key.proto)); 1418 } 1419 1420 static inline bool match(const AllocationSiteKey& a, const AllocationSiteKey& b) { 1421 return DefaultHasher<JSScript*>::match(a.script, b.script) && 1422 a.offset == b.offset && 1423 a.kind == b.kind && 1424 MovableCellHasher<JSObject*>::match(a.proto, b.proto); 1425 } 1426 1427 void trace(JSTracer* trc) { 1428 TraceRoot(trc, &script, "AllocationSiteKey script"); 1429 TraceNullableRoot(trc, &proto, "AllocationSiteKey proto"); 1430 } 1431 1432 bool needsSweep() { 1433 return IsAboutToBeFinalizedUnbarriered(script.unsafeGet()) || 1434 (proto && IsAboutToBeFinalizedUnbarriered(proto.unsafeGet())); 1435 } 1436 }; 1437 1438 class ObjectGroupCompartment::AllocationSiteTable 1439 : public JS::WeakCache<js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup, 1440 AllocationSiteKey, SystemAllocPolicy>> 1441 { 1442 using Table = js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup, 1443 AllocationSiteKey, SystemAllocPolicy>; 1444 using Base = JS::WeakCache<Table>; 1445 1446 public: 1447 explicit AllocationSiteTable(Zone* zone) : Base(zone, Table()) {} 1448 }; 1449 1450 /* static */ ObjectGroup* 1451 ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode* pc, 1452 JSProtoKey kind, HandleObject protoArg /* = nullptr */) 1453 { 1454 MOZ_ASSERT(!useSingletonForAllocationSite(scriptArg, pc, kind)); 1455 MOZ_ASSERT_IF(protoArg, kind == JSProto_Array); 1456 1457 uint32_t offset = scriptArg->pcToOffset(pc); 1458 1459 if (offset >= ObjectGroupCompartment::AllocationSiteKey::OFFSET_LIMIT) { 1460 if (protoArg) 1461 return defaultNewGroup(cx, GetClassForProtoKey(kind), TaggedProto(protoArg)); 1462 return defaultNewGroup(cx, kind); 1463 } 1464 1465 ObjectGroupCompartment::AllocationSiteTable*& table = 1466 cx->compartment()->objectGroups.allocationSiteTable; 1467 1468 if (!table) { 1469 table = cx->new_<ObjectGroupCompartment::AllocationSiteTable>(cx->zone()); 1470 if (!table || !table->init()) { 1471 ReportOutOfMemory(cx); 1472 js_delete(table); 1473 table = nullptr; 1474 return nullptr; 1475 } 1476 } 1477 1478 RootedScript script(cx, scriptArg); 1479 RootedObject proto(cx, protoArg); 1480 if (!proto && kind != JSProto_Null && !GetBuiltinPrototype(cx, kind, &proto)) 1481 return nullptr; 1482 1483 Rooted<ObjectGroupCompartment::AllocationSiteKey> key(cx, 1484 ObjectGroupCompartment::AllocationSiteKey(script, offset, kind, proto)); 1485 1486 ObjectGroupCompartment::AllocationSiteTable::AddPtr p = table->lookupForAdd(key); 1487 if (p) 1488 return p->value(); 1489 1490 AutoEnterAnalysis enter(cx); 1491 1492 Rooted<TaggedProto> tagged(cx, TaggedProto(proto)); 1493 ObjectGroup* res = ObjectGroupCompartment::makeGroup(cx, GetClassForProtoKey(kind), tagged, 1494 OBJECT_FLAG_FROM_ALLOCATION_SITE); 1495 if (!res) 1496 return nullptr; 1497 1498 if (JSOp(*pc) == JSOP_NEWOBJECT) { 1499 // Keep track of the preliminary objects with this group, so we can try 1500 // to use an unboxed layout for the object once some are allocated. 1501 Shape* shape = script->getObject(pc)->as<PlainObject>().lastProperty(); 1502 if (!shape->isEmptyShape()) { 1503 PreliminaryObjectArrayWithTemplate* preliminaryObjects = 1504 cx->new_<PreliminaryObjectArrayWithTemplate>(shape); 1505 if (preliminaryObjects) 1506 res->setPreliminaryObjects(preliminaryObjects); 1507 else 1508 cx->recoverFromOutOfMemory(); 1509 } 1510 } 1511 1512 if (kind == JSProto_Array && 1513 (JSOp(*pc) == JSOP_NEWARRAY || IsCallPC(pc)) && 1514 cx->options().unboxedArrays()) 1515 { 1516 PreliminaryObjectArrayWithTemplate* preliminaryObjects = 1517 cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr); 1518 if (preliminaryObjects) 1519 res->setPreliminaryObjects(preliminaryObjects); 1520 else 1521 cx->recoverFromOutOfMemory(); 1522 } 1523 1524 if (!table->add(p, key, res)) { 1525 ReportOutOfMemory(cx); 1526 return nullptr; 1527 } 1528 1529 return res; 1530 } 1531 1532 void 1533 ObjectGroupCompartment::replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc, 1534 JSProtoKey kind, ObjectGroup* group) 1535 { 1536 AllocationSiteKey key(script, script->pcToOffset(pc), kind, group->proto().toObjectOrNull()); 1537 1538 AllocationSiteTable::Ptr p = allocationSiteTable->lookup(key); 1539 MOZ_RELEASE_ASSERT(p); 1540 allocationSiteTable->get().remove(p); 1541 { 1542 AutoEnterOOMUnsafeRegion oomUnsafe; 1543 if (!allocationSiteTable->putNew(key, group)) 1544 oomUnsafe.crash("Inconsistent object table"); 1545 } 1546 } 1547 1548 /* static */ ObjectGroup* 1549 ObjectGroup::callingAllocationSiteGroup(JSContext* cx, JSProtoKey key, HandleObject proto) 1550 { 1551 MOZ_ASSERT_IF(proto, key == JSProto_Array); 1552 1553 jsbytecode* pc; 1554 RootedScript script(cx, cx->currentScript(&pc)); 1555 if (script) 1556 return allocationSiteGroup(cx, script, pc, key, proto); 1557 if (proto) 1558 return defaultNewGroup(cx, GetClassForProtoKey(key), TaggedProto(proto)); 1559 return defaultNewGroup(cx, key); 1560 } 1561 1562 /* static */ bool 1563 ObjectGroup::setAllocationSiteObjectGroup(JSContext* cx, 1564 HandleScript script, jsbytecode* pc, 1565 HandleObject obj, bool singleton) 1566 { 1567 JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass()); 1568 MOZ_ASSERT(key != JSProto_Null); 1569 MOZ_ASSERT(singleton == useSingletonForAllocationSite(script, pc, key)); 1570 1571 if (singleton) { 1572 MOZ_ASSERT(obj->isSingleton()); 1573 1574 /* 1575 * Inference does not account for types of run-once initializer 1576 * objects, as these may not be created until after the script 1577 * has been analyzed. 1578 */ 1579 TypeScript::Monitor(cx, script, pc, ObjectValue(*obj)); 1580 } else { 1581 ObjectGroup* group = allocationSiteGroup(cx, script, pc, key); 1582 if (!group) 1583 return false; 1584 obj->setGroup(group); 1585 } 1586 1587 return true; 1588 } 1589 1590 /* static */ ArrayObject* 1591 ObjectGroup::getOrFixupCopyOnWriteObject(JSContext* cx, HandleScript script, jsbytecode* pc) 1592 { 1593 // Make sure that the template object for script/pc has a type indicating 1594 // that the object and its copies have copy on write elements. 1595 RootedArrayObject obj(cx, &script->getObject(GET_UINT32_INDEX(pc))->as<ArrayObject>()); 1596 MOZ_ASSERT(obj->denseElementsAreCopyOnWrite()); 1597 1598 if (obj->group()->fromAllocationSite()) { 1599 MOZ_ASSERT(obj->group()->hasAnyFlags(OBJECT_FLAG_COPY_ON_WRITE)); 1600 return obj; 1601 } 1602 1603 RootedObjectGroup group(cx, allocationSiteGroup(cx, script, pc, JSProto_Array)); 1604 if (!group) 1605 return nullptr; 1606 1607 group->addFlags(OBJECT_FLAG_COPY_ON_WRITE); 1608 1609 // Update type information in the initializer object group. 1610 MOZ_ASSERT(obj->slotSpan() == 0); 1611 for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) { 1612 const Value& v = obj->getDenseElement(i); 1613 AddTypePropertyId(cx, group, nullptr, JSID_VOID, v); 1614 } 1615 1616 obj->setGroup(group); 1617 return obj; 1618 } 1619 1620 /* static */ ArrayObject* 1621 ObjectGroup::getCopyOnWriteObject(JSScript* script, jsbytecode* pc) 1622 { 1623 // getOrFixupCopyOnWriteObject should already have been called for 1624 // script/pc, ensuring that the template object has a group with the 1625 // COPY_ON_WRITE flag. We don't assert this here, due to a corner case 1626 // where this property doesn't hold. See jsop_newarray_copyonwrite in 1627 // IonBuilder. 1628 ArrayObject* obj = &script->getObject(GET_UINT32_INDEX(pc))->as<ArrayObject>(); 1629 MOZ_ASSERT(obj->denseElementsAreCopyOnWrite()); 1630 1631 return obj; 1632 } 1633 1634 /* static */ bool 1635 ObjectGroup::findAllocationSite(JSContext* cx, ObjectGroup* group, 1636 JSScript** script, uint32_t* offset) 1637 { 1638 *script = nullptr; 1639 *offset = 0; 1640 1641 const ObjectGroupCompartment::AllocationSiteTable* table = 1642 cx->compartment()->objectGroups.allocationSiteTable; 1643 1644 if (!table) 1645 return false; 1646 1647 for (ObjectGroupCompartment::AllocationSiteTable::Range r = table->all(); 1648 !r.empty(); 1649 r.popFront()) 1650 { 1651 if (group == r.front().value()) { 1652 *script = r.front().key().script; 1653 *offset = r.front().key().offset; 1654 return true; 1655 } 1656 } 1657 1658 return false; 1659 } 1660 1661 ///////////////////////////////////////////////////////////////////// 1662 // ObjectGroupCompartment 1663 ///////////////////////////////////////////////////////////////////// 1664 1665 ObjectGroupCompartment::ObjectGroupCompartment() 1666 { 1667 PodZero(this); 1668 } 1669 1670 ObjectGroupCompartment::~ObjectGroupCompartment() 1671 { 1672 js_delete(defaultNewTable); 1673 js_delete(lazyTable); 1674 js_delete(arrayObjectTable); 1675 js_delete(plainObjectTable); 1676 js_delete(allocationSiteTable); 1677 } 1678 1679 void 1680 ObjectGroupCompartment::removeDefaultNewGroup(const Class* clasp, TaggedProto proto, 1681 JSObject* associated) 1682 { 1683 auto p = defaultNewTable->lookup(NewEntry::Lookup(clasp, proto, associated)); 1684 MOZ_RELEASE_ASSERT(p); 1685 1686 defaultNewTable->get().remove(p); 1687 } 1688 1689 void 1690 ObjectGroupCompartment::replaceDefaultNewGroup(const Class* clasp, TaggedProto proto, 1691 JSObject* associated, ObjectGroup* group) 1692 { 1693 NewEntry::Lookup lookup(clasp, proto, associated); 1694 1695 auto p = defaultNewTable->lookup(lookup); 1696 MOZ_RELEASE_ASSERT(p); 1697 defaultNewTable->get().remove(p); 1698 { 1699 AutoEnterOOMUnsafeRegion oomUnsafe; 1700 if (!defaultNewTable->putNew(lookup, NewEntry(group, associated))) 1701 oomUnsafe.crash("Inconsistent object table"); 1702 } 1703 } 1704 1705 /* static */ 1706 ObjectGroup* 1707 ObjectGroupCompartment::makeGroup(ExclusiveContext* cx, const Class* clasp, 1708 Handle<TaggedProto> proto, 1709 ObjectGroupFlags initialFlags /* = 0 */) 1710 { 1711 MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); 1712 1713 ObjectGroup* group = Allocate<ObjectGroup>(cx); 1714 if (!group) 1715 return nullptr; 1716 new(group) ObjectGroup(clasp, proto, cx->compartment(), initialFlags); 1717 1718 return group; 1719 } 1720 1721 void 1722 ObjectGroupCompartment::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, 1723 size_t* allocationSiteTables, 1724 size_t* arrayObjectGroupTables, 1725 size_t* plainObjectGroupTables, 1726 size_t* compartmentTables) 1727 { 1728 if (allocationSiteTable) 1729 *allocationSiteTables += allocationSiteTable->sizeOfIncludingThis(mallocSizeOf); 1730 1731 if (arrayObjectTable) 1732 *arrayObjectGroupTables += arrayObjectTable->sizeOfIncludingThis(mallocSizeOf); 1733 1734 if (plainObjectTable) { 1735 *plainObjectGroupTables += plainObjectTable->sizeOfIncludingThis(mallocSizeOf); 1736 1737 for (PlainObjectTable::Enum e(*plainObjectTable); 1738 !e.empty(); 1739 e.popFront()) 1740 { 1741 const PlainObjectKey& key = e.front().key(); 1742 const PlainObjectEntry& value = e.front().value(); 1743 1744 /* key.ids and values.types have the same length. */ 1745 *plainObjectGroupTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types); 1746 } 1747 } 1748 1749 if (defaultNewTable) 1750 *compartmentTables += defaultNewTable->sizeOfIncludingThis(mallocSizeOf); 1751 1752 if (lazyTable) 1753 *compartmentTables += lazyTable->sizeOfIncludingThis(mallocSizeOf); 1754 } 1755 1756 void 1757 ObjectGroupCompartment::clearTables() 1758 { 1759 if (allocationSiteTable && allocationSiteTable->initialized()) 1760 allocationSiteTable->clear(); 1761 if (arrayObjectTable && arrayObjectTable->initialized()) 1762 arrayObjectTable->clear(); 1763 if (plainObjectTable && plainObjectTable->initialized()) { 1764 for (PlainObjectTable::Enum e(*plainObjectTable); !e.empty(); e.popFront()) { 1765 const PlainObjectKey& key = e.front().key(); 1766 PlainObjectEntry& entry = e.front().value(); 1767 js_free(key.properties); 1768 js_free(entry.types); 1769 } 1770 plainObjectTable->clear(); 1771 } 1772 if (defaultNewTable && defaultNewTable->initialized()) 1773 defaultNewTable->clear(); 1774 if (lazyTable && lazyTable->initialized()) 1775 lazyTable->clear(); 1776 } 1777 1778 /* static */ bool 1779 ObjectGroupCompartment::PlainObjectTableSweepPolicy::needsSweep(PlainObjectKey* key, 1780 PlainObjectEntry* entry) 1781 { 1782 if (!(JS::GCPolicy<PlainObjectKey>::needsSweep(key) || entry->needsSweep(key->nproperties))) 1783 return false; 1784 js_free(key->properties); 1785 js_free(entry->types); 1786 return true; 1787 } 1788 1789 void 1790 ObjectGroupCompartment::sweep(FreeOp* fop) 1791 { 1792 /* 1793 * Iterate through the array/object group tables and remove all entries 1794 * referencing collected data. These tables only hold weak references. 1795 */ 1796 1797 if (arrayObjectTable) 1798 arrayObjectTable->sweep(); 1799 if (plainObjectTable) 1800 plainObjectTable->sweep(); 1801 } 1802 1803 void 1804 ObjectGroupCompartment::fixupNewTableAfterMovingGC(NewTable* table) 1805 { 1806 /* 1807 * Each entry's hash depends on the object's prototype and we can't tell 1808 * whether that has been moved or not in sweepNewObjectGroupTable(). 1809 */ 1810 if (table && table->initialized()) { 1811 for (NewTable::Enum e(*table); !e.empty(); e.popFront()) { 1812 NewEntry& entry = e.mutableFront(); 1813 1814 ObjectGroup* group = entry.group.unbarrieredGet(); 1815 if (IsForwarded(group)) { 1816 group = Forwarded(group); 1817 entry.group.set(group); 1818 } 1819 TaggedProto proto = group->proto(); 1820 if (proto.isObject() && IsForwarded(proto.toObject())) { 1821 proto = TaggedProto(Forwarded(proto.toObject())); 1822 // Update the group's proto here so that we are able to lookup 1823 // entries in this table before all object pointers are updated. 1824 group->proto() = proto; 1825 } 1826 if (entry.associated && IsForwarded(entry.associated)) 1827 entry.associated = Forwarded(entry.associated); 1828 } 1829 } 1830 } 1831 1832 #ifdef JSGC_HASH_TABLE_CHECKS 1833 1834 void 1835 ObjectGroupCompartment::checkNewTableAfterMovingGC(NewTable* table) 1836 { 1837 /* 1838 * Assert that nothing points into the nursery or needs to be relocated, and 1839 * that the hash table entries are discoverable. 1840 */ 1841 if (!table || !table->initialized()) 1842 return; 1843 1844 for (NewTable::Enum e(*table); !e.empty(); e.popFront()) { 1845 NewEntry entry = e.front(); 1846 CheckGCThingAfterMovingGC(entry.group.unbarrieredGet()); 1847 TaggedProto proto = entry.group.unbarrieredGet()->proto(); 1848 if (proto.isObject()) 1849 CheckGCThingAfterMovingGC(proto.toObject()); 1850 CheckGCThingAfterMovingGC(entry.associated); 1851 1852 const Class* clasp = entry.group.unbarrieredGet()->clasp(); 1853 if (entry.associated && entry.associated->is<JSFunction>()) 1854 clasp = nullptr; 1855 1856 NewEntry::Lookup lookup(clasp, proto, entry.associated); 1857 auto ptr = table->lookup(lookup); 1858 MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front()); 1859 } 1860 } 1861 1862 #endif // JSGC_HASH_TABLE_CHECKS 1863