From ca1120a47e5f7413c3e2dcb968106c8bd7c0c64d Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Wed, 29 Oct 2014 11:14:53 -0700 Subject: [PATCH] Bug 1083600 - Use inline data for small transparent typed objects, r=sfink,nmatsakis. --- js/public/MemoryMetrics.h | 1 + js/src/builtin/SIMD.cpp | 8 +- js/src/builtin/TypedObject.cpp | 210 ++++++++++++++---- js/src/builtin/TypedObject.h | 106 +++++++-- js/src/gc/Nursery.cpp | 7 +- js/src/gc/RootMarking.cpp | 3 + .../tests/TypedObject/inlinetransparent.js | 35 +++ js/src/jit/CodeGenerator.cpp | 28 +-- js/src/jit/CodeGenerator.h | 1 - js/src/jit/IonBuilder.cpp | 92 ++++---- js/src/jit/IonBuilder.h | 15 +- js/src/jit/LIR-Common.h | 18 -- js/src/jit/LOpcodes.h | 1 - js/src/jit/Lowering.cpp | 10 - js/src/jit/Lowering.h | 1 - js/src/jit/MCallOptimize.cpp | 20 +- js/src/jit/MIR.h | 36 --- js/src/jit/MOpcodes.h | 1 - js/src/jit/ParallelSafetyAnalysis.cpp | 1 - js/src/jscompartment.cpp | 5 + js/src/jscompartment.h | 8 +- js/src/jsinfer.h | 19 +- js/src/jsobjinlines.h | 9 +- js/src/vm/ArrayBufferObject.cpp | 94 ++++++-- js/src/vm/ArrayBufferObject.h | 48 +++- js/src/vm/MemoryMetrics.cpp | 1 + js/xpconnect/src/XPCJSRuntime.cpp | 4 + 27 files changed, 510 insertions(+), 272 deletions(-) create mode 100644 js/src/jit-test/tests/TypedObject/inlinetransparent.js diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 1b1dd3c2113d..0bdd414f26b4 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -539,6 +539,7 @@ struct CompartmentStats macro(Other, NotLiveGCThing, compartmentObject) \ macro(Other, NotLiveGCThing, compartmentTables) \ macro(Other, NotLiveGCThing, innerViewsTable) \ + macro(Other, NotLiveGCThing, lazyArrayBuffersTable) \ macro(Other, NotLiveGCThing, crossCompartmentWrappersTable) \ macro(Other, NotLiveGCThing, regexpCompartment) \ macro(Other, NotLiveGCThing, savedStacksSet) diff --git a/js/src/builtin/SIMD.cpp b/js/src/builtin/SIMD.cpp index 93aa4fd3515d..f63fb6917e58 100644 --- a/js/src/builtin/SIMD.cpp +++ b/js/src/builtin/SIMD.cpp @@ -87,7 +87,7 @@ template static Elem TypedObjectMemory(HandleValue v) { - OutlineTypedObject &obj = v.toObject().as(); + TypedObject &obj = v.toObject().as(); return reinterpret_cast(obj.typedMem()); } @@ -138,7 +138,7 @@ static bool SignMask(JSContext *cx, unsigned argc, Value *vp) return false; } - OutlineTypedObject &typedObj = args.thisv().toObject().as(); + TypedObject &typedObj = args.thisv().toObject().as(); TypeDescr &descr = typedObj.typeDescr(); if (descr.kind() != type::Simd || descr.as().type() != SimdType::type) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, @@ -322,7 +322,7 @@ SimdTypeDescr::call(JSContext *cx, unsigned argc, Value *vp) return false; } - Rooted result(cx, OutlineTypedObject::createZeroed(cx, descr, 0)); + Rooted result(cx, TypedObject::createZeroed(cx, descr, 0)); if (!result) return false; @@ -450,7 +450,7 @@ js::CreateSimd(JSContext *cx, typename V::Elem *data) Rooted typeDescr(cx, &V::GetTypeDescr(*cx->global())); MOZ_ASSERT(typeDescr); - Rooted result(cx, OutlineTypedObject::createZeroed(cx, typeDescr, 0)); + Rooted result(cx, TypedObject::createZeroed(cx, typeDescr, 0)); if (!result) return nullptr; diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index 2a6bb6c33b32..31998e8bda8c 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -1460,7 +1460,7 @@ js_InitTypedObjectDummy(JSContext *cx, HandleObject obj) int32_t TypedObject::offset() const { - if (is()) + if (is()) return 0; return typedMem() - typedMemBase(); } @@ -1478,8 +1478,8 @@ TypedObject::typedMem() const { MOZ_ASSERT(isAttached()); - if (is()) - return as().inlineTypedMem(); + if (is()) + return as().inlineTypedMem(); return as().outOfLineTypedMem(); } @@ -1492,12 +1492,22 @@ TypedObject::typedMemBase() const JSObject &owner = as().owner(); if (owner.is()) return owner.as().dataPointer(); - return owner.as().inlineTypedMem(); + return owner.as().inlineTypedMem(); } bool TypedObject::isAttached() const { + if (is()) { + LazyArrayBufferTable *table = compartment()->lazyArrayBuffers; + if (table) { + ArrayBufferObject *buffer = + table->maybeBuffer(&const_cast(this)->as()); + if (buffer) + return !buffer->isNeutered(); + } + return true; + } if (is()) return true; if (!as().outOfLineTypedMem()) @@ -1511,7 +1521,7 @@ TypedObject::isAttached() const bool TypedObject::maybeForwardedIsAttached() const { - if (is()) + if (is()) return true; if (!as().outOfLineTypedMem()) return false; @@ -1525,7 +1535,15 @@ TypedObject::maybeForwardedIsAttached() const TypedObject::GetBuffer(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); - args.rval().setObject(args[0].toObject().as().owner()); + JSObject &obj = args[0].toObject(); + ArrayBufferObject *buffer; + if (obj.is()) + buffer = obj.as().getOrCreateBuffer(cx); + else + buffer = obj.as().getOrCreateBuffer(cx); + if (!buffer) + MOZ_CRASH(); + args.rval().setObject(*buffer); return true; } @@ -1549,7 +1567,7 @@ OutlineTypedObject::createUnattached(JSContext *cx, if (descr->opaque()) return createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length); else - return createUnattachedWithClass(cx, &TransparentTypedObject::class_, descr, length); + return createUnattachedWithClass(cx, &OutlineTransparentTypedObject::class_, descr, length); } static JSObject * @@ -1590,7 +1608,7 @@ OutlineTypedObject::createUnattachedWithClass(JSContext *cx, HandleTypeDescr type, int32_t length) { - MOZ_ASSERT(clasp == &TransparentTypedObject::class_ || + MOZ_ASSERT(clasp == &OutlineTransparentTypedObject::class_ || clasp == &OutlineOpaqueTypedObject::class_); RootedObject proto(cx, PrototypeForTypeDescr(cx, type)); @@ -1618,6 +1636,9 @@ OutlineTypedObject::attach(JSContext *cx, ArrayBufferObject &buffer, int32_t off MOZ_ASSERT(offset >= 0); MOZ_ASSERT((size_t) (offset + size()) <= buffer.byteLength()); + if (typeDescr().is()) + buffer.setHasSizedObjectViews(); + if (!buffer.addView(cx, this)) CrashAtUnhandlableOOM("TypedObject::attach"); @@ -1639,8 +1660,8 @@ OutlineTypedObject::attach(JSContext *cx, TypedObject &typedObj, int32_t offset) if (owner->is()) { attach(cx, owner->as(), offset); } else { - MOZ_ASSERT(owner->is()); - setOwnerAndData(owner, owner->as().inlineTypedMem() + offset); + MOZ_ASSERT(owner->is()); + setOwnerAndData(owner, owner->as().inlineTypedMem() + offset); } } @@ -1674,9 +1695,9 @@ OutlineTypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type, int32_t length = TypedObjLengthFromType(*type); - const js::Class *clasp = typedObj->is() - ? &TransparentTypedObject::class_ - : &OutlineOpaqueTypedObject::class_; + const js::Class *clasp = typedObj->opaque() + ? &OutlineOpaqueTypedObject::class_ + : &OutlineTransparentTypedObject::class_; Rooted obj(cx); obj = createUnattachedWithClass(cx, clasp, type, length); if (!obj) @@ -1690,11 +1711,10 @@ OutlineTypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type, TypedObject::createZeroed(JSContext *cx, HandleTypeDescr descr, int32_t length) { // If possible, create an object with inline data. - if (descr->opaque() && - descr->is() && - (size_t) descr->as().size() <= InlineOpaqueTypedObject::MaximumSize) + if (descr->is() && + (size_t) descr->as().size() <= InlineTypedObject::MaximumSize) { - InlineOpaqueTypedObject *obj = InlineOpaqueTypedObject::create(cx, descr); + InlineTypedObject *obj = InlineTypedObject::create(cx, descr); descr->as().initInstances(cx->runtime(), obj->inlineTypedMem(), 1); return obj; } @@ -1791,7 +1811,7 @@ OutlineTypedObject::obj_trace(JSTracer *trc, JSObject *object) // Update the data pointer if the owner moved and the owner's data is // inline with it. if (owner != oldOwner && - (owner->is() || + (owner->is() || owner->as().hasInlineData())) { mem += reinterpret_cast(owner) - reinterpret_cast(oldOwner); @@ -2353,8 +2373,8 @@ OutlineTypedObject::neuter(void *newData) * Inline typed objects */ -/* static */ InlineOpaqueTypedObject * -InlineOpaqueTypedObject::create(JSContext *cx, HandleTypeDescr descr) +/* static */ InlineTypedObject * +InlineTypedObject::create(JSContext *cx, HandleTypeDescr descr) { gc::AllocKind allocKind = allocKindForTypeDescriptor(descr); @@ -2362,24 +2382,21 @@ InlineOpaqueTypedObject::create(JSContext *cx, HandleTypeDescr descr) if (!proto) return nullptr; - RootedObject obj(cx, NewObjectWithClassProto(cx, &class_, proto, nullptr, allocKind)); + const Class *clasp = descr->opaque() + ? &InlineOpaqueTypedObject::class_ + : &InlineTransparentTypedObject::class_; + + RootedObject obj(cx, NewObjectWithClassProto(cx, clasp, proto, nullptr, allocKind)); if (!obj) return nullptr; - return &obj->as(); -} - -/* static */ -size_t -InlineOpaqueTypedObject::offsetOfDataStart() -{ - return NativeObject::getFixedSlotOffset(0); + return &obj->as(); } /* static */ void -InlineOpaqueTypedObject::obj_trace(JSTracer *trc, JSObject *object) +InlineTypedObject::obj_trace(JSTracer *trc, JSObject *object) { - InlineOpaqueTypedObject &typedObj = object->as(); + InlineTypedObject &typedObj = object->as(); // When this is called for compacting GC, the related objects we touch here // may not have had their slots updated yet. @@ -2388,6 +2405,119 @@ InlineOpaqueTypedObject::obj_trace(JSTracer *trc, JSObject *object) descr.as().traceInstances(trc, typedObj.inlineTypedMem(), 1); } +ArrayBufferObject * +InlineTransparentTypedObject::getOrCreateBuffer(JSContext *cx) +{ + LazyArrayBufferTable *&table = cx->compartment()->lazyArrayBuffers; + if (!table) { + table = cx->new_(cx); + if (!table) + return nullptr; + } + + ArrayBufferObject *buffer = table->maybeBuffer(this); + if (buffer) + return buffer; + + ArrayBufferObject::BufferContents contents = + ArrayBufferObject::BufferContents::createPlain(inlineTypedMem()); + size_t nbytes = typeDescr().as().size(); + + // Prevent GC under ArrayBufferObject::create, which might move this object + // and its contents. + gc::AutoSuppressGC suppress(cx); + + buffer = ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData); + if (!buffer) + return nullptr; + + // The owning object must always be the array buffer's first view. This + // both prevents the memory from disappearing out from under the buffer + // (the first view is held strongly by the buffer) and is used by the + // buffer marking code to detect whether its data pointer needs to be + // relocated. + JS_ALWAYS_TRUE(buffer->addView(cx, this)); + + buffer->setForInlineTypedObject(); + buffer->setHasSizedObjectViews(); + + if (!table->addBuffer(cx, this, buffer)) + return nullptr; + + return buffer; +} + +ArrayBufferObject * +OutlineTransparentTypedObject::getOrCreateBuffer(JSContext *cx) +{ + if (owner().is()) + return &owner().as(); + return owner().as().getOrCreateBuffer(cx); +} + +LazyArrayBufferTable::LazyArrayBufferTable(JSContext *cx) + : map(cx) +{ + if (!map.init()) + CrashAtUnhandlableOOM("LazyArrayBufferTable"); +} + +LazyArrayBufferTable::~LazyArrayBufferTable() +{ + WeakMapBase::removeWeakMapFromList(&map); +} + +ArrayBufferObject * +LazyArrayBufferTable::maybeBuffer(InlineTransparentTypedObject *obj) +{ + if (Map::Ptr p = map.lookup(obj)) + return &p->value()->as(); + return nullptr; +} + +bool +LazyArrayBufferTable::addBuffer(JSContext *cx, InlineTransparentTypedObject *obj, ArrayBufferObject *buffer) +{ + MOZ_ASSERT(!map.has(obj)); + if (!map.put(obj, buffer)) { + js_ReportOutOfMemory(cx); + return false; + } + +#ifdef JSGC_GENERATIONAL + MOZ_ASSERT(!IsInsideNursery(buffer)); + if (IsInsideNursery(obj)) { + // Strip the barriers from the type before inserting into the store + // buffer, as is done for DebugScopes::proxiedScopes. + Map::Base *baseHashMap = static_cast(&map); + + typedef HashMap UnbarrieredMap; + UnbarrieredMap *unbarrieredMap = reinterpret_cast(baseHashMap); + + typedef gc::HashKeyRef Ref; + cx->runtime()->gc.storeBuffer.putGeneric(Ref(unbarrieredMap, obj)); + + // Also make sure the buffer is traced, so that its data pointer is + // updated after the typed object moves. + cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(buffer); + } +#endif + + return true; +} + +void +LazyArrayBufferTable::trace(JSTracer *trc) +{ + map.trace(trc); +} + +size_t +LazyArrayBufferTable::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) +{ + return mallocSizeOf(this) + map.sizeOfExcludingThis(mallocSizeOf); +} + /****************************************************************************** * Typed object classes */ @@ -2433,14 +2563,10 @@ InlineOpaqueTypedObject::obj_trace(JSTracer *trc, JSObject *object) } \ } -DEFINE_TYPEDOBJ_CLASS(TransparentTypedObject, - OutlineTypedObject::obj_trace); - -DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject, - OutlineTypedObject::obj_trace); - -DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject, - InlineOpaqueTypedObject::obj_trace); +DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace); +DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject, OutlineTypedObject::obj_trace); +DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject, InlineTypedObject::obj_trace); +DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject, InlineTypedObject::obj_trace); static int32_t LengthForType(TypeDescr &descr) @@ -2869,7 +2995,8 @@ js::ObjectIsOpaqueTypedObject(ThreadSafeContext *, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); - args.rval().setBoolean(!args[0].toObject().is()); + JSObject &obj = args[0].toObject(); + args.rval().setBoolean(obj.is() && obj.as().opaque()); return true; } @@ -2882,7 +3009,8 @@ js::ObjectIsTransparentTypedObject(ThreadSafeContext *, unsigned argc, Value *vp { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); - args.rval().setBoolean(args[0].toObject().is()); + JSObject &obj = args[0].toObject(); + args.rval().setBoolean(obj.is() && !obj.as().opaque()); return true; } diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index d55ea188de51..ec902b226120 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -8,6 +8,7 @@ #define builtin_TypedObject_h #include "jsobj.h" +#include "jsweakmap.h" #include "builtin/TypedObjectConstants.h" #include "vm/ArrayBufferObject.h" @@ -62,19 +63,18 @@ * Typed objects can be either transparent or opaque, depending on whether * their underlying buffer can be accessed. Transparent and opaque typed * objects have different classes, and can have different physical layouts. - * The following layouts are currently possible: + * The following layouts are possible: * - * InlineOpaqueTypedObject: Typed objects whose data immediately follows the - * object's header are inline typed objects. These do not have an associated - * array buffer, so only opaque typed objects can be inline. + * InlineTypedObject: Typed objects whose data immediately follows the object's + * header are inline typed objects. The buffer for these objects is created + * lazily and stored via the compartment's LazyArrayBufferTable, and points + * back into the object's internal data. * - * OutlineTypedObject: Transparent or opaque typed objects whose data is owned by - * another object, which can be either an array buffer or an inline typed - * object (opaque objects only). Outline typed objects may be attached or - * unattached. An unattached typed object has no memory associated with it. - * When first created, objects are always attached, but they can become - * unattached if their buffer is neutered (note that this implies that typed - * objects of opaque types can't be unattached). + * OutlineTypedObject: Typed objects whose data is owned by another object, + * which can be either an array buffer or an inline typed object. Outline + * typed objects may be attached or unattached. An unattached typed object + * has no data associated with it. When first created, objects are always + * attached, but they can become unattached if their buffer is neutered. * * Note that whether a typed object is opaque is not directly * connected to its type. That is, opaque types are *always* @@ -671,6 +671,8 @@ class TypedObject : public JSObject return typedMem() + offset; } + inline bool opaque() const; + // Creates a new typed object whose memory is freshly allocated and // initialized with zeroes (or, in the case of references, an appropriate // default value). @@ -784,14 +786,16 @@ class OutlineTypedObject : public TypedObject static void obj_trace(JSTracer *trace, JSObject *object); }; -// Class for a transparent typed object, whose owner is an array buffer. -class TransparentTypedObject : public OutlineTypedObject +// Class for a transparent typed object whose owner is an array buffer. +class OutlineTransparentTypedObject : public OutlineTypedObject { public: static const Class class_; + + ArrayBufferObject *getOrCreateBuffer(JSContext *cx); }; -// Class for an opaque typed object, whose owner may be either an array buffer +// Class for an opaque typed object whose owner may be either an array buffer // or an opaque inlined typed object. class OutlineOpaqueTypedObject : public OutlineTypedObject { @@ -799,15 +803,13 @@ class OutlineOpaqueTypedObject : public OutlineTypedObject static const Class class_; }; -// Class for an opaque typed object whose data is allocated inline. -class InlineOpaqueTypedObject : public TypedObject +// Class for a typed object whose data is allocated inline. +class InlineTypedObject : public TypedObject { // Start of the inline data, which immediately follows the shape and type. uint8_t data_[1]; public: - static const Class class_; - static const size_t MaximumSize = NativeObject::MAX_FIXED_SLOTS * sizeof(Value); static gc::AllocKind allocKindForTypeDescriptor(TypeDescr *descr) { @@ -820,16 +822,35 @@ class InlineOpaqueTypedObject : public TypedObject } uint8_t *inlineTypedMem() const { - static_assert(offsetof(InlineOpaqueTypedObject, data_) == sizeof(JSObject), + static_assert(offsetof(InlineTypedObject, data_) == sizeof(JSObject), "The data for an inline typed object must follow the shape and type."); return (uint8_t *) &data_; } static void obj_trace(JSTracer *trace, JSObject *object); - static size_t offsetOfDataStart(); + static size_t offsetOfDataStart() { + return offsetof(InlineTypedObject, data_); + } - static InlineOpaqueTypedObject *create(JSContext *cx, HandleTypeDescr descr); + static InlineTypedObject *create(JSContext *cx, HandleTypeDescr descr); +}; + +// Class for a transparent typed object with inline data, which may have a +// lazily allocated array buffer. +class InlineTransparentTypedObject : public InlineTypedObject +{ + public: + static const Class class_; + + ArrayBufferObject *getOrCreateBuffer(JSContext *cx); +}; + +// Class for an opaque typed object with inline data and no array buffer. +class InlineOpaqueTypedObject : public InlineTypedObject +{ + public: + static const Class class_; }; /* @@ -1044,7 +1065,8 @@ JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_LOAD_REFERENCE_CLASS_DEFN) inline bool IsTypedObjectClass(const Class *class_) { - return class_ == &TransparentTypedObject::class_ || + return class_ == &OutlineTransparentTypedObject::class_ || + class_ == &InlineTransparentTypedObject::class_ || class_ == &OutlineOpaqueTypedObject::class_ || class_ == &InlineOpaqueTypedObject::class_; } @@ -1078,6 +1100,36 @@ IsTypeDescrClass(const Class* clasp) clasp == &UnsizedArrayTypeDescr::class_; } +inline bool +TypedObject::opaque() const +{ + return is() || is(); +} + +// Inline transparent typed objects do not initially have an array buffer, but +// can have that buffer created lazily if it is accessed later. This table +// manages references from such typed objects to their buffers. +class LazyArrayBufferTable +{ + private: + // The map from transparent typed objects to their lazily created buffer. + // Keys in this map are InlineTransparentTypedObjects and values are + // ArrayBufferObjects, but we don't enforce this in the type system due to + // the extra marking code goop that requires. + typedef WeakMap Map; + Map map; + + public: + LazyArrayBufferTable(JSContext *cx); + ~LazyArrayBufferTable(); + + ArrayBufferObject *maybeBuffer(InlineTransparentTypedObject *obj); + bool addBuffer(JSContext *cx, InlineTransparentTypedObject *obj, ArrayBufferObject *buffer); + + void trace(JSTracer *trc); + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); +}; + } // namespace js JSObject * @@ -1122,10 +1174,18 @@ template <> inline bool JSObject::is() const { - return getClass() == &js::TransparentTypedObject::class_ || + return getClass() == &js::OutlineTransparentTypedObject::class_ || getClass() == &js::OutlineOpaqueTypedObject::class_; } +template <> +inline bool +JSObject::is() const +{ + return getClass() == &js::InlineTransparentTypedObject::class_ || + getClass() == &js::InlineOpaqueTypedObject::class_; +} + inline void js::TypedProto::initTypeDescrSlot(TypeDescr &descr) { diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index c1e157afc9ab..e3a9a50b296f 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -395,12 +395,13 @@ GetObjectAllocKindForCopy(const Nursery &nursery, JSObject *obj) // Inlined opaque typed objects are followed by their data, so make sure we // copy it all over to the new object. - if (obj->is()) { + if (obj->is()) { // Figure out the size of this object, from the prototype's TypeDescr. // The objects we are traversing here are all tenured, so we don't need // to check forwarding pointers. - TypeDescr *descr = &obj->as().typeDescr(); - return InlineOpaqueTypedObject::allocKindForTypeDescriptor(descr); + TypeDescr *descr = &obj->as().typeDescr(); + MOZ_ASSERT(!IsInsideNursery(descr)); + return InlineTypedObject::allocKindForTypeDescriptor(descr); } // Outline typed objects have special requirements for their allocation kind. diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index 0abe761190d1..bc7611bfb09d 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -536,6 +536,9 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, /* Mark debug scopes, if present */ if (c->debugScopes) c->debugScopes->mark(trc); + + if (c->lazyArrayBuffers) + c->lazyArrayBuffers->trace(trc); } MarkInterpreterActivations(&rt->mainThread, trc); diff --git a/js/src/jit-test/tests/TypedObject/inlinetransparent.js b/js/src/jit-test/tests/TypedObject/inlinetransparent.js new file mode 100644 index 000000000000..83528fa0baa5 --- /dev/null +++ b/js/src/jit-test/tests/TypedObject/inlinetransparent.js @@ -0,0 +1,35 @@ +if (!this.hasOwnProperty("TypedObject")) + quit(); + +var TO = TypedObject; + +var PointType = new TO.StructType({x: TO.int32, y: TO.int32}); +var LineType = new TO.StructType({from: PointType, to: PointType}); + +function testBasic(how) { + var line = new LineType(); + var from = line.from; + var to = line.to; + TO.storage(to).buffer.expando = "hello"; + var dataview = new DataView(TO.storage(from).buffer); + line.from.x = 12; + line.to.x = 3; + if (how == 1) + minorgc(); + else if (how == 2) + gc(); + assertEq(from.x, 12); + assertEq(from.y, 0); + assertEq(to.x, 3); + assertEq(to.y, 0); + assertEq(TO.storage(to).byteOffset, 8); + dataview.setInt32(8, 10, true); + assertEq(to.x, 10); + assertEq(TO.storage(line).buffer.expando, "hello"); +} +for (var i = 0; i < 5; i++) + testBasic(0); +for (var i = 0; i < 5; i++) + testBasic(1); +for (var i = 0; i < 5; i++) + testBasic(2); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index e8ba0034ea40..266a30fd35ed 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -5007,28 +5007,6 @@ CodeGenerator::visitTypedArrayElements(LTypedArrayElements *lir) return true; } -bool -CodeGenerator::visitNeuterCheck(LNeuterCheck *lir) -{ - Register obj = ToRegister(lir->object()); - Register temp = ToRegister(lir->temp()); - - Label inlineObject; - masm.loadObjClass(obj, temp); - masm.branchPtr(Assembler::Equal, temp, ImmPtr(&InlineOpaqueTypedObject::class_), &inlineObject); - - masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfOwner()), temp); - masm.unboxInt32(Address(temp, ArrayBufferObject::offsetOfFlagsSlot()), temp); - - Imm32 flag(ArrayBufferObject::neuteredFlag()); - if (!bailoutTest32(Assembler::NonZero, temp, flag, lir->snapshot())) - return false; - - masm.bind(&inlineObject); - - return true; -} - bool CodeGenerator::visitTypedObjectProto(LTypedObjectProto *lir) { @@ -5065,12 +5043,13 @@ CodeGenerator::visitTypedObjectElements(LTypedObjectElements *lir) Label inlineObject, done; masm.loadObjClass(obj, out); masm.branchPtr(Assembler::Equal, out, ImmPtr(&InlineOpaqueTypedObject::class_), &inlineObject); + masm.branchPtr(Assembler::Equal, out, ImmPtr(&InlineTransparentTypedObject::class_), &inlineObject); masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfData()), out); masm.jump(&done); masm.bind(&inlineObject); - masm.computeEffectiveAddress(Address(obj, InlineOpaqueTypedObject::offsetOfDataStart()), out); + masm.computeEffectiveAddress(Address(obj, InlineTypedObject::offsetOfDataStart()), out); masm.bind(&done); return true; @@ -5090,12 +5069,13 @@ CodeGenerator::visitSetTypedObjectOffset(LSetTypedObjectOffset *lir) Label inlineObject, done; masm.loadObjClass(temp0, temp1); masm.branchPtr(Assembler::Equal, temp1, ImmPtr(&InlineOpaqueTypedObject::class_), &inlineObject); + masm.branchPtr(Assembler::Equal, temp1, ImmPtr(&InlineTransparentTypedObject::class_), &inlineObject); masm.loadPrivate(Address(temp0, ArrayBufferObject::offsetOfDataSlot()), temp0); masm.jump(&done); masm.bind(&inlineObject); - masm.addPtr(ImmWord(InlineOpaqueTypedObject::offsetOfDataStart()), temp0); + masm.addPtr(ImmWord(InlineTypedObject::offsetOfDataStart()), temp0); masm.bind(&done); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 25cde18244a8..3420d7ef5f24 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -181,7 +181,6 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitSetArrayLength(LSetArrayLength *lir); bool visitTypedArrayLength(LTypedArrayLength *lir); bool visitTypedArrayElements(LTypedArrayElements *lir); - bool visitNeuterCheck(LNeuterCheck *lir); bool visitTypedObjectElements(LTypedObjectElements *lir); bool visitSetTypedObjectOffset(LSetTypedObjectOffset *lir); bool visitTypedObjectProto(LTypedObjectProto *ins); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index d952d338a698..ac19c66f6160 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -7247,8 +7247,7 @@ IonBuilder::checkTypedObjectIndexInBounds(int32_t elemSize, MDefinition *obj, MDefinition *index, TypedObjectPrediction objPrediction, - MDefinition **indexAsByteOffset, - bool *canBeNeutered) + MDefinition **indexAsByteOffset) { // Ensure index is an integer. MInstruction *idInt32 = MToInt32::New(alloc(), index); @@ -7263,17 +7262,17 @@ IonBuilder::checkTypedObjectIndexInBounds(int32_t elemSize, if (objPrediction.hasKnownArrayLength(&lenOfAll)) { length = constantInt(lenOfAll); - // If we are not loading the length from the object itself, - // then we still need to check if the object was neutered. - *canBeNeutered = true; + // If we are not loading the length from the object itself, only + // optimize if the array buffer can't have been neutered. + types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global()); + if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED)) + return false; } else if (objPrediction.kind() == type::UnsizedArray) { + // Note: unsized arrays will have their length set to zero if they are + // neutered, so we don't need to make sure that no neutering has + // occurred which affects this object. length = MTypedObjectUnsizedLength::New(alloc(), obj); current->add(length->toInstruction()); - - // If we are loading the length from the object itself, - // then we do not need an extra neuter check, because the length - // will have been set to 0 when the object was neutered. - *canBeNeutered = false; } else { return false; } @@ -7304,31 +7303,25 @@ IonBuilder::getElemTryScalarElemOfTypedObject(bool *emitted, ScalarTypeDescr::Type elemType = elemPrediction.scalarType(); MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType)); - bool canBeNeutered; MDefinition *indexAsByteOffset; - if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, - &indexAsByteOffset, &canBeNeutered)) - { + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) return true; - } - return pushScalarLoadFromTypedObject(emitted, obj, indexAsByteOffset, elemType, canBeNeutered); + return pushScalarLoadFromTypedObject(emitted, obj, indexAsByteOffset, elemType); } bool IonBuilder::pushScalarLoadFromTypedObject(bool *emitted, MDefinition *obj, MDefinition *offset, - ScalarTypeDescr::Type elemType, - bool canBeNeutered) + ScalarTypeDescr::Type elemType) { int32_t size = ScalarTypeDescr::size(elemType); MOZ_ASSERT(size == ScalarTypeDescr::alignment(elemType)); // Find location within the owner object. MDefinition *elements, *scaledOffset; - loadTypedObjectElements(obj, offset, size, canBeNeutered, - &elements, &scaledOffset); + loadTypedObjectElements(obj, offset, size, &elements, &scaledOffset); // Load the element. MLoadTypedArrayElement *load = MLoadTypedArrayElement::New(alloc(), elements, scaledOffset, elemType); @@ -7369,16 +7362,12 @@ IonBuilder::getElemTryComplexElemOfTypedObject(bool *emitted, MDefinition *type = loadTypedObjectType(obj); MDefinition *elemTypeObj = typeObjectForElementFromArrayStructType(type); - bool canBeNeutered; MDefinition *indexAsByteOffset; - if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, - &indexAsByteOffset, &canBeNeutered)) - { + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) return true; - } return pushDerivedTypedObject(emitted, obj, indexAsByteOffset, - elemPrediction, elemTypeObj, canBeNeutered); + elemPrediction, elemTypeObj); } bool @@ -7386,12 +7375,11 @@ IonBuilder::pushDerivedTypedObject(bool *emitted, MDefinition *obj, MDefinition *offset, TypedObjectPrediction derivedPrediction, - MDefinition *derivedTypeObj, - bool canBeNeutered) + MDefinition *derivedTypeObj) { // Find location within the owner object. MDefinition *owner, *ownerOffset; - loadTypedObjectData(obj, offset, canBeNeutered, &owner, &ownerOffset); + loadTypedObjectData(obj, offset, &owner, &ownerOffset); // Create the derived typed object. MInstruction *derivedTypedObj = MNewDerivedTypedObject::New(alloc(), @@ -8159,16 +8147,12 @@ IonBuilder::setElemTryScalarElemOfTypedObject(bool *emitted, ScalarTypeDescr::Type elemType = elemPrediction.scalarType(); MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType)); - bool canBeNeutered; MDefinition *indexAsByteOffset; - if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, - &indexAsByteOffset, &canBeNeutered)) - { + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) return true; - } // Store the element - if (!storeScalarTypedObjectValue(obj, indexAsByteOffset, elemType, canBeNeutered, false, value)) + if (!storeScalarTypedObjectValue(obj, indexAsByteOffset, elemType, false, value)) return false; current->push(value); @@ -8543,7 +8527,7 @@ IonBuilder::jsop_setelem_typed_object(Scalar::Type arrayType, SetElemSafety safe MIRType_Int32, MMul::Integer); current->add(byteOffset); - if (!storeScalarTypedObjectValue(object, byteOffset, arrayType, false, racy, value)) + if (!storeScalarTypedObjectValue(object, byteOffset, arrayType, racy, value)) return false; return true; @@ -9325,9 +9309,13 @@ IonBuilder::getPropTryScalarPropOfTypedObject(bool *emitted, MDefinition *typedO // Must always be loading the same scalar type Scalar::Type fieldType = fieldPrediction.scalarType(); + // Don't optimize if the typed object might be neutered. + types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global()); + if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED)) + return true; + // OK, perform the optimization. - return pushScalarLoadFromTypedObject(emitted, typedObj, constantInt(fieldOffset), - fieldType, true); + return pushScalarLoadFromTypedObject(emitted, typedObj, constantInt(fieldOffset), fieldType); } bool @@ -9338,6 +9326,11 @@ IonBuilder::getPropTryComplexPropOfTypedObject(bool *emitted, size_t fieldIndex, types::TemporaryTypeSet *resultTypes) { + // Don't optimize if the typed object might be neutered. + types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global()); + if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED)) + return true; + // OK, perform the optimization // Identify the type object for the field. @@ -9345,7 +9338,7 @@ IonBuilder::getPropTryComplexPropOfTypedObject(bool *emitted, MDefinition *fieldTypeObj = typeObjectForFieldFromStructType(type, fieldIndex); return pushDerivedTypedObject(emitted, typedObj, constantInt(fieldOffset), - fieldPrediction, fieldTypeObj, true); + fieldPrediction, fieldTypeObj); } bool @@ -9992,9 +9985,14 @@ IonBuilder::setPropTryScalarPropOfTypedObject(bool *emitted, // Must always be loading the same scalar type Scalar::Type fieldType = fieldPrediction.scalarType(); + // Don't optimize if the typed object might be neutered. + types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global()); + if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED)) + return true; + // OK! Perform the optimization. - if (!storeScalarTypedObjectValue(obj, constantInt(fieldOffset), fieldType, true, false, value)) + if (!storeScalarTypedObjectValue(obj, constantInt(fieldOffset), fieldType, false, value)) return false; current->push(value); @@ -10958,7 +10956,6 @@ IonBuilder::loadTypedObjectType(MDefinition *typedObj) void IonBuilder::loadTypedObjectData(MDefinition *typedObj, MDefinition *offset, - bool canBeNeutered, MDefinition **owner, MDefinition **ownerOffset) { @@ -10985,12 +10982,6 @@ IonBuilder::loadTypedObjectData(MDefinition *typedObj, return; } - if (canBeNeutered) { - MNeuterCheck *chk = MNeuterCheck::New(alloc(), typedObj); - current->add(chk); - typedObj = chk; - } - *owner = typedObj; *ownerOffset = offset; } @@ -11004,12 +10995,11 @@ void IonBuilder::loadTypedObjectElements(MDefinition *typedObj, MDefinition *offset, int32_t unit, - bool canBeNeutered, MDefinition **ownerElements, MDefinition **ownerScaledOffset) { MDefinition *owner, *ownerOffset; - loadTypedObjectData(typedObj, offset, canBeNeutered, &owner, &ownerOffset); + loadTypedObjectData(typedObj, offset, &owner, &ownerOffset); // Load the element data. MTypedObjectElements *elements = MTypedObjectElements::New(alloc(), owner); @@ -11096,15 +11086,13 @@ bool IonBuilder::storeScalarTypedObjectValue(MDefinition *typedObj, MDefinition *byteOffset, ScalarTypeDescr::Type type, - bool canBeNeutered, bool racy, MDefinition *value) { // Find location within the owner object. MDefinition *elements, *scaledOffset; size_t alignment = ScalarTypeDescr::alignment(type); - loadTypedObjectElements(typedObj, byteOffset, alignment, canBeNeutered, - &elements, &scaledOffset); + loadTypedObjectElements(typedObj, byteOffset, alignment, &elements, &scaledOffset); // Clamp value to [0, 255] when type is Uint8Clamped MDefinition *toWrite = value; diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index faf235b3a85d..685ca0948c67 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -471,13 +471,11 @@ class IonBuilder MDefinition *loadTypedObjectType(MDefinition *value); void loadTypedObjectData(MDefinition *typedObj, MDefinition *offset, - bool canBeNeutered, MDefinition **owner, MDefinition **ownerOffset); void loadTypedObjectElements(MDefinition *typedObj, MDefinition *offset, int32_t unit, - bool canBeNeutered, MDefinition **ownerElements, MDefinition **ownerScaledOffset); MDefinition *typeObjectForElementFromArrayStructType(MDefinition *typedObj); @@ -486,26 +484,22 @@ class IonBuilder bool storeScalarTypedObjectValue(MDefinition *typedObj, MDefinition *offset, ScalarTypeDescr::Type type, - bool canBeNeutered, bool racy, MDefinition *value); bool checkTypedObjectIndexInBounds(int32_t elemSize, MDefinition *obj, MDefinition *index, TypedObjectPrediction objTypeDescrs, - MDefinition **indexAsByteOffset, - bool *canBeNeutered); + MDefinition **indexAsByteOffset); bool pushDerivedTypedObject(bool *emitted, MDefinition *obj, MDefinition *offset, TypedObjectPrediction derivedTypeDescrs, - MDefinition *derivedTypeObj, - bool canBeNeutered); + MDefinition *derivedTypeObj); bool pushScalarLoadFromTypedObject(bool *emitted, MDefinition *obj, MDefinition *offset, - ScalarTypeDescr::Type type, - bool canBeNeutered); + ScalarTypeDescr::Type type); MDefinition *neuterCheck(MDefinition *obj); // jsop_setelem() helpers. @@ -768,7 +762,8 @@ class IonBuilder InliningStatus inlineDump(CallInfo &callInfo); InliningStatus inlineHasClass(CallInfo &callInfo, const Class *clasp, const Class *clasp2 = nullptr, - const Class *clasp3 = nullptr); + const Class *clasp3 = nullptr, + const Class *clasp4 = nullptr); InliningStatus inlineIsConstructing(CallInfo &callInfo); // Testing functions. diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 1b5d7842f4d7..0bd94620e5dd 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -4258,24 +4258,6 @@ class LSetTypedObjectOffset : public LInstructionHelper<0, 2, 2> } }; -// Check whether a typed object has a neutered owner buffer. -class LNeuterCheck : public LInstructionHelper<0, 1, 1> -{ - public: - LIR_HEADER(NeuterCheck) - - LNeuterCheck(const LAllocation &object, const LDefinition &temp) { - setOperand(0, object); - setTemp(0, temp); - } - const LAllocation *object() { - return getOperand(0); - } - const LDefinition *temp() { - return getTemp(0); - } -}; - // Bailout if index >= length. class LBoundsCheck : public LInstructionHelper<0, 2, 0> { diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index c32f6d6274e2..b2418df044ed 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -211,7 +211,6 @@ _(PostWriteBarrierV) \ _(InitializedLength) \ _(SetInitializedLength) \ - _(NeuterCheck) \ _(BoundsCheck) \ _(BoundsCheckRange) \ _(BoundsCheckLower) \ diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 8d1189c16336..a33bafbd89b7 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2590,16 +2590,6 @@ LIRGenerator::visitNot(MNot *ins) } } -bool -LIRGenerator::visitNeuterCheck(MNeuterCheck *ins) -{ - LNeuterCheck *chk = new(alloc()) LNeuterCheck(useRegister(ins->object()), - temp()); - if (!assignSnapshot(chk, Bailout_Neutered)) - return false; - return redefine(ins, ins->input()) && add(chk, ins); -} - bool LIRGenerator::visitBoundsCheck(MBoundsCheck *ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index ccf3bba87c42..5a50d61e4a03 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -186,7 +186,6 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitSetArrayLength(MSetArrayLength *ins); bool visitTypedArrayLength(MTypedArrayLength *ins); bool visitTypedArrayElements(MTypedArrayElements *ins); - bool visitNeuterCheck(MNeuterCheck *lir); bool visitTypedObjectElements(MTypedObjectElements *ins); bool visitSetTypedObjectOffset(MSetTypedObjectOffset *ins); bool visitTypedObjectProto(MTypedObjectProto *ins); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 73a305c85df8..e3b9ff54c594 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -201,11 +201,14 @@ IonBuilder::inlineNativeCall(CallInfo &callInfo, JSFunction *target) // TypedObject intrinsics. if (native == intrinsic_ObjectIsTypedObject) return inlineHasClass(callInfo, - &TransparentTypedObject::class_, + &OutlineTransparentTypedObject::class_, &OutlineOpaqueTypedObject::class_, + &InlineTransparentTypedObject::class_, &InlineOpaqueTypedObject::class_); if (native == intrinsic_ObjectIsTransparentTypedObject) - return inlineHasClass(callInfo, &TransparentTypedObject::class_); + return inlineHasClass(callInfo, + &OutlineTransparentTypedObject::class_, + &InlineTransparentTypedObject::class_); if (native == intrinsic_ObjectIsOpaqueTypedObject) return inlineHasClass(callInfo, &OutlineOpaqueTypedObject::class_, @@ -1827,7 +1830,9 @@ IonBuilder::inlineNewDenseArrayForParallelExecution(CallInfo &callInfo) } IonBuilder::InliningStatus -IonBuilder::inlineHasClass(CallInfo &callInfo, const Class *clasp1, const Class *clasp2, const Class *clasp3) +IonBuilder::inlineHasClass(CallInfo &callInfo, + const Class *clasp1, const Class *clasp2, + const Class *clasp3, const Class *clasp4) { if (callInfo.constructing() || callInfo.argc() != 1) return InliningStatus_NotInlined; @@ -1840,15 +1845,18 @@ IonBuilder::inlineHasClass(CallInfo &callInfo, const Class *clasp1, const Class types::TemporaryTypeSet *types = callInfo.getArg(0)->resultTypeSet(); const Class *knownClass = types ? types->getKnownClass() : nullptr; if (knownClass) { - pushConstant(BooleanValue(knownClass == clasp1 || knownClass == clasp2 || knownClass == clasp3)); + pushConstant(BooleanValue(knownClass == clasp1 || + knownClass == clasp2 || + knownClass == clasp3 || + knownClass == clasp4)); } else { MHasClass *hasClass1 = MHasClass::New(alloc(), callInfo.getArg(0), clasp1); current->add(hasClass1); - if (!clasp2 && !clasp3) { + if (!clasp2 && !clasp3 && !clasp4) { current->push(hasClass1); } else { - const Class *remaining[] = { clasp2, clasp3 }; + const Class *remaining[] = { clasp2, clasp3, clasp4 }; MDefinition *last = hasClass1; for (size_t i = 0; i < ArrayLength(remaining); i++) { MHasClass *hasClass = MHasClass::New(alloc(), callInfo.getArg(0), remaining[i]); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 3d345b62c8d6..a6a8cab0b9ed 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -7351,42 +7351,6 @@ class MTypedArrayElements ALLOW_CLONE(MTypedArrayElements) }; -// Checks whether a typed object is neutered. -class MNeuterCheck - : public MUnaryInstruction, - public SingleObjectPolicy::Data -{ - private: - explicit MNeuterCheck(MDefinition *object) - : MUnaryInstruction(object) - { - MOZ_ASSERT(object->type() == MIRType_Object); - setResultType(MIRType_Object); - setResultTypeSet(object->resultTypeSet()); - setGuard(); - setMovable(); - } - - public: - INSTRUCTION_HEADER(NeuterCheck) - - static MNeuterCheck *New(TempAllocator &alloc, MDefinition *object) { - return new(alloc) MNeuterCheck(object); - } - - MDefinition *object() const { - return getOperand(0); - } - - bool congruentTo(const MDefinition *ins) const { - return congruentIfOperandsEqual(ins); - } - - AliasSet getAliasSet() const { - return AliasSet::Load(AliasSet::ObjectFields); - } -}; - // Load a binary data object's "elements", which is just its opaque // binary data space. Eventually this should probably be // unified with `MTypedArrayElements`. diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 3e2866b49457..c22516daa2a1 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -166,7 +166,6 @@ namespace jit { _(InitializedLength) \ _(SetInitializedLength) \ _(Not) \ - _(NeuterCheck) \ _(BoundsCheck) \ _(BoundsCheckLower) \ _(InArray) \ diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index bcd353a9328f..7ea0bc3a5544 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -252,7 +252,6 @@ class ParallelSafetyVisitor : public MDefinitionVisitor SAFE_OP(InitializedLength) WRITE_GUARDED_OP(SetInitializedLength, elements) SAFE_OP(Not) - SAFE_OP(NeuterCheck) SAFE_OP(BoundsCheck) SAFE_OP(BoundsCheckLower) SAFE_OP(LoadElement) diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index b071929d36d3..826e42167da2 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -57,6 +57,7 @@ JSCompartment::JSCompartment(Zone *zone, const JS::CompartmentOptions &options = globalWriteBarriered(false), propertyTree(thisForCtor()), selfHostingScriptSource(nullptr), + lazyArrayBuffers(nullptr), gcIncomingGrayPointers(nullptr), gcWeakMapList(nullptr), gcPreserveJitCode(options.preserveJitCode()), @@ -83,6 +84,7 @@ JSCompartment::~JSCompartment() js_delete(scriptCountsMap); js_delete(debugScriptMap); js_delete(debugScopes); + js_delete(lazyArrayBuffers); js_free(enumerators); runtime_->numCompartments--; @@ -869,6 +871,7 @@ JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t *compartmentObject, size_t *compartmentTables, size_t *innerViewsArg, + size_t *lazyArrayBuffersArg, size_t *crossCompartmentWrappersArg, size_t *regexpCompartment, size_t *savedStacksSet) @@ -881,6 +884,8 @@ JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, + newTypeObjects.sizeOfExcludingThis(mallocSizeOf) + lazyTypeObjects.sizeOfExcludingThis(mallocSizeOf); *innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf); + if (lazyArrayBuffers) + *lazyArrayBuffersArg += lazyArrayBuffers->sizeOfIncludingThis(mallocSizeOf); *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf); *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf); *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf); diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index bfc90c818949..5dacdf31abbe 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -132,6 +132,7 @@ struct TypeInferenceSizes; namespace js { class AutoDebugModeInvalidation; class DebugScopes; +class LazyArrayBufferTable; class WeakMapBase; } @@ -249,6 +250,7 @@ struct JSCompartment size_t *compartmentObject, size_t *compartmentTables, size_t *innerViews, + size_t *lazyArrayBuffers, size_t *crossCompartmentWrappers, size_t *regexpCompartment, size_t *savedStacksSet); @@ -292,10 +294,12 @@ struct JSCompartment */ js::ReadBarrieredScriptSourceObject selfHostingScriptSource; - // Information mapping objects which own their own storage to other objects - // sharing that storage. + // Map from array buffers to views sharing that storage. js::InnerViewTable innerViews; + // Map from typed objects to array buffers lazily created for them. + js::LazyArrayBufferTable *lazyArrayBuffers; + /* During GC, stores the index of this compartment in rt->compartments. */ unsigned gcIndex; diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index e059942cf415..d3cba0a6ad32 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -482,23 +482,30 @@ enum MOZ_ENUM_TYPE(uint32_t) { */ OBJECT_FLAG_RUNONCE_INVALIDATED = 0x00200000, + /* + * For a global object, whether any array buffers in this compartment with + * sized typed object views have been neutered. Sized typed objects have + * different neutering checks from other array buffer views. + */ + OBJECT_FLAG_SIZED_OBJECT_NEUTERED = 0x00400000, + /* * Whether objects with this type should be allocated directly in the * tenured heap. */ - OBJECT_FLAG_PRE_TENURE = 0x00400000, + OBJECT_FLAG_PRE_TENURE = 0x00800000, /* Whether objects with this type might have copy on write elements. */ - OBJECT_FLAG_COPY_ON_WRITE = 0x00800000, + OBJECT_FLAG_COPY_ON_WRITE = 0x01000000, /* * Whether all properties of this object are considered unknown. * If set, all other flags in DYNAMIC_MASK will also be set. */ - OBJECT_FLAG_UNKNOWN_PROPERTIES = 0x01000000, + OBJECT_FLAG_UNKNOWN_PROPERTIES = 0x02000000, /* Flags which indicate dynamic properties of represented objects. */ - OBJECT_FLAG_DYNAMIC_MASK = 0x01ff0000, + OBJECT_FLAG_DYNAMIC_MASK = 0x03ff0000, /* Mask for objects created with unknown properties. */ OBJECT_FLAG_UNKNOWN_MASK = @@ -507,8 +514,8 @@ enum MOZ_ENUM_TYPE(uint32_t) { // Mask/shift for this type object's generation. If out of sync with the // TypeZone's generation, this TypeObject hasn't been swept yet. - OBJECT_FLAG_GENERATION_MASK = 0x02000000, - OBJECT_FLAG_GENERATION_SHIFT = 25, + OBJECT_FLAG_GENERATION_MASK = 0x04000000, + OBJECT_FLAG_GENERATION_SHIFT = 26, }; typedef uint32_t TypeObjectFlags; diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 51b7a4a1bd0b..6493cd384879 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -283,8 +283,10 @@ JSObject::isUnqualifiedVarObj() return lastProperty()->hasObjectFlag(js::BaseShape::UNQUALIFIED_VAROBJ); } +namespace js { + inline bool -ClassCanHaveFixedData(const js::Class *clasp) +ClassCanHaveFixedData(const Class *clasp) { // Normally, the number of fixed slots given an object is the maximum // permitted for its size class. For array buffers and non-shared typed @@ -293,10 +295,11 @@ ClassCanHaveFixedData(const js::Class *clasp) // buffer's data. return !clasp->isNative() || clasp == &js::ArrayBufferObject::class_ - || clasp == &js::InlineOpaqueTypedObject::class_ || js::IsTypedArrayClass(clasp); } +} // namespace js + /* static */ inline JSObject * JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap, js::HandleShape shape, js::HandleTypeObject type) @@ -304,7 +307,7 @@ JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::Initi MOZ_ASSERT(shape && type); MOZ_ASSERT(type->clasp() == shape->getObjectClass()); MOZ_ASSERT(type->clasp() != &js::ArrayObject::class_); - MOZ_ASSERT_IF(!ClassCanHaveFixedData(type->clasp()), + MOZ_ASSERT_IF(!js::ClassCanHaveFixedData(type->clasp()), js::gc::GetGCKindSlots(kind, type->clasp()) == shape->numFixedSlots()); MOZ_ASSERT_IF(type->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE, IsBackgroundFinalized(kind)); MOZ_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap); diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 4c4676e5d700..6e3981878dab 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -121,7 +121,7 @@ const Class ArrayBufferObject::class_ = { nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ - nullptr, /* trace */ + ArrayBufferObject::trace, JS_NULL_CLASS_SPEC, { nullptr, /* outerObject */ @@ -514,6 +514,24 @@ ArrayBufferObject::neuter(JSContext *cx, Handle buffer, if (buffer->isAsmJS() && !OnDetachAsmJSArrayBuffer(cx, buffer)) return false; + // When neutering buffers where we don't know all views, the new data must + // match the old data. All missing views are sized typed objects, which do + // not have a length property to mutate when neutering. + MOZ_ASSERT_IF(buffer->forInlineTypedObject(), + newContents.data() == buffer->dataPointer()); + + // When neutering a buffer with sized typed object views, any jitcode which + // accesses such views needs to be deoptimized so that neuter checks are + // performed. This is done by setting a compartment wide flag indicating + // that buffers with sized object views have been neutered. + if (buffer->hasSizedObjectViews()) { + // Make sure the global object's type has been instantiated, so the + // flag change will be observed. + if (!cx->global()->getType(cx)) + CrashAtUnhandlableOOM("ArrayBufferObject::neuter"); + types::MarkTypeObjectFlags(cx, cx->global(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED); + } + // Neuter all views on the buffer, clear out the list of views and the // buffer's data. @@ -522,9 +540,16 @@ ArrayBufferObject::neuter(JSContext *cx, Handle buffer, buffer->neuterView(cx, (*views)[i], newContents); cx->compartment()->innerViews.removeViews(buffer); } - if (buffer->firstView()) - buffer->neuterView(cx, buffer->firstView(), newContents); - buffer->setFirstView(nullptr); + if (buffer->firstView()) { + if (buffer->forInlineTypedObject()) { + // The buffer points to inline data in its first view, so to keep + // this pointer alive we don't clear out the first view. + MOZ_ASSERT(buffer->firstView()->is()); + } else { + buffer->neuterView(cx, buffer->firstView(), newContents); + buffer->setFirstView(nullptr); + } + } if (newContents.data() != buffer->dataPointer()) buffer->setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents); @@ -567,6 +592,8 @@ ArrayBufferObject::changeViewContents(JSContext *cx, ArrayBufferViewObject *view void ArrayBufferObject::changeContents(JSContext *cx, BufferContents newContents) { + MOZ_ASSERT(!forInlineTypedObject()); + // Change buffer contents. uint8_t* oldDataPointer = dataPointer(); setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents); @@ -583,6 +610,11 @@ ArrayBufferObject::changeContents(JSContext *cx, BufferContents newContents) /* static */ bool ArrayBufferObject::prepareForAsmJSNoSignals(JSContext *cx, Handle buffer) { + if (buffer->forInlineTypedObject()) { + JS_ReportError(cx, "ArrayBuffer can't be used by asm.js"); + return false; + } + if (!buffer->ownsData()) { BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength()); if (!contents) @@ -613,6 +645,11 @@ ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle buf return false; } + if (buffer->forInlineTypedObject()) { + JS_ReportError(cx, "ArrayBuffer can't be used by asm.js"); + return false; + } + // Get the entire reserved region (with all pages inaccessible). void *data; # ifdef XP_WIN @@ -740,6 +777,7 @@ ArrayBufferObject::setFlags(uint32_t flags) ArrayBufferObject * ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, BufferContents contents, + OwnsState ownsState /* = OwnsData */, NewObjectKind newKind /* = GenericObject */) { MOZ_ASSERT_IF(contents.kind() == MAPPED, contents); @@ -753,18 +791,21 @@ ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, BufferContents content size_t nslots = reservedSlots; bool allocated = false; if (contents) { - // The ABO is taking ownership, so account the bytes against the zone. - size_t nAllocated = nbytes; - if (contents.kind() == MAPPED) - nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize()); - cx->zone()->updateMallocCounter(nAllocated); + if (ownsState == OwnsData) { + // The ABO is taking ownership, so account the bytes against the zone. + size_t nAllocated = nbytes; + if (contents.kind() == MAPPED) + nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize()); + cx->zone()->updateMallocCounter(nAllocated); + } } else { + MOZ_ASSERT(ownsState == OwnsData); size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots; if (nbytes <= usableSlots * sizeof(Value)) { int newSlots = (nbytes - 1) / sizeof(Value) + 1; MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value))); nslots = reservedSlots + newSlots; - contents = BufferContents::createUnowned(nullptr); + contents = BufferContents::createPlain(nullptr); } else { contents = AllocateArrayBufferContents(cx, nbytes); if (!contents) @@ -789,9 +830,9 @@ ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, BufferContents content if (!contents) { void *data = obj->inlineDataPointer(); memset(data, 0, nbytes); - obj->initialize(nbytes, BufferContents::createUnowned(data), DoesntOwnData); + obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData); } else { - obj->initialize(nbytes, contents, OwnsData); + obj->initialize(nbytes, contents, ownsState); } return obj; @@ -801,7 +842,7 @@ ArrayBufferObject * ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, NewObjectKind newKind /* = GenericObject */) { - return create(cx, nbytes, BufferContents::createUnowned(nullptr)); + return create(cx, nbytes, BufferContents::createPlain(nullptr)); } JSObject * @@ -866,7 +907,7 @@ ArrayBufferObject::stealContents(JSContext *cx, Handle buffe BufferContents oldContents(buffer->dataPointer(), buffer->bufferKind()); BufferContents newContents = AllocateArrayBufferContents(cx, buffer->byteLength()); if (!newContents) - return BufferContents::createUnowned(nullptr); + return BufferContents::createPlain(nullptr); if (hasStealableContents) { // Return the old contents and give the neutered buffer a pointer to @@ -875,7 +916,7 @@ ArrayBufferObject::stealContents(JSContext *cx, Handle buffe buffer->setOwnsData(DoesntOwnData); if (!ArrayBufferObject::neuter(cx, buffer, newContents)) { js_free(newContents.data()); - return BufferContents::createUnowned(nullptr); + return BufferContents::createPlain(nullptr); } return oldContents; } @@ -885,7 +926,7 @@ ArrayBufferObject::stealContents(JSContext *cx, Handle buffe memcpy(newContents.data(), oldContents.data(), buffer->byteLength()); if (!ArrayBufferObject::neuter(cx, buffer, oldContents)) { js_free(newContents.data()); - return BufferContents::createUnowned(nullptr); + return BufferContents::createPlain(nullptr); } return newContents; } @@ -924,6 +965,23 @@ ArrayBufferObject::finalize(FreeOp *fop, JSObject *obj) buffer.releaseData(fop); } +/* static */ void +ArrayBufferObject::trace(JSTracer *trc, JSObject *obj) +{ + // If this buffer is associated with an inline typed object, + // fix up the data pointer if the typed object was moved. + ArrayBufferObject &buf = obj->as(); + + if (!buf.forInlineTypedObject()) + return; + + JSObject *view = buf.firstView(); + MOZ_ASSERT(view && view->is()); + + gc::MarkObjectUnbarriered(trc, &view, "array buffer inline typed object owner"); + buf.setSlot(DATA_SLOT, PrivateValue(view->as().inlineTypedMem())); +} + /* static */ void ArrayBufferObject::objectMoved(JSObject *obj, const JSObject *old) { @@ -1279,7 +1337,7 @@ JS_NewArrayBufferWithContents(JSContext *cx, size_t nbytes, void *data) MOZ_ASSERT_IF(!data, nbytes == 0); ArrayBufferObject::BufferContents contents = ArrayBufferObject::BufferContents::create(data); - return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject); + return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData, TenuredObject); } JS_FRIEND_API(bool) @@ -1336,7 +1394,7 @@ JS_NewMappedArrayBufferWithContents(JSContext *cx, size_t nbytes, void *data) MOZ_ASSERT(data); ArrayBufferObject::BufferContents contents = ArrayBufferObject::BufferContents::create(data); - return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject); + return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData, TenuredObject); } JS_PUBLIC_API(void *) diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index 9f21e9983f67..403f48bcd657 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -81,8 +81,8 @@ class ArrayBufferObjectMaybeShared : public NativeObject * This class holds the underlying raw buffer that the various * ArrayBufferViewObject subclasses (DataViewObject and the TypedArrays) * access. It can be created explicitly and passed to an ArrayBufferViewObject - * subclass, or can be created implicitly by constructing a TypedArrayObject - * with a size. + * subclass, or can be created lazily when it is first accessed for a + * TypedArrayObject or TypedObject that doesn't have an explicit buffer. * * ArrayBufferObject (or really the underlying memory) /is not racy/: the * memory is private to a single worker. @@ -104,6 +104,11 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared public: + enum OwnsState { + DoesntOwnData = 0, + OwnsData = 1, + }; + enum BufferKind { PLAIN = 0, // malloced or inline data ASMJS_MALLOCED = 1, @@ -125,7 +130,21 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared // when no longer in use. Releasing the pointer may be done by either // freeing or unmapping it, and how to do this is determined by the // buffer's other flags. + // + // Array buffers which do not own their data include buffers that + // allocate their data inline, and buffers that are created lazily for + // typed objects with inline storage, in which case the buffer points + // directly to the typed object's storage. OWNS_DATA = 0x8, + + // This array buffer was created lazily for a typed object with inline + // data. This implies both that the typed object owns the buffer's data + // and that the list of views sharing this buffer's data might be + // incomplete. Any missing views will be sized typed objects. + FOR_INLINE_TYPED_OBJECT = 0x10, + + // Views of this buffer might include sized typed objects. + SIZED_OBJECT_VIEWS = 0x20 }; public: @@ -151,7 +170,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared return BufferContents(static_cast(data), Kind); } - static BufferContents createUnowned(void *data) + static BufferContents createPlain(void *data) { return BufferContents(static_cast(data), PLAIN); } @@ -181,6 +200,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared static ArrayBufferObject *create(JSContext *cx, uint32_t nbytes, BufferContents contents, + OwnsState ownsState = OwnsData, NewObjectKind newKind = GenericObject); static ArrayBufferObject *create(JSContext *cx, uint32_t nbytes, NewObjectKind newKind = GenericObject); @@ -197,6 +217,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared template static bool createTypedArrayFromBuffer(JSContext *cx, unsigned argc, Value *vp); + static void trace(JSTracer *trc, JSObject *obj); static void objectMoved(JSObject *obj, const JSObject *old); static BufferContents stealContents(JSContext *cx, @@ -227,9 +248,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared // ArrayBufferObjects (strongly) store the first view added to them, while // later views are (weakly) stored in the compartment's InnerViewTable - // below. Buffers typically have at least one view, so this slot optimizes - // for the common case. Avoid entries in the InnerViewTable saves memory - // and non-incrementalized sweep time. + // below. Buffers usually only have one view, so this slot optimizes for + // the common case. Avoiding entries in the InnerViewTable saves memory and + // non-incrementalized sweep time. ArrayBufferViewObject *firstView(); bool addView(JSContext *cx, JSObject *view); @@ -301,12 +322,14 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared static uint32_t neuteredFlag() { return NEUTERED; } - protected: - enum OwnsState { - DoesntOwnData = 0, - OwnsData = 1, - }; + void setForInlineTypedObject() { + setFlags(flags() | FOR_INLINE_TYPED_OBJECT); + } + void setHasSizedObjectViews() { + setFlags(flags() | SIZED_OBJECT_VIEWS); + } + protected: void setDataPointer(BufferContents contents, OwnsState ownsState); void setByteLength(size_t length); @@ -318,6 +341,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA)); } + bool forInlineTypedObject() const { return flags() & FOR_INLINE_TYPED_OBJECT; } + bool hasSizedObjectViews() const { return flags() & SIZED_OBJECT_VIEWS; } + void setIsAsmJSMalloced() { setFlags((flags() & ~KIND_MASK) | ASMJS_MALLOCED); } void setIsNeutered() { setFlags(flags() | NEUTERED); } diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index f85253fd01f3..4445fcfe1734 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -337,6 +337,7 @@ StatsCompartmentCallback(JSRuntime *rt, void *data, JSCompartment *compartment) &cStats.compartmentObject, &cStats.compartmentTables, &cStats.innerViewsTable, + &cStats.lazyArrayBuffersTable, &cStats.crossCompartmentWrappersTable, &cStats.regexpCompartment, &cStats.savedStacksSet); diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 0553e78f56a5..cbaffde5b22c 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2240,6 +2240,10 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, cStats.innerViewsTable, "The table for array buffer inner views."); + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("lazy-array-buffers"), + cStats.lazyArrayBuffersTable, + "The table for typed object lazy array buffers."); + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrapper-table"), cStats.crossCompartmentWrappersTable, "The cross-compartment wrapper table.");