author | Brian Hackett <bhackett1024@gmail.com> |
Wed, 29 Oct 2014 11:14:53 -0700 | |
changeset 212996 | 4ec33eddc6fcdcab1c8bc9bc5100bc2d699c17e3 |
parent 212995 | 33b93eb7e383198b49a04e026797f7eb08d5bace |
child 212997 | a9cab8a15d2bee231a85f2b30b70afd2ab59b525 |
push id | 27738 |
push user | cbook@mozilla.com |
push date | Thu, 30 Oct 2014 13:46:07 +0000 |
treeherder | mozilla-central@1aa1b23d799e [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | sfink, nmatsakis |
bugs | 1083600 |
milestone | 36.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -534,16 +534,17 @@ struct CompartmentStats macro(Other, NotLiveGCThing, ionData) \ macro(Other, NotLiveGCThing, typeInferenceTypeScripts) \ macro(Other, NotLiveGCThing, typeInferenceAllocationSiteTables) \ macro(Other, NotLiveGCThing, typeInferenceArrayTypeTables) \ macro(Other, NotLiveGCThing, typeInferenceObjectTypeTables) \ 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) CompartmentStats() : FOR_EACH_SIZE(ZERO_SIZE) classInfo(), extra(),
--- a/js/src/builtin/SIMD.cpp +++ b/js/src/builtin/SIMD.cpp @@ -82,17 +82,17 @@ js::ToSimdConstant(JSContext *cx, Handle template bool js::ToSimdConstant<Int32x4>(JSContext *cx, HandleValue v, jit::SimdConstant *out); template bool js::ToSimdConstant<Float32x4>(JSContext *cx, HandleValue v, jit::SimdConstant *out); template<typename Elem> static Elem TypedObjectMemory(HandleValue v) { - OutlineTypedObject &obj = v.toObject().as<OutlineTypedObject>(); + TypedObject &obj = v.toObject().as<TypedObject>(); return reinterpret_cast<Elem>(obj.typedMem()); } template<typename SimdType, int lane> static bool GetSimdLane(JSContext *cx, unsigned argc, Value *vp) { typedef typename SimdType::Elem Elem; @@ -133,17 +133,17 @@ static bool SignMask(JSContext *cx, unsi CallArgs args = CallArgsFromVp(argc, vp); if (!args.thisv().isObject() || !args.thisv().toObject().is<TypedObject>()) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, SimdTypeDescr::class_.name, "signMask", InformalValueTypeName(args.thisv())); return false; } - OutlineTypedObject &typedObj = args.thisv().toObject().as<OutlineTypedObject>(); + TypedObject &typedObj = args.thisv().toObject().as<TypedObject>(); TypeDescr &descr = typedObj.typeDescr(); if (descr.kind() != type::Simd || descr.as<SimdTypeDescr>().type() != SimdType::type) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, SimdTypeDescr::class_.name, "signMask", InformalValueTypeName(args.thisv())); return false; } @@ -317,17 +317,17 @@ SimdTypeDescr::call(JSContext *cx, unsig } if (args.length() < LANES) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, args.callee().getClass()->name, "3", "s"); return false; } - Rooted<TypedObject*> result(cx, OutlineTypedObject::createZeroed(cx, descr, 0)); + Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, descr, 0)); if (!result) return false; switch (descr->type()) { case SimdTypeDescr::TYPE_INT32: { int32_t *mem = reinterpret_cast<int32_t*>(result->typedMem()); for (unsigned i = 0; i < 4; i++) { if (!ToInt32(cx, args[i], &mem[i])) @@ -445,17 +445,17 @@ js_InitSIMDClass(JSContext *cx, HandleOb template<typename V> JSObject * js::CreateSimd(JSContext *cx, typename V::Elem *data) { typedef typename V::Elem Elem; Rooted<TypeDescr*> typeDescr(cx, &V::GetTypeDescr(*cx->global())); MOZ_ASSERT(typeDescr); - Rooted<TypedObject *> result(cx, OutlineTypedObject::createZeroed(cx, typeDescr, 0)); + Rooted<TypedObject *> result(cx, TypedObject::createZeroed(cx, typeDescr, 0)); if (!result) return nullptr; Elem *resultMem = reinterpret_cast<Elem *>(result->typedMem()); memcpy(resultMem, data, sizeof(Elem) * V::lanes); return result; }
--- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -1455,17 +1455,17 @@ js_InitTypedObjectDummy(JSContext *cx, H /****************************************************************************** * Typed objects */ int32_t TypedObject::offset() const { - if (is<InlineOpaqueTypedObject>()) + if (is<InlineTypedObject>()) return 0; return typedMem() - typedMemBase(); } int32_t TypedObject::length() const { if (typeDescr().is<SizedArrayTypeDescr>()) @@ -1473,64 +1473,82 @@ TypedObject::length() const return as<OutlineTypedObject>().unsizedLength(); } uint8_t * TypedObject::typedMem() const { MOZ_ASSERT(isAttached()); - if (is<InlineOpaqueTypedObject>()) - return as<InlineOpaqueTypedObject>().inlineTypedMem(); + if (is<InlineTypedObject>()) + return as<InlineTypedObject>().inlineTypedMem(); return as<OutlineTypedObject>().outOfLineTypedMem(); } uint8_t * TypedObject::typedMemBase() const { MOZ_ASSERT(isAttached()); MOZ_ASSERT(is<OutlineTypedObject>()); JSObject &owner = as<OutlineTypedObject>().owner(); if (owner.is<ArrayBufferObject>()) return owner.as<ArrayBufferObject>().dataPointer(); - return owner.as<InlineOpaqueTypedObject>().inlineTypedMem(); + return owner.as<InlineTypedObject>().inlineTypedMem(); } bool TypedObject::isAttached() const { + if (is<InlineTransparentTypedObject>()) { + LazyArrayBufferTable *table = compartment()->lazyArrayBuffers; + if (table) { + ArrayBufferObject *buffer = + table->maybeBuffer(&const_cast<TypedObject *>(this)->as<InlineTransparentTypedObject>()); + if (buffer) + return !buffer->isNeutered(); + } + return true; + } if (is<InlineOpaqueTypedObject>()) return true; if (!as<OutlineTypedObject>().outOfLineTypedMem()) return false; JSObject &owner = as<OutlineTypedObject>().owner(); if (owner.is<ArrayBufferObject>() && owner.as<ArrayBufferObject>().isNeutered()) return false; return true; } bool TypedObject::maybeForwardedIsAttached() const { - if (is<InlineOpaqueTypedObject>()) + if (is<InlineTypedObject>()) return true; if (!as<OutlineTypedObject>().outOfLineTypedMem()) return false; JSObject &owner = *MaybeForwarded(&as<OutlineTypedObject>().owner()); if (owner.is<ArrayBufferObject>() && owner.as<ArrayBufferObject>().isNeutered()) return false; return true; } /* static */ bool TypedObject::GetBuffer(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); - args.rval().setObject(args[0].toObject().as<TransparentTypedObject>().owner()); + JSObject &obj = args[0].toObject(); + ArrayBufferObject *buffer; + if (obj.is<OutlineTransparentTypedObject>()) + buffer = obj.as<OutlineTransparentTypedObject>().getOrCreateBuffer(cx); + else + buffer = obj.as<InlineTransparentTypedObject>().getOrCreateBuffer(cx); + if (!buffer) + MOZ_CRASH(); + args.rval().setObject(*buffer); return true; } /* static */ bool TypedObject::GetByteOffset(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setInt32(args[0].toObject().as<TypedObject>().offset()); @@ -1544,17 +1562,17 @@ TypedObject::GetByteOffset(JSContext *cx /*static*/ OutlineTypedObject * OutlineTypedObject::createUnattached(JSContext *cx, HandleTypeDescr descr, int32_t length) { 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 * PrototypeForTypeDescr(JSContext *cx, HandleTypeDescr descr) { if (descr->is<SimpleTypeDescr>()) { // FIXME Bug 929651 -- What prototype to use? return cx->global()->getOrCreateObjectPrototype(cx); @@ -1585,17 +1603,17 @@ OutlineTypedObject::setOwnerAndData(JSOb } /*static*/ OutlineTypedObject * OutlineTypedObject::createUnattachedWithClass(JSContext *cx, const Class *clasp, HandleTypeDescr type, int32_t length) { - MOZ_ASSERT(clasp == &TransparentTypedObject::class_ || + MOZ_ASSERT(clasp == &OutlineTransparentTypedObject::class_ || clasp == &OutlineOpaqueTypedObject::class_); RootedObject proto(cx, PrototypeForTypeDescr(cx, type)); if (!proto) return nullptr; gc::AllocKind allocKind = allocKindForTypeDescriptor(type); JSObject *obj = NewObjectWithClassProto(cx, clasp, proto, nullptr, allocKind); @@ -1613,16 +1631,19 @@ OutlineTypedObject::createUnattachedWith void OutlineTypedObject::attach(JSContext *cx, ArrayBufferObject &buffer, int32_t offset) { MOZ_ASSERT(!isAttached()); MOZ_ASSERT(offset >= 0); MOZ_ASSERT((size_t) (offset + size()) <= buffer.byteLength()); + if (typeDescr().is<SizedTypeDescr>()) + buffer.setHasSizedObjectViews(); + if (!buffer.addView(cx, this)) CrashAtUnhandlableOOM("TypedObject::attach"); setOwnerAndData(&buffer, buffer.dataPointer() + offset); } void OutlineTypedObject::attach(JSContext *cx, TypedObject &typedObj, int32_t offset) @@ -1634,18 +1655,18 @@ OutlineTypedObject::attach(JSContext *cx if (typedObj.is<OutlineTypedObject>()) { owner = &typedObj.as<OutlineTypedObject>().owner(); offset += typedObj.offset(); } if (owner->is<ArrayBufferObject>()) { attach(cx, owner->as<ArrayBufferObject>(), offset); } else { - MOZ_ASSERT(owner->is<InlineOpaqueTypedObject>()); - setOwnerAndData(owner, owner->as<InlineOpaqueTypedObject>().inlineTypedMem() + offset); + MOZ_ASSERT(owner->is<InlineTypedObject>()); + setOwnerAndData(owner, owner->as<InlineTypedObject>().inlineTypedMem() + offset); } } // Returns a suitable JS_TYPEDOBJ_SLOT_LENGTH value for an instance of // the type `type`. `type` must not be an unsized array. static int32_t TypedObjLengthFromType(TypeDescr &descr) { @@ -1669,37 +1690,36 @@ TypedObjLengthFromType(TypeDescr &descr) OutlineTypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type, HandleTypedObject typedObj, int32_t offset) { MOZ_ASSERT(offset <= typedObj->size()); MOZ_ASSERT(offset + type->size() <= typedObj->size()); int32_t length = TypedObjLengthFromType(*type); - const js::Class *clasp = typedObj->is<TransparentTypedObject>() - ? &TransparentTypedObject::class_ - : &OutlineOpaqueTypedObject::class_; + const js::Class *clasp = typedObj->opaque() + ? &OutlineOpaqueTypedObject::class_ + : &OutlineTransparentTypedObject::class_; Rooted<OutlineTypedObject*> obj(cx); obj = createUnattachedWithClass(cx, clasp, type, length); if (!obj) return nullptr; obj->attach(cx, *typedObj, offset); return obj; } /*static*/ TypedObject * TypedObject::createZeroed(JSContext *cx, HandleTypeDescr descr, int32_t length) { // If possible, create an object with inline data. - if (descr->opaque() && - descr->is<SizedTypeDescr>() && - (size_t) descr->as<SizedTypeDescr>().size() <= InlineOpaqueTypedObject::MaximumSize) + if (descr->is<SizedTypeDescr>() && + (size_t) descr->as<SizedTypeDescr>().size() <= InlineTypedObject::MaximumSize) { - InlineOpaqueTypedObject *obj = InlineOpaqueTypedObject::create(cx, descr); + InlineTypedObject *obj = InlineTypedObject::create(cx, descr); descr->as<SizedTypeDescr>().initInstances(cx->runtime(), obj->inlineTypedMem(), 1); return obj; } // Create unattached wrapper object. Rooted<OutlineTypedObject*> obj(cx, OutlineTypedObject::createUnattached(cx, descr, length)); if (!obj) return nullptr; @@ -1786,17 +1806,17 @@ OutlineTypedObject::obj_trace(JSTracer * gc::MarkObjectUnbarriered(trc, &typedObj.owner_, "typed object owner"); JSObject *owner = typedObj.owner_; uint8_t *mem = typedObj.outOfLineTypedMem(); // Update the data pointer if the owner moved and the owner's data is // inline with it. if (owner != oldOwner && - (owner->is<InlineOpaqueTypedObject>() || + (owner->is<InlineTypedObject>() || owner->as<ArrayBufferObject>().hasInlineData())) { mem += reinterpret_cast<uint8_t *>(owner) - reinterpret_cast<uint8_t *>(oldOwner); typedObj.setData(mem); } if (!descr.opaque() || !typedObj.maybeForwardedIsAttached()) return; @@ -2348,51 +2368,161 @@ OutlineTypedObject::neuter(void *newData setUnsizedLength(0); setData(reinterpret_cast<uint8_t *>(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); RootedObject proto(cx, PrototypeForTypeDescr(cx, 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<InlineOpaqueTypedObject>(); -} - -/* static */ -size_t -InlineOpaqueTypedObject::offsetOfDataStart() -{ - return NativeObject::getFixedSlotOffset(0); + return &obj->as<InlineTypedObject>(); } /* static */ void -InlineOpaqueTypedObject::obj_trace(JSTracer *trc, JSObject *object) +InlineTypedObject::obj_trace(JSTracer *trc, JSObject *object) { - InlineOpaqueTypedObject &typedObj = object->as<InlineOpaqueTypedObject>(); + InlineTypedObject &typedObj = object->as<InlineTypedObject>(); // When this is called for compacting GC, the related objects we touch here // may not have had their slots updated yet. TypeDescr &descr = typedObj.maybeForwardedTypeDescr(); descr.as<SizedTypeDescr>().traceInstances(trc, typedObj.inlineTypedMem(), 1); } +ArrayBufferObject * +InlineTransparentTypedObject::getOrCreateBuffer(JSContext *cx) +{ + LazyArrayBufferTable *&table = cx->compartment()->lazyArrayBuffers; + if (!table) { + table = cx->new_<LazyArrayBufferTable>(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<SizedTypeDescr>().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<ArrayBufferObject>()) + return &owner().as<ArrayBufferObject>(); + return owner().as<InlineTransparentTypedObject>().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<ArrayBufferObject>(); + 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::Base *>(&map); + + typedef HashMap<JSObject *, JSObject *> UnbarrieredMap; + UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap); + + typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> 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 */ #define DEFINE_TYPEDOBJ_CLASS(Name, Trace) \ const Class Name::class_ = { \ # Name, \ Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS, \ @@ -2428,24 +2558,20 @@ InlineOpaqueTypedObject::obj_trace(JSTra TypedObject::obj_deleteGeneric, \ nullptr, nullptr, /* watch/unwatch */ \ nullptr, /* slice */ \ TypedObject::obj_enumerate, \ nullptr, /* thisObject */ \ } \ } -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) { switch (descr.kind()) { case type::Scalar: case type::Reference: case type::Struct: @@ -2864,30 +2990,32 @@ JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js ObjectIsTypedObjectJitInfo, js::ObjectIsTypedObject); bool js::ObjectIsOpaqueTypedObject(ThreadSafeContext *, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); - args.rval().setBoolean(!args[0].toObject().is<TransparentTypedObject>()); + JSObject &obj = args[0].toObject(); + args.rval().setBoolean(obj.is<TypedObject>() && obj.as<TypedObject>().opaque()); return true; } JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::ObjectIsOpaqueTypedObjectJitInfo, ObjectIsOpaqueTypedObjectJitInfo, js::ObjectIsOpaqueTypedObject); bool js::ObjectIsTransparentTypedObject(ThreadSafeContext *, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 1); - args.rval().setBoolean(args[0].toObject().is<TransparentTypedObject>()); + JSObject &obj = args[0].toObject(); + args.rval().setBoolean(obj.is<TypedObject>() && !obj.as<TypedObject>().opaque()); return true; } JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::ObjectIsTransparentTypedObjectJitInfo, ObjectIsTransparentTypedObjectJitInfo, js::ObjectIsTransparentTypedObject); bool
--- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -3,16 +3,17 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef builtin_TypedObject_h #define builtin_TypedObject_h #include "jsobj.h" +#include "jsweakmap.h" #include "builtin/TypedObjectConstants.h" #include "vm/ArrayBufferObject.h" /* * ------------- * Typed Objects * ------------- @@ -57,29 +58,28 @@ * sometimes accesses them). * * - Typed objects: * * A typed object is an instance of a *type object* (note the past participle). * 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: - * - * 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. + * The following layouts are possible: * - * 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). + * 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: 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* * represented by opaque typed objects, but you may have opaque typed * objects for transparent types too. This can occur for two reasons: * (1) a transparent type may be embedded within an opaque type or (2) * users can choose to convert transparent typed objects into opaque * ones to avoid giving access to the buffer itself. @@ -666,16 +666,18 @@ class TypedObject : public JSObject // == size(), but it can happen when taking the "address of" a // 0-sized value. (In other words, we maintain the invariant // that `offset + size <= size()` -- this is always checked in // the caller's side.) MOZ_ASSERT(offset <= (size_t) size()); 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). static TypedObject *createZeroed(JSContext *cx, HandleTypeDescr typeObj, int32_t length); // User-accessible constructor (`new TypeDescriptor(...)`) used for sized // types. Note that the callee here is the type descriptor. static bool constructSized(JSContext *cx, unsigned argc, Value *vp); @@ -779,62 +781,81 @@ class OutlineTypedObject : public TypedO void attach(JSContext *cx, TypedObject &typedObj, int32_t offset); // Invoked when array buffer is transferred elsewhere void neuter(void *newData); 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 { public: 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) { size_t nbytes = descr->as<SizedTypeDescr>().size(); MOZ_ASSERT(nbytes <= MaximumSize); size_t dataSlots = AlignBytes(nbytes, sizeof(Value) / sizeof(Value)); MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value)); return gc::GetGCObjectKind(dataSlots); } 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 InlineTypedObject *create(JSContext *cx, HandleTypeDescr descr); +}; - static InlineOpaqueTypedObject *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_; }; /* * Usage: NewOpaqueTypedObject(typeObj) * * Constructs a new, unattached instance of `Handle`. */ bool NewOpaqueTypedObject(JSContext *cx, unsigned argc, Value *vp); @@ -1039,17 +1060,18 @@ class LoadReference##T { JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_STORE_SCALAR_CLASS_DEFN) JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_LOAD_SCALAR_CLASS_DEFN) JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_STORE_REFERENCE_CLASS_DEFN) 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_; } inline bool IsSimpleTypeDescrClass(const Class* clasp) { return clasp == &ScalarTypeDescr::class_ || @@ -1073,16 +1095,46 @@ IsSizedTypeDescrClass(const Class* clasp inline bool IsTypeDescrClass(const Class* clasp) { return IsSizedTypeDescrClass(clasp) || clasp == &UnsizedArrayTypeDescr::class_; } +inline bool +TypedObject::opaque() const +{ + return is<OutlineOpaqueTypedObject>() || is<InlineOpaqueTypedObject>(); +} + +// 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<PreBarrieredObject, RelocatablePtrObject> 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 * js_InitTypedObjectModuleObject(JSContext *cx, JS::HandleObject obj); template <> inline bool JSObject::is<js::SimpleTypeDescr>() const @@ -1117,20 +1169,28 @@ JSObject::is<js::TypedObject>() const { return IsTypedObjectClass(getClass()); } template <> inline bool JSObject::is<js::OutlineTypedObject>() const { - return getClass() == &js::TransparentTypedObject::class_ || + return getClass() == &js::OutlineTransparentTypedObject::class_ || getClass() == &js::OutlineOpaqueTypedObject::class_; } +template <> +inline bool +JSObject::is<js::InlineTypedObject>() const +{ + return getClass() == &js::InlineTransparentTypedObject::class_ || + getClass() == &js::InlineOpaqueTypedObject::class_; +} + inline void js::TypedProto::initTypeDescrSlot(TypeDescr &descr) { initReservedSlot(JS_TYPROTO_SLOT_DESCR, ObjectValue(descr)); } inline js::type::Kind js::TypedProto::kind() const {
--- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -390,22 +390,23 @@ GetObjectAllocKindForCopy(const Nursery return GetBackgroundAllocKind(TypedArrayObject::AllocKindForLazyBuffer(nbytes)); } // Proxies have finalizers and are not nursery allocated. MOZ_ASSERT(!IsProxy(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<InlineOpaqueTypedObject>()) { + if (obj->is<InlineTypedObject>()) { // 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<InlineOpaqueTypedObject>().typeDescr(); - return InlineOpaqueTypedObject::allocKindForTypeDescriptor(descr); + TypeDescr *descr = &obj->as<InlineTypedObject>().typeDescr(); + MOZ_ASSERT(!IsInsideNursery(descr)); + return InlineTypedObject::allocKindForTypeDescriptor(descr); } // Outline typed objects have special requirements for their allocation kind. if (obj->is<OutlineTypedObject>()) { TypeDescr *descr = &obj->as<OutlineTypedObject>().typeDescr(); return OutlineTypedObject::allocKindForTypeDescriptor(descr); }
--- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -531,16 +531,19 @@ js::gc::GCRuntime::markRuntime(JSTracer if (traceOrMark == TraceRuntime) { if (c->watchpointMap) c->watchpointMap->markAll(trc); } /* Mark debug scopes, if present */ if (c->debugScopes) c->debugScopes->mark(trc); + + if (c->lazyArrayBuffers) + c->lazyArrayBuffers->trace(trc); } MarkInterpreterActivations(&rt->mainThread, trc); jit::MarkJitActivations(&rt->mainThread, trc); if (!isHeapMinorCollecting()) { /*
new file mode 100644 --- /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);
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -5003,38 +5003,16 @@ CodeGenerator::visitTypedArrayElements(L { Register obj = ToRegister(lir->object()); Register out = ToRegister(lir->output()); masm.loadPtr(Address(obj, TypedArrayLayout::dataOffset()), out); 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) { Register obj = ToRegister(lir->object()); MOZ_ASSERT(ToRegister(lir->output()) == ReturnReg); // Eventually we ought to inline this helper function for // efficiency, but it's mildly non-trivial since we must reach // into the type object and so on. @@ -5060,22 +5038,23 @@ bool CodeGenerator::visitTypedObjectElements(LTypedObjectElements *lir) { Register obj = ToRegister(lir->object()); Register out = ToRegister(lir->output()); 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; } bool CodeGenerator::visitSetTypedObjectOffset(LSetTypedObjectOffset *lir) { @@ -5085,22 +5064,23 @@ CodeGenerator::visitSetTypedObjectOffset Register temp1 = ToRegister(lir->temp1()); // Compute the base pointer for the typed object's owner. masm.loadPtr(Address(object, OutlineTypedObject::offsetOfOwner()), temp0); 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); // Compute the new data pointer and set it in the object. masm.addPtr(offset, temp0); masm.storePtr(temp0, Address(object, OutlineTypedObject::offsetOfData())); return true;
--- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -176,17 +176,16 @@ class CodeGenerator : public CodeGenerat bool visitSetArgumentsObjectArg(LSetArgumentsObjectArg *lir); bool visitReturnFromCtor(LReturnFromCtor *lir); bool visitComputeThis(LComputeThis *lir); bool visitLoadArrowThis(LLoadArrowThis *lir); bool visitArrayLength(LArrayLength *lir); 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); bool visitTypedObjectUnsizedLength(LTypedObjectUnsizedLength *ins); bool visitStringLength(LStringLength *lir); bool visitInitializedLength(LInitializedLength *lir); bool visitSetInitializedLength(LSetInitializedLength *lir); bool visitNotO(LNotO *ins);
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -7242,43 +7242,42 @@ IonBuilder::getElemTryTypedObject(bool * static MIRType MIRTypeForTypedArrayRead(Scalar::Type arrayType, bool observedDouble); bool 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); current->add(idInt32); // If we know the length statically from the type, just embed it. // Otherwise, load it from the appropriate reserved slot on the // typed object. We know it's an int32, so we can convert from // Value to int32 using truncation. int32_t lenOfAll; MDefinition *length; 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; } index = addBoundsCheck(idInt32, length); // Since we passed the bounds check, it is impossible for the // result of multiplication to overflow; so enable imul path. @@ -7299,41 +7298,35 @@ IonBuilder::getElemTryScalarElemOfTypedO int32_t elemSize) { MOZ_ASSERT(objPrediction.ofArrayKind()); // Must always be loading the same scalar type ScalarTypeDescr::Type elemType = elemPrediction.scalarType(); MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType)); - bool canBeNeutered; MDefinition *indexAsByteOffset; - if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, - &indexAsByteOffset, &canBeNeutered)) - { - return true; - } - - return pushScalarLoadFromTypedObject(emitted, obj, indexAsByteOffset, elemType, canBeNeutered); + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) + return true; + + 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); current->add(load); current->push(load); // If we are reading in-bounds elements, we can use knowledge about // the array type to determine the result type, even if the opcode has @@ -7364,39 +7357,34 @@ IonBuilder::getElemTryComplexElemOfTyped TypedObjectPrediction elemPrediction, int32_t elemSize) { MOZ_ASSERT(objPrediction.ofArrayKind()); MDefinition *type = loadTypedObjectType(obj); MDefinition *elemTypeObj = typeObjectForElementFromArrayStructType(type); - bool canBeNeutered; MDefinition *indexAsByteOffset; - if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, - &indexAsByteOffset, &canBeNeutered)) - { - return true; - } + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) + return true; return pushDerivedTypedObject(emitted, obj, indexAsByteOffset, - elemPrediction, elemTypeObj, canBeNeutered); + elemPrediction, elemTypeObj); } bool 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(), derivedPrediction, derivedTypeObj, owner, ownerOffset); current->add(derivedTypedObj); @@ -8154,26 +8142,22 @@ IonBuilder::setElemTryScalarElemOfTypedO MDefinition *value, TypedObjectPrediction elemPrediction, int32_t elemSize) { // Must always be loading the same scalar type ScalarTypeDescr::Type elemType = elemPrediction.scalarType(); MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType)); - bool canBeNeutered; MDefinition *indexAsByteOffset; - if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, - &indexAsByteOffset, &canBeNeutered)) - { - return true; - } + 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); *emitted = true; return true; } @@ -8538,17 +8522,17 @@ IonBuilder::jsop_setelem_typed_object(Sc MInstruction *int_index = MToInt32::New(alloc(), index); current->add(int_index); size_t elemSize = ScalarTypeDescr::alignment(arrayType); MMul *byteOffset = MMul::New(alloc(), int_index, constantInt(elemSize), 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; } bool IonBuilder::jsop_length() { @@ -9320,37 +9304,46 @@ bool IonBuilder::getPropTryScalarPropOfTypedObject(bool *emitted, MDefinition *typedObj, int32_t fieldOffset, TypedObjectPrediction fieldPrediction, types::TemporaryTypeSet *resultTypes) { // 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 IonBuilder::getPropTryComplexPropOfTypedObject(bool *emitted, MDefinition *typedObj, int32_t fieldOffset, TypedObjectPrediction fieldPrediction, 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. MDefinition *type = loadTypedObjectType(typedObj); MDefinition *fieldTypeObj = typeObjectForFieldFromStructType(type, fieldIndex); return pushDerivedTypedObject(emitted, typedObj, constantInt(fieldOffset), - fieldPrediction, fieldTypeObj, true); + fieldPrediction, fieldTypeObj); } bool IonBuilder::getPropTryDefiniteSlot(bool *emitted, MDefinition *obj, PropertyName *name, BarrierKind barrier, types::TemporaryTypeSet *types) { MOZ_ASSERT(*emitted == false); uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name); @@ -9987,19 +9980,24 @@ IonBuilder::setPropTryScalarPropOfTypedO MDefinition *obj, int32_t fieldOffset, MDefinition *value, TypedObjectPrediction fieldPrediction) { // 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); *emitted = true; return true; } @@ -10953,17 +10951,16 @@ IonBuilder::loadTypedObjectType(MDefinit // where the data can be found. Often, these returned values are the // same as the inputs, but in cases where intermediate derived type // objects have been created, the return values will remove // intermediate layers (often rendering those derived type objects // into dead code). void IonBuilder::loadTypedObjectData(MDefinition *typedObj, MDefinition *offset, - bool canBeNeutered, MDefinition **owner, MDefinition **ownerOffset) { MOZ_ASSERT(typedObj->type() == MIRType_Object); MOZ_ASSERT(offset->type() == MIRType_Int32); // Shortcircuit derived type objects, meaning the intermediate // objects created to represent `a.b` in an expression like @@ -10980,41 +10977,34 @@ IonBuilder::loadTypedObjectData(MDefinit MAdd *offsetAdd = MAdd::NewAsmJS(alloc(), ins->offset(), offset, MIRType_Int32); current->add(offsetAdd); *owner = ins->owner(); *ownerOffset = offsetAdd; return; } - if (canBeNeutered) { - MNeuterCheck *chk = MNeuterCheck::New(alloc(), typedObj); - current->add(chk); - typedObj = chk; - } - *owner = typedObj; *ownerOffset = offset; } // Takes as input a typed object, an offset into that typed object's // memory, and the type repr of the data found at that offset. Returns // the elements pointer and a scaled offset. The scaled offset is // expressed in units of `unit`; when working with typed array MIR, // this is typically the alignment. 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); current->add(elements); // Scale to a different unit for compat with typed array MIRs. if (unit != 1) { MDiv *scaledOffset = MDiv::NewAsmJS(alloc(), ownerOffset, constantInt(unit), MIRType_Int32, @@ -11091,25 +11081,23 @@ IonBuilder::typeObjectForFieldFromStruct return unboxFieldType; } 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; if (type == Scalar::Uint8Clamped) { toWrite = MClampToUint8::New(alloc(), value); current->add(toWrite->toInstruction()); }
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -466,51 +466,45 @@ class IonBuilder bool typedObjectHasField(MDefinition *typedObj, PropertyName *name, size_t *fieldOffset, TypedObjectPrediction *fieldTypeReprs, size_t *fieldIndex); 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); MDefinition *typeObjectForFieldFromStructType(MDefinition *type, size_t fieldIndex); 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. bool setElemTryTypedArray(bool *emitted, MDefinition *object, MDefinition *index, MDefinition *value); bool setElemTryTypedObject(bool *emitted, MDefinition *obj, MDefinition *index, MDefinition *value); bool setElemTryTypedStatic(bool *emitted, MDefinition *object, @@ -763,17 +757,18 @@ class IonBuilder InliningStatus inlineIsObject(CallInfo &callInfo); InliningStatus inlineHaveSameClass(CallInfo &callInfo); InliningStatus inlineToObject(CallInfo &callInfo); InliningStatus inlineToInteger(CallInfo &callInfo); InliningStatus inlineToString(CallInfo &callInfo); 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. InliningStatus inlineForceSequentialOrInParallelSection(CallInfo &callInfo); InliningStatus inlineBailout(CallInfo &callInfo); InliningStatus inlineAssertFloat32(CallInfo &callInfo); // Bind function.
--- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -4253,34 +4253,16 @@ class LSetTypedObjectOffset : public LIn const LDefinition *temp0() { return getTemp(0); } const LDefinition *temp1() { return getTemp(1); } }; -// 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> { public: LIR_HEADER(BoundsCheck) LBoundsCheck(const LAllocation &index, const LAllocation &length) { setOperand(0, index);
--- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -206,17 +206,16 @@ _(GuardThreadExclusive) \ _(TypeBarrierV) \ _(TypeBarrierO) \ _(MonitorTypes) \ _(PostWriteBarrierO) \ _(PostWriteBarrierV) \ _(InitializedLength) \ _(SetInitializedLength) \ - _(NeuterCheck) \ _(BoundsCheck) \ _(BoundsCheckRange) \ _(BoundsCheckLower) \ _(LoadElementV) \ _(LoadElementT) \ _(LoadElementHole) \ _(StoreElementV) \ _(StoreElementT) \
--- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2586,26 +2586,16 @@ LIRGenerator::visitNot(MNot *ins) } default: MOZ_CRASH("Unexpected MIRType."); } } 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) { LInstruction *check; if (ins->minimum() || ins->maximum()) { check = new(alloc()) LBoundsCheckRange(useRegisterOrConstant(ins->index()), useAny(ins->length()), temp()); } else {
--- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -181,17 +181,16 @@ class LIRGenerator : public LIRGenerator bool visitFilterTypeSet(MFilterTypeSet *ins); bool visitTypeBarrier(MTypeBarrier *ins); bool visitMonitorTypes(MMonitorTypes *ins); bool visitPostWriteBarrier(MPostWriteBarrier *ins); bool visitArrayLength(MArrayLength *ins); 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); bool visitTypedObjectUnsizedLength(MTypedObjectUnsizedLength *ins); bool visitInitializedLength(MInitializedLength *ins); bool visitSetInitializedLength(MSetInitializedLength *ins); bool visitNot(MNot *ins); bool visitBoundsCheck(MBoundsCheck *ins);
--- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -196,21 +196,24 @@ IonBuilder::inlineNativeCall(CallInfo &c if (native == intrinsic_ToString) return inlineToString(callInfo); if (native == intrinsic_IsConstructing) return inlineIsConstructing(callInfo); // 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_, &InlineOpaqueTypedObject::class_); if (native == intrinsic_ObjectIsTypeDescr) return inlineObjectIsTypeDescr(callInfo); if (native == intrinsic_TypeDescrIsSimpleType) return inlineHasClass(callInfo, @@ -1822,38 +1825,43 @@ IonBuilder::inlineNewDenseArrayForParall &templateObject->as<ArrayObject>()); current->add(newObject); current->push(newObject); return InliningStatus_Inlined; } 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; if (callInfo.getArg(0)->type() != MIRType_Object) return InliningStatus_NotInlined; if (getInlineReturnType() != MIRType_Boolean) return InliningStatus_NotInlined; 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]); current->add(hasClass); MBitOr *either = MBitOr::New(alloc(), last, hasClass); either->infer(inspector, pc); current->add(either); last = either;
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -7346,52 +7346,16 @@ class MTypedArrayElements } AliasSet getAliasSet() const { return AliasSet::Load(AliasSet::ObjectFields); } 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`. class MTypedObjectElements : public MUnaryInstruction, public SingleObjectPolicy::Data { private:
--- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -161,17 +161,16 @@ namespace jit { _(TypedArrayElements) \ _(TypedObjectProto) \ _(TypedObjectUnsizedLength) \ _(TypedObjectElements) \ _(SetTypedObjectOffset) \ _(InitializedLength) \ _(SetInitializedLength) \ _(Not) \ - _(NeuterCheck) \ _(BoundsCheck) \ _(BoundsCheckLower) \ _(InArray) \ _(LoadElement) \ _(LoadElementHole) \ _(StoreElement) \ _(StoreElementHole) \ _(ArrayPopShift) \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -247,17 +247,16 @@ class ParallelSafetyVisitor : public MDe SAFE_OP(TypedArrayElements) SAFE_OP(TypedObjectProto) SAFE_OP(TypedObjectUnsizedLength) SAFE_OP(TypedObjectElements) SAFE_OP(SetTypedObjectOffset) SAFE_OP(InitializedLength) WRITE_GUARDED_OP(SetInitializedLength, elements) SAFE_OP(Not) - SAFE_OP(NeuterCheck) SAFE_OP(BoundsCheck) SAFE_OP(BoundsCheckLower) SAFE_OP(LoadElement) SAFE_OP(LoadElementHole) MAYBE_WRITE_GUARDED_OP(StoreElement, elements) WRITE_GUARDED_OP(StoreElementHole, elements) UNSAFE_OP(ArrayPopShift) UNSAFE_OP(ArrayPush)
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -52,16 +52,17 @@ JSCompartment::JSCompartment(Zone *zone, enterCompartmentDepth(0), data(nullptr), objectMetadataCallback(nullptr), lastAnimationTime(0), regExps(runtime_), globalWriteBarriered(false), propertyTree(thisForCtor()), selfHostingScriptSource(nullptr), + lazyArrayBuffers(nullptr), gcIncomingGrayPointers(nullptr), gcWeakMapList(nullptr), gcPreserveJitCode(options.preserveJitCode()), debugModeBits(0), rngState(0), watchpointMap(nullptr), scriptCountsMap(nullptr), debugScriptMap(nullptr), @@ -78,16 +79,17 @@ JSCompartment::JSCompartment(Zone *zone, JSCompartment::~JSCompartment() { js_delete(jitCompartment_); js_delete(watchpointMap); js_delete(scriptCountsMap); js_delete(debugScriptMap); js_delete(debugScopes); + js_delete(lazyArrayBuffers); js_free(enumerators); runtime_->numCompartments--; } bool JSCompartment::init(JSContext *cx) { @@ -864,28 +866,31 @@ JSCompartment::clearBreakpointsIn(FreeOp void JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t *tiAllocationSiteTables, size_t *tiArrayTypeTables, size_t *tiObjectTypeTables, size_t *compartmentObject, size_t *compartmentTables, size_t *innerViewsArg, + size_t *lazyArrayBuffersArg, size_t *crossCompartmentWrappersArg, size_t *regexpCompartment, size_t *savedStacksSet) { *compartmentObject += mallocSizeOf(this); types.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables, tiArrayTypeTables, tiObjectTypeTables); *compartmentTables += baseShapes.sizeOfExcludingThis(mallocSizeOf) + initialShapes.sizeOfExcludingThis(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); } void JSCompartment::adoptWorkerAllocator(Allocator *workerAllocator) {
--- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -127,16 +127,17 @@ typedef HashMap<CrossCompartmentKey, Rea namespace JS { struct TypeInferenceSizes; } namespace js { class AutoDebugModeInvalidation; class DebugScopes; +class LazyArrayBufferTable; class WeakMapBase; } struct JSCompartment { JS::CompartmentOptions options_; private: @@ -244,16 +245,17 @@ struct JSCompartment public: void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t *tiAllocationSiteTables, size_t *tiArrayTypeTables, size_t *tiObjectTypeTables, size_t *compartmentObject, size_t *compartmentTables, size_t *innerViews, + size_t *lazyArrayBuffers, size_t *crossCompartmentWrappers, size_t *regexpCompartment, size_t *savedStacksSet); /* * Shared scope property tree, and arena-pool for allocating its nodes. */ js::PropertyTree propertyTree; @@ -287,20 +289,22 @@ struct JSCompartment void sweepCallsiteClones(); /* * Lazily initialized script source object to use for scripts cloned * from the self-hosting global. */ 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; /* * During GC, stores the head of a list of incoming pointers from gray cells. * * The objects in the list are either cross-compartment wrappers, or * debugger wrapper objects. The list link is either in the second extra
--- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -478,42 +478,49 @@ enum MOZ_ENUM_TYPE(uint32_t) { /* * For the function on a run-once script, whether the function has actually * run multiple times. */ 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 = OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_SETS_MARKED_UNKNOWN, // 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; class StackTypeSet; class HeapTypeSet; class TemporaryTypeSet; /*
--- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -278,38 +278,41 @@ JSObject::isQualifiedVarObj() inline bool JSObject::isUnqualifiedVarObj() { if (is<js::DebugScopeObject>()) return as<js::DebugScopeObject>().scope().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 // arrays we only use enough to cover the class reserved slots, so that // the remaining space in the object's allocation is available for the // 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) { 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); // Non-native classes cannot have reserved slots or private data, and the // objects can't have any fixed slots, for compatibility with // GetReservedOrProxyPrivateSlot. MOZ_ASSERT_IF(!type->clasp()->isNative(), JSCLASS_RESERVED_SLOTS(type->clasp()) == 0);
--- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -116,17 +116,17 @@ const Class ArrayBufferObject::class_ = JS_StrictPropertyStub, /* setProperty */ JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ArrayBufferObject::finalize, nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ - nullptr, /* trace */ + ArrayBufferObject::trace, JS_NULL_CLASS_SPEC, { nullptr, /* outerObject */ nullptr, /* innerObject */ nullptr, /* iteratorObject */ false, /* isWrappedNative */ nullptr, /* weakmapKeyDelegateOp */ ArrayBufferObject::objectMoved @@ -509,27 +509,52 @@ ArrayBufferObject::neuterView(JSContext /* static */ bool ArrayBufferObject::neuter(JSContext *cx, Handle<ArrayBufferObject*> buffer, BufferContents newContents) { 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. if (InnerViewTable::ViewVector *views = cx->compartment()->innerViews.maybeViewsUnbarriered(buffer)) { for (size_t i = 0; i < views->length(); i++) 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<InlineTransparentTypedObject>()); + } else { + buffer->neuterView(cx, buffer->firstView(), newContents); + buffer->setFirstView(nullptr); + } + } if (newContents.data() != buffer->dataPointer()) buffer->setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents); buffer->setByteLength(0); buffer->setIsNeutered(); return true; } @@ -562,32 +587,39 @@ ArrayBufferObject::changeViewContents(JS // Notify compiled jit code that the base pointer has moved. MarkObjectStateChange(cx, view); } void ArrayBufferObject::changeContents(JSContext *cx, BufferContents newContents) { + MOZ_ASSERT(!forInlineTypedObject()); + // Change buffer contents. uint8_t* oldDataPointer = dataPointer(); setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents); // Update all views. if (InnerViewTable::ViewVector *views = cx->compartment()->innerViews.maybeViewsUnbarriered(this)) { for (size_t i = 0; i < views->length(); i++) changeViewContents(cx, (*views)[i], oldDataPointer, newContents); } if (firstView()) changeViewContents(cx, firstView(), oldDataPointer, newContents); } /* static */ bool ArrayBufferObject::prepareForAsmJSNoSignals(JSContext *cx, Handle<ArrayBufferObject*> 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) return false; memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength()); buffer->changeContents(cx, contents); } @@ -608,16 +640,21 @@ ArrayBufferObject::prepareForAsmJS(JSCon return true; // This can't happen except via the shell toggling signals.enabled. if (buffer->isAsmJSMalloced()) { JS_ReportError(cx, "can't access same buffer with and without signals enabled"); 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 data = VirtualAlloc(nullptr, AsmJSMappedSize, MEM_RESERVE, PAGE_NOACCESS); if (!data) return false; # else data = MozTaggedAnonymousMmap(nullptr, AsmJSMappedSize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0, "asm-js-reserved"); @@ -735,41 +772,45 @@ ArrayBufferObject::flags() const void ArrayBufferObject::setFlags(uint32_t flags) { setSlot(FLAGS_SLOT, Int32Value(flags)); } ArrayBufferObject * ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, BufferContents contents, + OwnsState ownsState /* = OwnsData */, NewObjectKind newKind /* = GenericObject */) { MOZ_ASSERT_IF(contents.kind() == MAPPED, contents); // If we need to allocate data, try to use a larger object size class so // that the array buffer's data can be allocated inline with the object. // The extra space will be left unused by the object's fixed slots and // available for the buffer's data, see NewObject(). size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_); 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) return nullptr; allocated = true; } } @@ -784,29 +825,29 @@ ArrayBufferObject::create(JSContext *cx, } MOZ_ASSERT(obj->getClass() == &class_); MOZ_ASSERT(!gc::IsInsideNursery(obj)); 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; } 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 * ArrayBufferObject::createSlice(JSContext *cx, Handle<ArrayBufferObject*> arrayBuffer, uint32_t begin, uint32_t end) { uint32_t bufLength = arrayBuffer->byteLength(); if (begin > bufLength || end > bufLength || begin > end) { @@ -861,36 +902,36 @@ ArrayBufferObject::createDataViewForThis ArrayBufferObject::stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer, bool hasStealableContents) { MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents()); 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 // freshly allocated memory that we will never write to and should // never get committed. buffer->setOwnsData(DoesntOwnData); if (!ArrayBufferObject::neuter(cx, buffer, newContents)) { js_free(newContents.data()); - return BufferContents::createUnowned(nullptr); + return BufferContents::createPlain(nullptr); } return oldContents; } // Create a new chunk of memory to return since we cannot steal the // existing contents away from the buffer. 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; } /* static */ void ArrayBufferObject::addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo *info) { @@ -920,16 +961,33 @@ ArrayBufferObject::finalize(FreeOp *fop, { ArrayBufferObject &buffer = obj->as<ArrayBufferObject>(); if (buffer.ownsData()) 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<ArrayBufferObject>(); + + if (!buf.forInlineTypedObject()) + return; + + JSObject *view = buf.firstView(); + MOZ_ASSERT(view && view->is<InlineTransparentTypedObject>()); + + gc::MarkObjectUnbarriered(trc, &view, "array buffer inline typed object owner"); + buf.setSlot(DATA_SLOT, PrivateValue(view->as<InlineTransparentTypedObject>().inlineTypedMem())); +} + +/* static */ void ArrayBufferObject::objectMoved(JSObject *obj, const JSObject *old) { ArrayBufferObject &dst = obj->as<ArrayBufferObject>(); const ArrayBufferObject &src = old->as<ArrayBufferObject>(); // Fix up possible inline data pointer. if (src.hasInlineData()) dst.setSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer())); @@ -1274,17 +1332,17 @@ JS_NewArrayBuffer(JSContext *cx, uint32_ } JS_PUBLIC_API(JSObject *) JS_NewArrayBufferWithContents(JSContext *cx, size_t nbytes, void *data) { MOZ_ASSERT_IF(!data, nbytes == 0); ArrayBufferObject::BufferContents contents = ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data); - return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject); + return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData, TenuredObject); } JS_FRIEND_API(bool) JS_IsArrayBufferObject(JSObject *obj) { obj = CheckedUnwrap(obj); return obj && obj->is<ArrayBufferObject>(); } @@ -1331,17 +1389,17 @@ JS_StealArrayBufferContents(JSContext *c } JS_PUBLIC_API(JSObject *) JS_NewMappedArrayBufferWithContents(JSContext *cx, size_t nbytes, void *data) { MOZ_ASSERT(data); ArrayBufferObject::BufferContents contents = ArrayBufferObject::BufferContents::create<ArrayBufferObject::MAPPED>(data); - return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject); + return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData, TenuredObject); } JS_PUBLIC_API(void *) JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length) { return ArrayBufferObject::createMappedContents(fd, offset, length).data(); }
--- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -76,18 +76,18 @@ class ArrayBufferObjectMaybeShared : pub }; /* * ArrayBufferObject * * 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. */ class ArrayBufferObject : public ArrayBufferObjectMaybeShared { static bool byteLengthGetterImpl(JSContext *cx, CallArgs args); static bool fun_slice_impl(JSContext *cx, CallArgs args); @@ -99,16 +99,21 @@ class ArrayBufferObject : public ArrayBu static const uint8_t FLAGS_SLOT = 3; static const uint8_t RESERVED_SLOTS = 4; static const size_t ARRAY_BUFFER_ALIGNMENT = 8; public: + enum OwnsState { + DoesntOwnData = 0, + OwnsData = 1, + }; + enum BufferKind { PLAIN = 0, // malloced or inline data ASMJS_MALLOCED = 1, ASMJS_MAPPED = 2, MAPPED = 3, KIND_MASK = 0x3 }; @@ -120,17 +125,31 @@ class ArrayBufferObject : public ArrayBu BUFFER_KIND_MASK = BufferKind::KIND_MASK, NEUTERED = 0x4, // The dataPointer() is owned by this buffer and should be released // 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: class BufferContents { uint8_t *data_; BufferKind kind_; @@ -146,17 +165,17 @@ class ArrayBufferObject : public ArrayBu public: template<BufferKind Kind> static BufferContents create(void *data) { return BufferContents(static_cast<uint8_t*>(data), Kind); } - static BufferContents createUnowned(void *data) + static BufferContents createPlain(void *data) { return BufferContents(static_cast<uint8_t*>(data), PLAIN); } uint8_t *data() const { return data_; } BufferKind kind() const { return kind_; } operator ConvertibleToBool() const { return data_ ? &BufferContents::nonNull : nullptr; } @@ -176,32 +195,34 @@ class ArrayBufferObject : public ArrayBu #ifdef NIGHTLY_BUILD static bool fun_transfer(JSContext *cx, unsigned argc, Value *vp); #endif static bool class_constructor(JSContext *cx, unsigned argc, Value *vp); 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); static JSObject *createSlice(JSContext *cx, Handle<ArrayBufferObject*> arrayBuffer, uint32_t begin, uint32_t end); static bool createDataViewForThisImpl(JSContext *cx, CallArgs args); static bool createDataViewForThis(JSContext *cx, unsigned argc, Value *vp); template<typename T> static bool createTypedArrayFromBufferImpl(JSContext *cx, CallArgs args); template<typename T> 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, Handle<ArrayBufferObject*> buffer, bool hasStealableContents); bool hasStealableContents() const { // Inline elements strictly adhere to the corresponding buffer. @@ -222,19 +243,19 @@ class ArrayBufferObject : public ArrayBu return (ownsData() && isPlain()) || isAsmJSMalloced(); } static void addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo *info); // 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); void setNewOwnedData(FreeOp* fop, BufferContents newContents); void changeContents(JSContext *cx, BufferContents newContents); /* @@ -296,33 +317,38 @@ class ArrayBufferObject : public ArrayBu return getFixedSlotOffset(FLAGS_SLOT); } static size_t offsetOfDataSlot() { return getFixedSlotOffset(DATA_SLOT); } static uint32_t neuteredFlag() { return NEUTERED; } + void setForInlineTypedObject() { + setFlags(flags() | FOR_INLINE_TYPED_OBJECT); + } + void setHasSizedObjectViews() { + setFlags(flags() | SIZED_OBJECT_VIEWS); + } + protected: - enum OwnsState { - DoesntOwnData = 0, - OwnsData = 1, - }; - void setDataPointer(BufferContents contents, OwnsState ownsState); void setByteLength(size_t length); uint32_t flags() const; void setFlags(uint32_t flags); bool ownsData() const { return flags() & OWNS_DATA; } void setOwnsData(OwnsState owns) { 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); } void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) { setByteLength(byteLength); setFlags(0); setFirstView(nullptr); setDataPointer(contents, ownsState);
--- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -332,16 +332,17 @@ StatsCompartmentCallback(JSRuntime *rt, // Measure the compartment object itself, and things hanging off it. compartment->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &cStats.typeInferenceAllocationSiteTables, &cStats.typeInferenceArrayTypeTables, &cStats.typeInferenceObjectTypeTables, &cStats.compartmentObject, &cStats.compartmentTables, &cStats.innerViewsTable, + &cStats.lazyArrayBuffersTable, &cStats.crossCompartmentWrappersTable, &cStats.regexpCompartment, &cStats.savedStacksSet); } static void StatsArenaCallback(JSRuntime *rt, void *data, gc::Arena *arena, JSGCTraceKind traceKind, size_t thingSize)
--- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2235,16 +2235,20 @@ ReportCompartmentStats(const JS::Compart ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-tables"), cStats.compartmentTables, "Compartment-wide tables storing shape and type object information."); ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("inner-views"), 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."); ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("regexp-compartment"), cStats.regexpCompartment, "The regexp compartment and regexp data.");