diff --git a/js/src/jit-test/tests/basic/globals-shared-shapes.js b/js/src/jit-test/tests/basic/globals-shared-shapes.js index f2f0013ce248..6c879598ce1e 100644 --- a/js/src/jit-test/tests/basic/globals-shared-shapes.js +++ b/js/src/jit-test/tests/basic/globals-shared-shapes.js @@ -1,30 +1,13 @@ -// Test that objects in different compartments can have the same shape. +// Test that objects in different compartments can have the same shape if they +// have a null proto. var g1 = newGlobal(); var g2 = newGlobal({sameZoneAs: g1}); g1.eval("x1 = {foo: 1}"); g2.eval("x2 = {foo: 2}"); -assertEq(unwrappedObjectsHaveSameShape(g1.x1, g2.x2), true); - -g1.eval("x1 = [1]"); -g2.eval("x2 = [2]"); -assertEq(unwrappedObjectsHaveSameShape(g1.x1, g2.x2), true); - -g1.eval("x1 = function f(){}"); -g2.eval("x2 = function f(){}"); -assertEq(unwrappedObjectsHaveSameShape(g1.x1, g2.x2), true); - -g1.eval("x1 = /abc/;"); -g2.eval("x2 = /def/"); -assertEq(unwrappedObjectsHaveSameShape(g1.x1, g2.x2), true); - -// Now the same, but we change Array.prototype.__proto__. -// The arrays should no longer get the same Shape, as their -// proto chain is different. -g1 = newGlobal(); -g2 = newGlobal({sameZoneAs: g1}); -g1.eval("x1 = [1];"); -g2.eval("Array.prototype.__proto__ = Math;"); -g2.eval("x2 = [2];"); assertEq(unwrappedObjectsHaveSameShape(g1.x1, g2.x2), false); + +g1.eval("x1 = Object.create(null); x1.foo = 1;"); +g2.eval("x2 = Object.create(null); x2.foo = 2;"); +assertEq(unwrappedObjectsHaveSameShape(g1.x1, g2.x2), true); diff --git a/js/src/jit-test/tests/realms/bug1518753.js b/js/src/jit-test/tests/realms/bug1518753.js new file mode 100644 index 000000000000..61743d311166 --- /dev/null +++ b/js/src/jit-test/tests/realms/bug1518753.js @@ -0,0 +1,13 @@ +var g = newGlobal({sameCompartmentAs: this}); + +var o1 = Array(1, 2); +var o2 = g.Array(1, 2); +Array.prototype.x = 10; + +function test(o, v) { + for (var i = 0; i < 15; i++) { + assertEq(o.x, v); + } +} +test(o1, 10); +test(o2, undefined); diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index 2247dd80f3ba..eb61ca325f33 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -1593,7 +1593,7 @@ void BaseShape::finalize(FreeOp* fop) { inline InitialShapeEntry::InitialShapeEntry() : shape(nullptr), proto() {} inline InitialShapeEntry::InitialShapeEntry(Shape* shape, - const Lookup::ShapeProto& proto) + const TaggedProto& proto) : shape(shape), proto(proto) {} #ifdef JSGC_HASH_TABLE_CHECKS @@ -1606,8 +1606,7 @@ void Zone::checkInitialShapesTableAfterMovingGC() { */ for (auto r = initialShapes().all(); !r.empty(); r.popFront()) { InitialShapeEntry entry = r.front(); - JSProtoKey protoKey = entry.proto.key(); - TaggedProto proto = entry.proto.proto().unbarrieredGet(); + TaggedProto proto = entry.proto.unbarrieredGet(); Shape* shape = entry.shape.unbarrieredGet(); CheckGCThingAfterMovingGC(shape); @@ -1616,8 +1615,8 @@ void Zone::checkInitialShapesTableAfterMovingGC() { } using Lookup = InitialShapeEntry::Lookup; - Lookup lookup(shape->getObjectClass(), Lookup::ShapeProto(protoKey, proto), - shape->numFixedSlots(), shape->getObjectFlags()); + Lookup lookup(shape->getObjectClass(), proto, shape->numFixedSlots(), + shape->getObjectFlags()); InitialShapeSet::Ptr ptr = initialShapes().lookup(lookup); MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); } @@ -2053,59 +2052,6 @@ void Shape::dumpSubtree(int level, js::GenericPrinter& out) const { #endif -static bool IsOriginalProto(GlobalObject* global, JSProtoKey key, - NativeObject& proto) { - if (global->getPrototype(key) != ObjectValue(proto)) { - return false; - } - - MOZ_ASSERT(&proto.global() == global); - - if (key == JSProto_Object) { - MOZ_ASSERT(proto.staticPrototypeIsImmutable(), - "proto should be Object.prototype, whose prototype is " - "immutable"); - MOZ_ASSERT(proto.staticPrototype() == nullptr, - "Object.prototype must have null prototype"); - return true; - } - - // Check that other prototypes still have Object.prototype as proto. - JSObject* protoProto = proto.staticPrototype(); - if (!protoProto || - global->getPrototype(JSProto_Object) != ObjectValue(*protoProto)) { - return false; - } - - MOZ_ASSERT(protoProto->staticPrototypeIsImmutable(), - "protoProto should be Object.prototype, whose prototype is " - "immutable"); - MOZ_ASSERT(protoProto->staticPrototype() == nullptr, - "Object.prototype must have null prototype"); - return true; -} - -static JSProtoKey GetInitialShapeProtoKey(TaggedProto proto, JSContext* cx) { - if (proto.isObject() && proto.toObject()->isNative()) { - GlobalObject* global = cx->global(); - NativeObject& obj = proto.toObject()->as(); - - if (IsOriginalProto(global, JSProto_Object, obj)) { - return JSProto_Object; - } - if (IsOriginalProto(global, JSProto_Function, obj)) { - return JSProto_Function; - } - if (IsOriginalProto(global, JSProto_Array, obj)) { - return JSProto_Array; - } - if (IsOriginalProto(global, JSProto_RegExp, obj)) { - return JSProto_RegExp; - } - } - return JSProto_LIMIT; -} - /* static */ Shape* EmptyShape::getInitialShape(JSContext* cx, const Class* clasp, TaggedProto proto, @@ -2117,61 +2063,28 @@ static JSProtoKey GetInitialShapeProtoKey(TaggedProto proto, JSContext* cx) { auto& table = cx->zone()->initialShapes(); using Lookup = InitialShapeEntry::Lookup; - auto protoPointer = MakeDependentAddPtr( - cx, table, Lookup(clasp, Lookup::ShapeProto(proto), nfixed, objectFlags)); + auto protoPointer = + MakeDependentAddPtr(cx, table, Lookup(clasp, proto, nfixed, objectFlags)); if (protoPointer) { return protoPointer->shape; } - // No entry for this proto. If the proto is one of a few common builtin - // prototypes, try to do a lookup based on the JSProtoKey, so we can share - // shapes across globals. Rooted protoRoot(cx, proto); - Shape* shape = nullptr; - bool insertKey = false; - mozilla::Maybe> keyPointer; - - JSProtoKey key = GetInitialShapeProtoKey(protoRoot, cx); - if (key != JSProto_LIMIT) { - keyPointer.emplace(MakeDependentAddPtr( - cx, table, - Lookup(clasp, Lookup::ShapeProto(key), nfixed, objectFlags))); - if (keyPointer.ref()) { - shape = keyPointer.ref()->shape; - MOZ_ASSERT(shape); - } else { - insertKey = true; - } - } - - if (!shape) { - StackBaseShape base(clasp, objectFlags); - Rooted nbase(cx, BaseShape::getUnowned(cx, base)); - if (!nbase) { - return nullptr; - } - - shape = EmptyShape::new_(cx, nbase, nfixed); - if (!shape) { - return nullptr; - } - } - - Lookup::ShapeProto shapeProto(protoRoot); - Lookup lookup(clasp, shapeProto, nfixed, objectFlags); - if (!protoPointer.add(cx, table, lookup, - InitialShapeEntry(shape, shapeProto))) { + StackBaseShape base(clasp, objectFlags); + Rooted nbase(cx, BaseShape::getUnowned(cx, base)); + if (!nbase) { return nullptr; } - // Also add an entry based on the JSProtoKey, if needed. - if (insertKey) { - Lookup::ShapeProto shapeProto(key); - Lookup lookup(clasp, shapeProto, nfixed, objectFlags); - if (!keyPointer->add(cx, table, lookup, - InitialShapeEntry(shape, shapeProto))) { - return nullptr; - } + RootedShape shape(cx, EmptyShape::new_(cx, nbase, nfixed)); + if (!shape) { + return nullptr; + } + + Lookup lookup(clasp, protoRoot, nfixed, objectFlags); + if (!protoPointer.add(cx, table, lookup, + InitialShapeEntry(shape, protoRoot))) { + return nullptr; } return shape; @@ -2223,7 +2136,7 @@ void NewObjectCache::invalidateEntriesForShape(JSContext* cx, HandleShape shape, HandleShape shape, HandleObject proto) { using Lookup = InitialShapeEntry::Lookup; - Lookup lookup(shape->getObjectClass(), Lookup::ShapeProto(TaggedProto(proto)), + Lookup lookup(shape->getObjectClass(), TaggedProto(proto), shape->numFixedSlots(), shape->getObjectFlags()); InitialShapeSet::Ptr p = cx->zone()->initialShapes().lookup(lookup); @@ -2248,22 +2161,6 @@ void NewObjectCache::invalidateEntriesForShape(JSContext* cx, HandleShape shape, entry.shape = ReadBarrieredShape(shape); - // For certain prototypes -- namely, those of various builtin classes, - // keyed by JSProtoKey |key| -- there are two entries: one for a lookup - // via |proto|, and one for a lookup via |key|. If this is such a - // prototype, also update the alternate |key|-keyed shape. - JSProtoKey key = GetInitialShapeProtoKey(TaggedProto(proto), cx); - if (key != JSProto_LIMIT) { - Lookup lookup(shape->getObjectClass(), Lookup::ShapeProto(key), - shape->numFixedSlots(), shape->getObjectFlags()); - if (InitialShapeSet::Ptr p = cx->zone()->initialShapes().lookup(lookup)) { - InitialShapeEntry& entry = const_cast(*p); - if (entry.shape != shape) { - entry.shape = ReadBarrieredShape(shape); - } - } - } - /* * This affects the shape that will be produced by the various NewObject * methods, so clear any cache entry referring to the old shape. This is @@ -2292,12 +2189,12 @@ void Zone::fixupInitialShapeTable() { // If the prototype has moved we have to rekey the entry. InitialShapeEntry entry = e.front(); // Use unbarrieredGet() to prevent triggering read barrier while collecting. - const TaggedProto& proto = entry.proto.proto().unbarrieredGet(); + const TaggedProto& proto = entry.proto.unbarrieredGet(); if (proto.isObject() && IsForwarded(proto.toObject())) { - entry.proto.setProto(TaggedProto(Forwarded(proto.toObject()))); + entry.proto = TaggedProto(Forwarded(proto.toObject())); using Lookup = InitialShapeEntry::Lookup; - Lookup relookup(shape->getObjectClass(), Lookup::ShapeProto(proto), - shape->numFixedSlots(), shape->getObjectFlags()); + Lookup relookup(shape->getObjectClass(), proto, shape->numFixedSlots(), + shape->getObjectFlags()); e.rekeyFront(relookup, entry); } } diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index 1cc6cb355d19..7f76dc4432c9 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -1254,70 +1254,6 @@ struct EmptyShape : public js::Shape { Handle obj); }; -// InitialShapeProto stores either: -// -// * A TaggedProto (or ReadBarriered). -// -// * A JSProtoKey. This is used instead of the TaggedProto if the proto is one -// of the global's builtin prototypes. For instance, if the proto is the -// initial Object.prototype, we use key_ = JSProto_Object, proto_ = nullptr. -// -// Using the JSProtoKey here is an optimization that lets us share more shapes -// across compartments within a zone. -template -class InitialShapeProto { - template - friend class InitialShapeProto; - - JSProtoKey key_; - PtrType proto_; - - public: - InitialShapeProto() : key_(JSProto_LIMIT), proto_() {} - - InitialShapeProto(JSProtoKey key, TaggedProto proto) - : key_(key), proto_(proto) {} - - template - explicit InitialShapeProto(const InitialShapeProto& other) - : key_(other.key()), proto_(other.proto_) {} - - explicit InitialShapeProto(TaggedProto proto) - : key_(JSProto_LIMIT), proto_(proto) {} - explicit InitialShapeProto(JSProtoKey key) : key_(key), proto_(nullptr) { - MOZ_ASSERT(key < JSProto_LIMIT); - } - - JSProtoKey key() const { return key_; } - const PtrType& proto() const { return proto_; } - void setProto(TaggedProto proto) { proto_ = proto; } - - bool operator==(const InitialShapeProto& other) const { - return key_ == other.key_ && proto_ == other.proto_; - } -}; - -template <> -struct MovableCellHasher>> { - using Key = InitialShapeProto>; - using Lookup = InitialShapeProto; - - static bool hasHash(const Lookup& l) { - return MovableCellHasher::hasHash(l.proto()); - } - static bool ensureHash(const Lookup& l) { - return MovableCellHasher::ensureHash(l.proto()); - } - static HashNumber hash(const Lookup& l) { - HashNumber hash = MovableCellHasher::hash(l.proto()); - return mozilla::AddToHash(hash, l.key()); - } - static bool match(const Key& k, const Lookup& l) { - return k.key() == l.key() && MovableCellHasher::match( - k.proto().unbarrieredGet(), l.proto()); - } -}; - /* * Entries for the per-zone initialShapes set indexing initial shapes for * objects in the zone and the associated types. @@ -1334,23 +1270,21 @@ struct InitialShapeEntry { * Matching prototype for the entry. The shape of an object determines its * prototype, but the prototype cannot be determined from the shape itself. */ - using ShapeProto = InitialShapeProto>; - ShapeProto proto; + ReadBarriered proto; /* State used to determine a match on an initial shape. */ struct Lookup { - using ShapeProto = InitialShapeProto; const Class* clasp; - ShapeProto proto; + TaggedProto proto; uint32_t nfixed; uint32_t baseFlags; - Lookup(const Class* clasp, ShapeProto proto, uint32_t nfixed, + Lookup(const Class* clasp, const TaggedProto& proto, uint32_t nfixed, uint32_t baseFlags) : clasp(clasp), proto(proto), nfixed(nfixed), baseFlags(baseFlags) {} explicit Lookup(const InitialShapeEntry& entry) - : proto(entry.proto.key(), entry.proto.proto().unbarrieredGet()) { + : proto(entry.proto.unbarrieredGet()) { const Shape* shape = entry.shape.unbarrieredGet(); clasp = shape->getObjectClass(); nfixed = shape->numFixedSlots(); @@ -1359,10 +1293,10 @@ struct InitialShapeEntry { }; inline InitialShapeEntry(); - inline InitialShapeEntry(Shape* shape, const Lookup::ShapeProto& proto); + inline InitialShapeEntry(Shape* shape, const TaggedProto& proto); static HashNumber hash(const Lookup& lookup) { - HashNumber hash = MovableCellHasher::hash(lookup.proto); + HashNumber hash = MovableCellHasher::hash(lookup.proto); return mozilla::AddToHash( hash, mozilla::HashGeneric(lookup.clasp, lookup.nfixed)); } @@ -1371,7 +1305,7 @@ struct InitialShapeEntry { return lookup.clasp == shape->getObjectClass() && lookup.nfixed == shape->numFixedSlots() && lookup.baseFlags == shape->getObjectFlags() && - MovableCellHasher::match(key.proto, lookup.proto); + key.proto.unbarrieredGet() == lookup.proto; } static void rekey(InitialShapeEntry& k, const InitialShapeEntry& newKey) { k = newKey; @@ -1379,7 +1313,7 @@ struct InitialShapeEntry { bool needsSweep() { Shape* ushape = shape.unbarrieredGet(); - TaggedProto uproto = proto.proto().unbarrieredGet(); + TaggedProto uproto = proto.unbarrieredGet(); JSObject* protoObj = uproto.raw(); return ( gc::IsAboutToBeFinalizedUnbarriered(&ushape) ||