author | Brian Hackett <bhackett1024@gmail.com> |
Wed, 17 Sep 2014 11:13:17 -0700 | |
changeset 205895 | e3da4ca374cf921b5de1f641e19326dc5e2696bf |
parent 205894 | 7b3ba487c01b6c55a4d290b1f1ab826f42c37eec |
child 205896 | 3774cabb49f23b15d4be7f5649b4f6fb59955385 |
push id | 27507 |
push user | ryanvm@gmail.com |
push date | Thu, 18 Sep 2014 02:16:54 +0000 |
treeherder | mozilla-central@488d490da742 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | sfink |
bugs | 1061404 |
milestone | 35.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 @@ -533,16 +533,17 @@ struct CompartmentStats macro(Other, NotLiveGCThing, baselineStubsFallback) \ 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, crossCompartmentWrappersTable) \ macro(Other, NotLiveGCThing, regexpCompartment) \ macro(Other, NotLiveGCThing, savedStacksSet) CompartmentStats() : FOR_EACH_SIZE(ZERO_SIZE) classInfo(), extra(),
--- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -1503,40 +1503,41 @@ TypedObject::createUnattachedWithClass(J RootedObject obj(cx, NewObjectWithClassProto(cx, clasp, &*proto, nullptr)); if (!obj) return nullptr; obj->initPrivate(nullptr); obj->initReservedSlot(JS_BUFVIEW_SLOT_BYTEOFFSET, Int32Value(0)); obj->initReservedSlot(JS_BUFVIEW_SLOT_LENGTH, Int32Value(length)); obj->initReservedSlot(JS_BUFVIEW_SLOT_OWNER, NullValue()); - obj->initReservedSlot(JS_BUFVIEW_SLOT_NEXT_VIEW, PrivateValue(nullptr)); return static_cast<TypedObject*>(&*obj); } void -TypedObject::attach(ArrayBufferObject &buffer, int32_t offset) +TypedObject::attach(JSContext *cx, ArrayBufferObject &buffer, int32_t offset) { JS_ASSERT(offset >= 0); JS_ASSERT((size_t) (offset + size()) <= buffer.byteLength()); - buffer.addView(this); + if (!buffer.addView(cx, this)) + CrashAtUnhandlableOOM("TypedObject::attach"); + InitArrayBufferViewDataPointer(this, &buffer, offset); setReservedSlot(JS_BUFVIEW_SLOT_BYTEOFFSET, Int32Value(offset)); setReservedSlot(JS_BUFVIEW_SLOT_OWNER, ObjectValue(buffer)); } void -TypedObject::attach(TypedObject &typedObj, int32_t offset) +TypedObject::attach(JSContext *cx, TypedObject &typedObj, int32_t offset) { JS_ASSERT(!typedObj.owner().isNeutered()); JS_ASSERT(typedObj.typedMem() != NULL); - attach(typedObj.owner(), typedObj.offset() + offset); + attach(cx, typedObj.owner(), typedObj.offset() + 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) { switch (descr.kind()) { @@ -1567,17 +1568,17 @@ TypedObject::createDerived(JSContext *cx int32_t length = TypedObjLengthFromType(*type); const js::Class *clasp = typedObj->getClass(); Rooted<TypedObject*> obj(cx); obj = createUnattachedWithClass(cx, clasp, type, length); if (!obj) return nullptr; - obj->attach(*typedObj, offset); + obj->attach(cx, *typedObj, offset); return obj; } /*static*/ TypedObject * TypedObject::createZeroed(JSContext *cx, HandleTypeDescr descr, int32_t length) { @@ -1596,17 +1597,17 @@ TypedObject::createZeroed(JSContext *cx, case type::SizedArray: { size_t totalSize = descr->as<SizedTypeDescr>().size(); Rooted<ArrayBufferObject*> buffer(cx); buffer = ArrayBufferObject::create(cx, totalSize); if (!buffer) return nullptr; descr->as<SizedTypeDescr>().initInstances(cx->runtime(), buffer->dataPointer(), 1); - obj->attach(*buffer, 0); + obj->attach(cx, *buffer, 0); return obj; } case type::UnsizedArray: { Rooted<SizedTypeDescr*> elementTypeRepr(cx); elementTypeRepr = &descr->as<UnsizedArrayTypeDescr>().elementType(); @@ -1619,17 +1620,17 @@ TypedObject::createZeroed(JSContext *cx, Rooted<ArrayBufferObject*> buffer(cx); buffer = ArrayBufferObject::create(cx, totalSize.value()); if (!buffer) return nullptr; if (length) elementTypeRepr->initInstances(cx->runtime(), buffer->dataPointer(), length); - obj->attach(*buffer, 0); + obj->attach(cx, *buffer, 0); return obj; } } MOZ_CRASH("Bad TypeRepresentation Kind"); } static bool @@ -1647,21 +1648,21 @@ ReportTypedObjTypeError(JSContext *cx, JS_free(cx, (void *) typeReprStr); return false; } /*static*/ void TypedObject::obj_trace(JSTracer *trace, JSObject *object) { - ArrayBufferViewObject::trace(trace, object); - JS_ASSERT(object->is<TypedObject>()); TypedObject &typedObj = object->as<TypedObject>(); + gc::MarkSlot(trace, &typedObj.getFixedSlotRef(JS_BUFVIEW_SLOT_OWNER), "typed object owner"); + // 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(); if (descr.opaque()) { uint8_t *mem = typedObj.typedMem(); if (!mem) return; // partially constructed @@ -2400,17 +2401,17 @@ TypedObject::constructSized(JSContext *c return false; } Rooted<TypedObject*> obj(cx); obj = TypedObject::createUnattached(cx, callee, LengthForType(*callee)); if (!obj) return false; - obj->attach(*buffer, offset); + obj->attach(cx, *buffer, offset); args.rval().setObject(*obj); return true; } // Data constructor. if (args[0].isObject()) { // Create the typed object. int32_t length = LengthForType(*callee); @@ -2538,17 +2539,17 @@ TypedObject::constructUnsized(JSContext return false; } Rooted<TypedObject*> obj(cx); obj = TypedObject::createUnattached(cx, callee, length); if (!obj) return false; - obj->attach(args[0].toObject().as<ArrayBufferObject>(), offset); + obj->attach(cx, args[0].toObject().as<ArrayBufferObject>(), offset); } // Data constructor for unsized values if (args[0].isObject()) { // Read length out of the object. RootedObject arg(cx, &args[0].toObject()); RootedValue lengthVal(cx); if (!JSObject::getProperty(cx, arg, arg, cx->names().length, &lengthVal)) @@ -2681,29 +2682,35 @@ js::NewDerivedTypedObject(JSContext *cx, if (!obj) return false; args.rval().setObject(*obj); return true; } bool -js::AttachTypedObject(ThreadSafeContext *, unsigned argc, Value *vp) +js::AttachTypedObject(ThreadSafeContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); JS_ASSERT(args.length() == 3); JS_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>()); JS_ASSERT(args[1].isObject() && args[1].toObject().is<TypedObject>()); JS_ASSERT(args[2].isInt32()); TypedObject &handle = args[0].toObject().as<TypedObject>(); TypedObject &target = args[1].toObject().as<TypedObject>(); JS_ASSERT(handle.typedMem() == nullptr); // must not be attached already size_t offset = args[2].toInt32(); - handle.attach(target, offset); + + if (cx->isForkJoinContext()) { + LockedJSContext ncx(cx->asForkJoinContext()); + handle.attach(ncx, target, offset); + } else { + handle.attach(cx->asJSContext(), target, offset); + } return true; } JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::AttachTypedObjectJitInfo, AttachTypedObjectJitInfo, js::AttachTypedObject); bool
--- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -665,20 +665,20 @@ class TypedObject : public ArrayBufferVi // used for sized types. Note that the callee here is the *type descriptor*, // not the typedObj. static bool constructSized(JSContext *cx, unsigned argc, Value *vp); // As `constructSized`, but for unsized array types. static bool constructUnsized(JSContext *cx, unsigned argc, Value *vp); // Use this method when `buffer` is the owner of the memory. - void attach(ArrayBufferObject &buffer, int32_t offset); + void attach(JSContext *cx, ArrayBufferObject &buffer, int32_t offset); // Otherwise, use this to attach to memory referenced by another typedObj. - void attach(TypedObject &typedObj, int32_t offset); + void attach(JSContext *cx, TypedObject &typedObj, int32_t offset); // Invoked when array buffer is transferred elsewhere void neuter(void *newData); int32_t offset() const { return getReservedSlot(JS_BUFVIEW_SLOT_BYTEOFFSET).toInt32(); }
--- a/js/src/builtin/TypedObjectConstants.h +++ b/js/src/builtin/TypedObjectConstants.h @@ -90,30 +90,29 @@ /////////////////////////////////////////////////////////////////////////// // Slots for typed objects // Common to data view, typed arrays, and typed objects: #define JS_BUFVIEW_SLOT_BYTEOFFSET 0 #define JS_BUFVIEW_SLOT_LENGTH 1 // see (*) below #define JS_BUFVIEW_SLOT_OWNER 2 -#define JS_BUFVIEW_SLOT_NEXT_VIEW 3 // Specific to data view: -#define JS_DATAVIEW_SLOT_DATA 7 // see (**) below -#define JS_DATAVIEW_SLOTS 4 // Number of slots for data views +#define JS_DATAVIEW_SLOT_DATA 3 // see (**) below +#define JS_DATAVIEW_SLOTS 3 // Number of slots for data views // Specific to typed arrays: -#define JS_TYPEDARR_SLOT_TYPE 4 // A ScalarTypeDescr::Type constant +#define JS_TYPEDARR_SLOT_TYPE 3 // A ScalarTypeDescr::Type constant #define JS_TYPEDARR_SLOT_DATA 7 // see (**) below -#define JS_TYPEDARR_SLOTS 5 // Number of slots for typed arrays +#define JS_TYPEDARR_SLOTS 4 // Number of slots for typed arrays // Specific to typed objects: -#define JS_TYPEDOBJ_SLOT_DATA 7 -#define JS_TYPEDOBJ_SLOTS 4 // Number of slots for typed objs +#define JS_TYPEDOBJ_SLOT_DATA 3 +#define JS_TYPEDOBJ_SLOTS 3 // Number of slots for typed objs // (*) The interpretation of the JS_BUFVIEW_SLOT_LENGTH slot depends on // the kind of view: // - DataView: stores the length in bytes // - TypedArray: stores the array length // - TypedObject: for arrays, stores the array length, else 0 // (**) This is the index of the slot that will be used for private data.
--- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -318,17 +318,16 @@ class MinorCollectionTracer : public JST */ RelocationOverlay *head; RelocationOverlay **tail; /* Save and restore all of the runtime state we use during MinorGC. */ bool savedRuntimeNeedBarrier; AutoDisableProxyCheck disableStrictProxyChecking; AutoEnterOOMUnsafeRegion oomUnsafeRegion; - ArrayBufferVector liveArrayBuffers; /* Insert the given relocation entry into the list of things to visit. */ MOZ_ALWAYS_INLINE void insertIntoFixupList(RelocationOverlay *entry) { *tail = entry; tail = &entry->next_; *tail = nullptr; } @@ -349,35 +348,20 @@ class MinorCollectionTracer : public JST * pre-barriers do not fire on objects that have been relocated. The * pre-barrier's call to obj->zone() will try to look through shape_, * which is now the relocation magic and will crash. However, * zone->needsIncrementalBarrier() must still be set correctly so that * allocations we make in minor GCs between incremental slices will * allocate their objects marked. */ rt->setNeedsIncrementalBarrier(false); - - /* - * We use the live array buffer lists to track traced buffers so we can - * sweep their dead views. Incremental collection also use these lists, - * so we may need to save and restore their contents here. - */ - if (rt->gc.state() != NO_INCREMENTAL) { - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - if (!ArrayBufferObject::saveArrayBufferList(c, liveArrayBuffers)) - CrashAtUnhandlableOOM("OOM while saving live array buffers"); - ArrayBufferObject::resetArrayBufferList(c); - } - } } ~MinorCollectionTracer() { runtime()->setNeedsIncrementalBarrier(savedRuntimeNeedBarrier); - if (runtime()->gc.state() != NO_INCREMENTAL) - ArrayBufferObject::restoreArrayBufferLists(liveArrayBuffers); } }; } /* namespace gc */ } /* namespace js */ static AllocKind GetObjectAllocKindForCopy(const Nursery &nursery, JSObject *obj) @@ -821,18 +805,18 @@ js::Nursery::collect(JSRuntime *rt, JS:: TIME_START(collectToFP); TenureCountCache tenureCounts; collectToFixedPoint(&trc, tenureCounts); TIME_END(collectToFP); // Update the array buffer object's view lists. TIME_START(sweepArrayBufferViewList); for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { - if (!c->gcLiveArrayBuffers.empty()) - ArrayBufferObject::sweep(c); + if (c->innerViews.needsSweepAfterMinorGC()) + c->innerViews.sweepAfterMinorGC(rt); } TIME_END(sweepArrayBufferViewList); // Update any slot or element pointers whose destination has been tenured. TIME_START(updateJitActivations); js::jit::UpdateJitActivationsForMinorGC<Nursery>(&rt->mainThread, &trc); TIME_END(updateJitActivations);
--- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -287,16 +287,17 @@ static const PhaseInfo phases[] = { { PHASE_SWEEP_MARK_GRAY, "Mark Gray", PHASE_SWEEP_MARK }, { PHASE_SWEEP_MARK_GRAY_WEAK, "Mark Gray and Weak", PHASE_SWEEP_MARK }, { PHASE_FINALIZE_START, "Finalize Start Callback", PHASE_SWEEP }, { PHASE_SWEEP_ATOMS, "Sweep Atoms", PHASE_SWEEP }, { PHASE_SWEEP_SYMBOL_REGISTRY, "Sweep Symbol Registry", PHASE_SWEEP }, { PHASE_SWEEP_COMPARTMENTS, "Sweep Compartments", PHASE_SWEEP }, { PHASE_SWEEP_DISCARD_CODE, "Sweep Discard Code", PHASE_SWEEP_COMPARTMENTS }, { PHASE_SWEEP_TABLES, "Sweep Tables", PHASE_SWEEP_COMPARTMENTS }, + { PHASE_SWEEP_TABLES_INNER_VIEWS, "Sweep Inner Views", PHASE_SWEEP_TABLES }, { PHASE_SWEEP_TABLES_WRAPPER, "Sweep Cross Compartment Wrappers", PHASE_SWEEP_TABLES }, { PHASE_SWEEP_TABLES_BASE_SHAPE, "Sweep Base Shapes", PHASE_SWEEP_TABLES }, { PHASE_SWEEP_TABLES_INITIAL_SHAPE, "Sweep Initial Shapes", PHASE_SWEEP_TABLES }, { PHASE_SWEEP_TABLES_TYPE_OBJECT, "Sweep Type Objects", PHASE_SWEEP_TABLES }, { PHASE_SWEEP_TABLES_BREAKPOINT, "Sweep Breakpoints", PHASE_SWEEP_TABLES }, { PHASE_SWEEP_TABLES_REGEXP, "Sweep Regexps", PHASE_SWEEP_TABLES }, { PHASE_DISCARD_ANALYSIS, "Discard Analysis", PHASE_SWEEP_COMPARTMENTS }, { PHASE_DISCARD_TI, "Discard TI", PHASE_DISCARD_ANALYSIS },
--- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -38,16 +38,17 @@ enum Phase { PHASE_SWEEP_MARK_GRAY, PHASE_SWEEP_MARK_GRAY_WEAK, PHASE_FINALIZE_START, PHASE_SWEEP_ATOMS, PHASE_SWEEP_SYMBOL_REGISTRY, PHASE_SWEEP_COMPARTMENTS, PHASE_SWEEP_DISCARD_CODE, PHASE_SWEEP_TABLES, + PHASE_SWEEP_TABLES_INNER_VIEWS, PHASE_SWEEP_TABLES_WRAPPER, PHASE_SWEEP_TABLES_BASE_SHAPE, PHASE_SWEEP_TABLES_INITIAL_SHAPE, PHASE_SWEEP_TABLES_TYPE_OBJECT, PHASE_SWEEP_TABLES_BREAKPOINT, PHASE_SWEEP_TABLES_REGEXP, PHASE_DISCARD_ANALYSIS, PHASE_DISCARD_TI,
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -573,16 +573,22 @@ JSCompartment::markRoots(JSTracer *trc) void JSCompartment::sweep(FreeOp *fop, bool releaseTypes) { JS_ASSERT(!activeAnalysis); JSRuntime *rt = runtimeFromMainThread(); { gcstats::MaybeAutoPhase ap(rt->gc.stats, !rt->isHeapCompacting(), + gcstats::PHASE_SWEEP_TABLES_INNER_VIEWS); + innerViews.sweep(rt); + } + + { + gcstats::MaybeAutoPhase ap(rt->gc.stats, !rt->isHeapCompacting(), gcstats::PHASE_SWEEP_TABLES_WRAPPER); sweepCrossCompartmentWrappers(); } /* Remove dead references held weakly by the compartment. */ sweepBaseShapeTable(); sweepInitialShapeTable(); @@ -907,27 +913,29 @@ 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 *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); *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 @@ -227,16 +227,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 *crossCompartmentWrappers, size_t *regexpCompartment, size_t *savedStacksSet); /* * Shared scope property tree, and arena-pool for allocating its nodes. */ js::PropertyTree propertyTree; @@ -269,31 +270,32 @@ 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. + js::InnerViewTable innerViews; + /* 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 * slot for the former, or a special slot for the latter. */ JSObject *gcIncomingGrayPointers; - /* During GC, list of live array buffers with >1 view accumulated during tracing. */ - js::ArrayBufferVector gcLiveArrayBuffers; - /* Linked list of live weakmaps in this compartment. */ js::WeakMapBase *gcWeakMapList; private: enum { DebugMode = 1 << 0, DebugNeedDelazification = 1 << 1 };
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3680,17 +3680,16 @@ GCRuntime::beginMarkPhase(JS::gcreason:: } else { isFull = false; } zone->setPreservingCode(false); } for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) { - JS_ASSERT(c->gcLiveArrayBuffers.empty()); c->marked = false; c->scheduledForDestruction = false; c->maybeAlive = false; if (shouldPreserveJITCode(c, currentTime, reason)) c->zone()->setPreservingCode(true); } if (!rt->gc.cleanUpEverything) { @@ -3974,43 +3973,37 @@ js::gc::MarkingValidator::nonIncremental return; memcpy((void *)entry->bitmap, (void *)bitmap->bitmap, sizeof(bitmap->bitmap)); if (!map.putNew(r.front(), entry)) return; } /* - * Temporarily clear the weakmaps' mark flags and the lists of live array - * buffers for the compartments we are collecting. + * Temporarily clear the weakmaps' mark flags for the compartments we are + * collecting. */ WeakMapSet markedWeakMaps; if (!markedWeakMaps.init()) return; - ArrayBufferVector arrayBuffers; for (GCCompartmentsIter c(runtime); !c.done(); c.next()) { - if (!WeakMapBase::saveCompartmentMarkedWeakMaps(c, markedWeakMaps) || - !ArrayBufferObject::saveArrayBufferList(c, arrayBuffers)) - { + if (!WeakMapBase::saveCompartmentMarkedWeakMaps(c, markedWeakMaps)) return; - } } /* * After this point, the function should run to completion, so we shouldn't * do anything fallible. */ initialized = true; - for (GCCompartmentsIter c(runtime); !c.done(); c.next()) { + for (GCCompartmentsIter c(runtime); !c.done(); c.next()) WeakMapBase::unmarkCompartment(c); - ArrayBufferObject::resetArrayBufferList(c); - } /* Re-do all the marking, but non-incrementally. */ js::gc::State state = gc->incrementalState; gc->incrementalState = MARK_ROOTS; JS_ASSERT(gcmarker->isDrained()); gcmarker->reset(); @@ -4058,22 +4051,19 @@ js::gc::MarkingValidator::nonIncremental /* Take a copy of the non-incremental mark state and restore the original. */ for (GCChunkSet::Range r(gc->chunkSet.all()); !r.empty(); r.popFront()) { Chunk *chunk = r.front(); ChunkBitmap *bitmap = &chunk->bitmap; ChunkBitmap *entry = map.lookup(chunk)->value(); Swap(*entry, *bitmap); } - for (GCCompartmentsIter c(runtime); !c.done(); c.next()) { + for (GCCompartmentsIter c(runtime); !c.done(); c.next()) WeakMapBase::unmarkCompartment(c); - ArrayBufferObject::resetArrayBufferList(c); - } WeakMapBase::restoreCompartmentMarkedWeakMaps(markedWeakMaps); - ArrayBufferObject::restoreArrayBufferLists(arrayBuffers); gc->incrementalState = state; } void js::gc::MarkingValidator::validate() { /* @@ -4331,20 +4321,18 @@ GCRuntime::getNextZoneGroup() JS_ASSERT(zone->isGCMarking()); zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit); zone->setGCState(Zone::NoGC); zone->gcGrayRoots.clearAndFree(); } rt->setNeedsIncrementalBarrier(false); AssertNeedsBarrierFlagsConsistent(rt); - for (GCCompartmentGroupIter comp(rt); !comp.done(); comp.next()) { - ArrayBufferObject::resetArrayBufferList(comp); + for (GCCompartmentGroupIter comp(rt); !comp.done(); comp.next()) ResetGrayList(comp); - } abortSweepAfterCurrentGroup = false; currentZoneGroup = nullptr; } } /* * Gray marking: @@ -4657,20 +4645,16 @@ GCRuntime::beginSweepingZoneGroup() rt->sweepAtoms(); } { gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_SYMBOL_REGISTRY); rt->symbolRegistry().sweep(); } } - /* Prune out dead views from ArrayBuffer's view lists. */ - for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) - ArrayBufferObject::sweep(c); - /* Collect watch points associated with unreachable objects. */ WatchpointMap::sweepAll(rt); /* Detach unreachable debuggers and global objects from each other. */ Debugger::sweepAll(&fop); { gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_COMPARTMENTS); @@ -5014,17 +4998,16 @@ GCRuntime::endSweepPhase(bool lastGC) JS_ASSERT_IF(!IsBackgroundFinalized(AllocKind(i)) || !sweepOnBackgroundThread, !zone->allocator.arenas.arenaListsToSweep[i]); } } for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { JS_ASSERT(!c->gcIncomingGrayPointers); - JS_ASSERT(c->gcLiveArrayBuffers.empty()); for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) { if (e.front().key().kind != CrossCompartmentKey::StringWrapper) AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject()); } } #endif } @@ -5160,20 +5143,18 @@ GCRuntime::resetIncrementalGC(const char case MARK: { /* Cancel any ongoing marking. */ AutoCopyFreeListToArenasForGC copy(rt); marker.reset(); marker.stop(); - for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - ArrayBufferObject::resetArrayBufferList(c); + for (GCCompartmentsIter c(rt); !c.done(); c.next()) ResetGrayList(c); - } for (GCZonesIter zone(rt); !zone.done(); zone.next()) { JS_ASSERT(zone->isGCMarking()); zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit); zone->setGCState(Zone::NoGC); } rt->setNeedsIncrementalBarrier(false); AssertNeedsBarrierFlagsConsistent(rt); @@ -5203,19 +5184,16 @@ GCRuntime::resetIncrementalGC(const char default: MOZ_CRASH("Invalid incremental GC state"); } stats.reset(reason); #ifdef DEBUG - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) - JS_ASSERT(c->gcLiveArrayBuffers.empty()); - for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { JS_ASSERT(!zone->needsIncrementalBarrier()); for (unsigned i = 0; i < FINALIZE_LIMIT; ++i) JS_ASSERT(!zone->allocator.arenas.arenaListsToSweep[i]); } #endif }
--- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -114,17 +114,17 @@ const Class ArrayBufferObject::class_ = JS_StrictPropertyStub, /* setProperty */ JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ArrayBufferObject::finalize, nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ - ArrayBufferObject::obj_trace, + nullptr, /* trace */ JS_NULL_CLASS_SPEC, { nullptr, /* outerObject */ nullptr, /* innerObject */ nullptr, /* iteratorObject */ false, /* isWrappedNative */ nullptr, /* weakmapKeyDelegateOp */ ArrayBufferObject::objectMoved @@ -271,126 +271,108 @@ AllocateArrayBufferContents(JSContext *c { uint8_t *p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes); if (!p) js_ReportOutOfMemory(cx); return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN_BUFFER>(p); } -ArrayBufferViewObject * -ArrayBufferObject::viewList() const -{ - return reinterpret_cast<ArrayBufferViewObject *>(getSlot(VIEW_LIST_SLOT).toPrivate()); -} - -void -ArrayBufferObject::setViewListNoBarrier(ArrayBufferViewObject *viewsHead) -{ - setSlot(VIEW_LIST_SLOT, PrivateValue(viewsHead)); -} - -void -ArrayBufferObject::setViewList(ArrayBufferViewObject *viewsHead) -{ - if (ArrayBufferViewObject *oldHead = viewList()) - ArrayBufferViewObject::writeBarrierPre(oldHead); - setViewListNoBarrier(viewsHead); - PostBarrierTypedArrayObject(this); -} - bool ArrayBufferObject::canNeuter(JSContext *cx) { if (isAsmJSArrayBuffer()) { if (!ArrayBufferObject::canNeuterAsmJSArrayBuffer(cx, *this)) return false; } return true; } +void +ArrayBufferObject::neuterView(JSContext *cx, ArrayBufferViewObject *view, + BufferContents newContents) +{ + view->neuter(newContents.data()); + + // Notify compiled jit code that the base pointer has moved. + MarkObjectStateChange(cx, view); +} + /* static */ void ArrayBufferObject::neuter(JSContext *cx, Handle<ArrayBufferObject*> buffer, BufferContents newContents) { JS_ASSERT(buffer->canNeuter(cx)); // Neuter all views on the buffer, clear out the list of views and the // buffer's data. - for (ArrayBufferViewObject *view = buffer->viewList(); view; view = view->nextView()) { - view->neuter(newContents.data()); - - // Notify compiled jit code that the base pointer has moved. - MarkObjectStateChange(cx, view); + 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 (newContents.data() != buffer->dataPointer()) buffer->setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents); buffer->setByteLength(0); - buffer->setViewList(nullptr); buffer->setIsNeutered(); - - // If this is happening during an incremental GC, remove the buffer from - // the list of live buffers with multiple views if necessary. - if (buffer->inLiveList()) { - ArrayBufferVector &gcLiveArrayBuffers = cx->compartment()->gcLiveArrayBuffers; - DebugOnly<bool> found = false; - for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) { - if (buffer == gcLiveArrayBuffers[i]) { - found = true; - gcLiveArrayBuffers[i] = gcLiveArrayBuffers.back(); - gcLiveArrayBuffers.popBack(); - break; - } - } - JS_ASSERT(found); - buffer->setInLiveList(false); - } } void ArrayBufferObject::setNewOwnedData(FreeOp* fop, BufferContents newContents) { JS_ASSERT(!isAsmJSArrayBuffer()); if (ownsData()) { JS_ASSERT(newContents.data() != dataPointer()); releaseData(fop); } setDataPointer(newContents, OwnsData); } void +ArrayBufferObject::changeViewContents(JSContext *cx, ArrayBufferViewObject *view, + uint8_t *oldDataPointer, BufferContents newContents) +{ + // Watch out for NULL data pointers in views. This means that the view + // is not fully initialized (in which case it'll be initialized later + // with the correct pointer). + uint8_t *viewDataPointer = view->dataPointer(); + if (viewDataPointer) { + JS_ASSERT(newContents); + ptrdiff_t offset = viewDataPointer - oldDataPointer; + viewDataPointer = static_cast<uint8_t *>(newContents.data()) + offset; + view->setPrivate(viewDataPointer); + } + + // Notify compiled jit code that the base pointer has moved. + MarkObjectStateChange(cx, view); +} + +void ArrayBufferObject::changeContents(JSContext *cx, BufferContents newContents) { // Change buffer contents. uint8_t* oldDataPointer = dataPointer(); setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents); // Update all views. - ArrayBufferViewObject *viewListHead = viewList(); - for (ArrayBufferViewObject *view = viewListHead; view; view = view->nextView()) { - // Watch out for NULL data pointers in views. This means that the view - // is not fully initialized (in which case it'll be initialized later - // with the correct pointer). - uint8_t *viewDataPointer = view->dataPointer(); - if (viewDataPointer) { - JS_ASSERT(newContents); - ptrdiff_t offset = viewDataPointer - oldDataPointer; - viewDataPointer = static_cast<uint8_t *>(newContents.data()) + offset; - view->setPrivate(viewDataPointer); - } - - // Notify compiled jit code that the base pointer has moved. - MarkObjectStateChange(cx, view); + 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->isAsmJSArrayBuffer()) return true; @@ -532,36 +514,16 @@ void ArrayBufferObject::releaseMappedArray() { if(!isMappedArrayBuffer() || isNeutered()) return; DeallocateMappedContent(dataPointer(), byteLength()); } -void -ArrayBufferObject::addView(ArrayBufferViewObject *view) -{ - // Note that pre-barriers are not needed here because either the list was - // previously empty, in which case no pointer is being overwritten, or the - // list was nonempty and will be made weak during this call (and weak - // pointers cannot violate the snapshot-at-the-beginning invariant.) - - ArrayBufferViewObject *viewsHead = viewList(); - if (viewsHead == nullptr) { - // This ArrayBufferObject will have a single view at this point, so it - // is a strong pointer (it will be marked during tracing.) - JS_ASSERT(view->nextView() == nullptr); - } else { - view->setNextView(viewsHead); - } - - setViewList(view); -} - uint8_t * ArrayBufferObject::dataPointer() const { return static_cast<uint8_t *>(getSlot(DATA_SLOT).toPrivate()); } void ArrayBufferObject::releaseData(FreeOp *fop) @@ -799,204 +761,209 @@ ArrayBufferObject::finalize(FreeOp *fop, { ArrayBufferObject &buffer = obj->as<ArrayBufferObject>(); if (buffer.ownsData()) buffer.releaseData(fop); } /* static */ void -ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj) -{ - JSRuntime *rt = trc->runtime(); - if (!IS_GC_MARKING_TRACER(trc) && !rt->isHeapMinorCollecting() && !rt->isHeapCompacting() -#ifdef JSGC_FJGENERATIONAL - && !rt->isFJMinorCollecting() -#endif - ) - { - return; - } - - // ArrayBufferObjects need to maintain a list of possibly-weak pointers to - // their views. The straightforward way to update the weak pointers would - // be in the views' finalizers, but giving views finalizers means they - // cannot be swept in the background. This results in a very high - // performance cost. Instead, ArrayBufferObjects with a single view hold a - // strong pointer to the view. This can entrain garbage when the single - // view becomes otherwise unreachable while the buffer is still live, but - // this is expected to be rare. ArrayBufferObjects with 0-1 views are - // expected to be by far the most common cases. ArrayBufferObjects with - // multiple views are collected into a linked list during collection, and - // then swept to prune out their dead views. - - ArrayBufferObject &buffer = AsArrayBuffer(obj); - ArrayBufferViewObject *viewsHead = buffer.viewList(); - if (!viewsHead) - return; - - ArrayBufferViewObject *tmp = viewsHead; - buffer.setViewList(UpdateObjectIfRelocated(rt, &tmp)); - - if (tmp->nextView() == nullptr) { - // Single view: mark it, but only if we're actually doing a GC pass - // right now. Otherwise, the tracing pass for barrier verification will - // fail if we add another view and the pointer becomes weak. - MarkObjectUnbarriered(trc, &viewsHead, "arraybuffer.singleview"); - buffer.setViewListNoBarrier(viewsHead); - } else if (!rt->isHeapCompacting()) { - // Multiple views: do not mark, but append buffer to list. - ArrayBufferVector &gcLiveArrayBuffers = buffer.compartment()->gcLiveArrayBuffers; - - // obj_trace may be called multiple times before sweep(), so avoid - // adding this buffer to the list multiple times. - if (buffer.inLiveList()) { -#ifdef DEBUG - bool found = false; - for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) - found |= gcLiveArrayBuffers[i] == &buffer; - JS_ASSERT(found); -#endif - } else if (gcLiveArrayBuffers.append(&buffer)) { - buffer.setInLiveList(true); - } else { - CrashAtUnhandlableOOM("OOM while updating live array buffers"); - } - } else { - // If we're fixing up pointers after compacting then trace everything. - ArrayBufferViewObject *prev = nullptr; - ArrayBufferViewObject *view = viewsHead; - while (view) { - JS_ASSERT(buffer.compartment() == MaybeForwarded(view)->compartment()); - MarkObjectUnbarriered(trc, &view, "arraybuffer.singleview"); - if (prev) - prev->setNextView(view); - else - buffer.setViewListNoBarrier(view); - view = view->nextView(); - } - } -} - -/* static */ void -ArrayBufferObject::sweep(JSCompartment *compartment) -{ - JSRuntime *rt = compartment->runtimeFromMainThread(); - ArrayBufferVector &gcLiveArrayBuffers = compartment->gcLiveArrayBuffers; - - for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) { - ArrayBufferObject *buffer = gcLiveArrayBuffers[i]; - - JS_ASSERT(buffer->inLiveList()); - buffer->setInLiveList(false); - - ArrayBufferViewObject *viewsHead = buffer->viewList(); - JS_ASSERT(viewsHead); - buffer->setViewList(UpdateObjectIfRelocated(rt, &viewsHead)); - - // Rebuild the list of views of the ArrayBufferObject, discarding dead - // views. If there is only one view, it will have already been marked. - ArrayBufferViewObject *prevLiveView = nullptr; - ArrayBufferViewObject *view = viewsHead; - while (view) { - JS_ASSERT(buffer->compartment() == view->compartment()); - ArrayBufferViewObject *nextView = view->nextView(); - if (!IsObjectAboutToBeFinalized(&view)) { - view->setNextView(prevLiveView); - prevLiveView = view; - } - view = UpdateObjectIfRelocated(rt, &nextView); - } - - buffer->setViewList(prevLiveView); - } - - gcLiveArrayBuffers.clear(); -} - -/* 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. const size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&ArrayBufferObject::class_); if (src.dataPointer() == src.fixedData(reservedSlots)) dst.setSlot(DATA_SLOT, PrivateValue(dst.fixedData(reservedSlots))); } +ArrayBufferViewObject * +ArrayBufferObject::firstView() +{ + return getSlot(FIRST_VIEW_SLOT).isObject() + ? &getSlot(FIRST_VIEW_SLOT).toObject().as<ArrayBufferViewObject>() + : nullptr; +} + void -ArrayBufferObject::resetArrayBufferList(JSCompartment *comp) +ArrayBufferObject::setFirstView(ArrayBufferViewObject *view) +{ + setSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view)); +} + +bool +ArrayBufferObject::addView(JSContext *cx, ArrayBufferViewObject *view) { - ArrayBufferVector &gcLiveArrayBuffers = comp->gcLiveArrayBuffers; + if (!firstView()) { + setFirstView(view); + return true; + } + return cx->compartment()->innerViews.addView(cx, this, view); +} + +/* + * InnerViewTable + */ + +static size_t VIEW_LIST_MAX_LENGTH = 500; - for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) { - ArrayBufferObject *buffer = gcLiveArrayBuffers[i]; +bool +InnerViewTable::addView(JSContext *cx, ArrayBufferObject *obj, ArrayBufferViewObject *view) +{ + // ArrayBufferObject entries are only added when there are multiple views. + JS_ASSERT(obj->firstView()); + + if (!map.initialized() && !map.init()) + return false; + + Map::AddPtr p = map.lookupForAdd(obj); + + JS_ASSERT(!gc::IsInsideNursery(obj)); + bool addToNursery = nurseryKeysValid && gc::IsInsideNursery(view); + + if (p) { + ViewVector &views = p->value(); + JS_ASSERT(!views.empty()); - JS_ASSERT(buffer->inLiveList()); - buffer->setInLiveList(false); + if (addToNursery) { + // Only add the entry to |nurseryKeys| if it isn't already there. + if (views.length() >= VIEW_LIST_MAX_LENGTH) { + // To avoid quadratic blowup, skip the loop below if we end up + // adding enormous numbers of views for the same object. + nurseryKeysValid = false; + } else { + for (size_t i = 0; i < views.length(); i++) { + if (gc::IsInsideNursery(views[i])) + addToNursery = false; + } + } + } + + if (!views.append(view)) + return false; + } else { + if (!map.add(p, obj, ViewVector())) + return false; + JS_ALWAYS_TRUE(p->value().append(view)); } - gcLiveArrayBuffers.clear(); -} - -/* static */ bool -ArrayBufferObject::saveArrayBufferList(JSCompartment *comp, ArrayBufferVector &vector) -{ - const ArrayBufferVector &gcLiveArrayBuffers = comp->gcLiveArrayBuffers; - - for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) { - if (!vector.append(gcLiveArrayBuffers[i])) - return false; - } + if (addToNursery && !nurseryKeys.append(obj)) + nurseryKeysValid = false; return true; } -/* static */ void -ArrayBufferObject::restoreArrayBufferLists(ArrayBufferVector &vector) +InnerViewTable::ViewVector * +InnerViewTable::maybeViewsUnbarriered(ArrayBufferObject *obj) +{ + if (!map.initialized()) + return nullptr; + + Map::Ptr p = map.lookup(obj); + if (p) + return &p->value(); + return nullptr; +} + +void +InnerViewTable::removeViews(ArrayBufferObject *obj) { - for (size_t i = 0; i < vector.length(); i++) { - ArrayBufferObject *buffer = vector[i]; + Map::Ptr p = map.lookup(obj); + JS_ASSERT(p); + + map.remove(p); +} + +bool +InnerViewTable::sweepEntry(JSObject **pkey, ViewVector &views) +{ + if (IsObjectAboutToBeFinalized(pkey)) + return true; + + JS_ASSERT(!views.empty()); + for (size_t i = 0; i < views.length(); i++) { + if (IsObjectAboutToBeFinalized(&views[i])) { + views[i--] = views.back(); + views.popBack(); + } + } + + return views.empty(); +} + +void +InnerViewTable::sweep(JSRuntime *rt) +{ + JS_ASSERT(nurseryKeys.empty()); + + if (!map.initialized()) + return; - JS_ASSERT(!buffer->inLiveList()); - buffer->setInLiveList(true); + for (Map::Enum e(map); !e.empty(); e.popFront()) { + JSObject *key = e.front().key(); + if (sweepEntry(&key, e.front().value())) + e.removeFront(); + else if (key != e.front().key()) + e.rekeyFront(key); + } +} + +void +InnerViewTable::sweepAfterMinorGC(JSRuntime *rt) +{ + JS_ASSERT(!nurseryKeys.empty()); + + if (nurseryKeysValid) { + for (size_t i = 0; i < nurseryKeys.length(); i++) { + JSObject *key = nurseryKeys[i]; + Map::Ptr p = map.lookup(key); + if (!p) + continue; - buffer->compartment()->gcLiveArrayBuffers.infallibleAppend(buffer); + if (sweepEntry(&key, p->value())) + map.remove(nurseryKeys[i]); + else + map.rekeyIfMoved(nurseryKeys[i], key); + } + nurseryKeys.clear(); + } else { + // Do the required sweeping by looking at every map entry. + nurseryKeys.clear(); + sweep(rt); + + nurseryKeysValid = true; } } +size_t +InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) +{ + if (!map.initialized()) + return 0; + + size_t vectorSize = 0; + for (Map::Enum e(map); !e.empty(); e.popFront()) + vectorSize += e.front().value().sizeOfExcludingThis(mallocSizeOf); + + return vectorSize + + map.sizeOfExcludingThis(mallocSizeOf) + + nurseryKeys.sizeOfExcludingThis(mallocSizeOf); +} + /* * ArrayBufferViewObject */ -/* - * This method is used to trace TypedArrayObjects and DataViewObjects. We need - * a custom tracer because some of an ArrayBufferViewObject's reserved slots - * are weak references, and some need to be updated specially during moving - * GCs. - */ -/* static */ void -ArrayBufferViewObject::trace(JSTracer *trc, JSObject *obj) +template <> +bool +JSObject::is<js::ArrayBufferViewObject>() const { - HeapSlot &bufSlot = obj->getReservedSlotRef(BUFFER_SLOT); - MarkSlot(trc, &bufSlot, "typedarray.buffer"); - - // Update obj's data pointer if the array buffer moved. Note that during - // initialization, bufSlot may still contain |undefined|. - if (bufSlot.isObject()) { - ArrayBufferObject &buf = AsArrayBuffer(MaybeForwarded(&bufSlot.toObject())); - int32_t offset = obj->getReservedSlot(BYTEOFFSET_SLOT).toInt32(); - MOZ_ASSERT(buf.dataPointer() != nullptr); - obj->initPrivate(buf.dataPointer() + offset); - } - - /* Update NEXT_VIEW_SLOT, if the view moved. */ - IsSlotMarked(&obj->getReservedSlotRef(NEXT_VIEW_SLOT)); + return is<DataViewObject>() || is<TypedArrayObject>() || is<TypedObject>(); } void ArrayBufferViewObject::neuter(void *newData) { MOZ_ASSERT(newData != nullptr); if (is<DataViewObject>()) as<DataViewObject>().neuter(newData);
--- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -52,18 +52,16 @@ class ArrayBufferViewObject; // // Most APIs will only accept ArrayBufferObject. ArrayBufferObjectMaybeShared exists // as a join point to allow APIs that can take or use either, notably AsmJS. // // As ArrayBufferObject and SharedArrayBufferObject are separated, so are the // TypedArray hierarchies below the two. However, the TypedArrays have the // same layout (see TypedArrayObject.h), so there is little code duplication. -typedef Vector<ArrayBufferObject *, 0, SystemAllocPolicy> ArrayBufferVector; - class ArrayBufferObjectMaybeShared; uint32_t AnyArrayBufferByteLength(const ArrayBufferObjectMaybeShared *buf); uint8_t *AnyArrayBufferDataPointer(const ArrayBufferObjectMaybeShared *buf); ArrayBufferObjectMaybeShared &AsAnyArrayBuffer(HandleValue val); class ArrayBufferObjectMaybeShared : public JSObject { @@ -92,17 +90,17 @@ class ArrayBufferObjectMaybeShared : pub class ArrayBufferObject : public ArrayBufferObjectMaybeShared { static bool byteLengthGetterImpl(JSContext *cx, CallArgs args); static bool fun_slice_impl(JSContext *cx, CallArgs args); public: static const uint8_t DATA_SLOT = 0; static const uint8_t BYTE_LENGTH_SLOT = 1; - static const uint8_t VIEW_LIST_SLOT = 2; + static const uint8_t FIRST_VIEW_SLOT = 2; static const uint8_t FLAGS_SLOT = 3; static const uint8_t RESERVED_SLOTS = 4; static const size_t ARRAY_BUFFER_ALIGNMENT = 8; public: @@ -114,26 +112,23 @@ class ArrayBufferObject : public ArrayBu }; protected: enum ArrayBufferFlags { // The flags also store the BufferKind BUFFER_KIND_MASK = BufferKind::KIND_MASK, - NEUTERED_BUFFER = 0x8, - - // In the gcLiveArrayBuffers list. - IN_LIVE_LIST = 0x10, + NEUTERED_BUFFER = 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. - OWNS_DATA = 0x20, + OWNS_DATA = 0x8, }; public: class BufferContents { uint8_t *data_; BufferKind kind_; @@ -192,26 +187,18 @@ class ArrayBufferObject : public ArrayBu 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 obj_trace(JSTracer *trc, JSObject *obj); - - static void sweep(JSCompartment *rt); - static void objectMoved(JSObject *obj, const JSObject *old); - static void resetArrayBufferList(JSCompartment *rt); - static bool saveArrayBufferList(JSCompartment *c, ArrayBufferVector &vector); - static void restoreArrayBufferLists(ArrayBufferVector &vector); - static BufferContents stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer); bool hasStealableContents() const { // Inline elements strictly adhere to the corresponding buffer. if (!ownsData()) return false; // asm.js buffer contents are transferred by copying, just like inline @@ -225,32 +212,47 @@ class ArrayBufferObject : public ArrayBu // allocate new ones based on the current byteLength, which is 0 for a // neutered array -- not the original byteLength. return !isNeutered(); } static void addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo *info); - void addView(ArrayBufferViewObject *view); + // 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. + ArrayBufferViewObject *firstView(); + + bool addView(JSContext *cx, ArrayBufferViewObject *view); void setNewOwnedData(FreeOp* fop, BufferContents newContents); void changeContents(JSContext *cx, BufferContents newContents); /* * Ensure data is not stored inline in the object. Used when handing back a * GC-safe pointer. */ static bool ensureNonInline(JSContext *cx, Handle<ArrayBufferObject*> buffer); bool canNeuter(JSContext *cx); /* Neuter this buffer and all its views. */ static void neuter(JSContext *cx, Handle<ArrayBufferObject*> buffer, BufferContents newContents); + private: + void neuterView(JSContext *cx, ArrayBufferViewObject *view, + BufferContents newContents); + void changeViewContents(JSContext *cx, ArrayBufferViewObject *view, + uint8_t *oldDataPointer, BufferContents newContents); + void setFirstView(ArrayBufferViewObject *view); + + public: uint8_t *dataPointer() const; size_t byteLength() const; BufferContents contents() const { return BufferContents(dataPointer(), bufferKind()); } void releaseData(FreeOp *fop); @@ -286,41 +288,32 @@ class ArrayBufferObject : public ArrayBu enum OwnsState { DoesntOwnData = 0, OwnsData = 1, }; void setDataPointer(BufferContents contents, OwnsState ownsState); void setByteLength(size_t length); - ArrayBufferViewObject *viewList() const; - void setViewList(ArrayBufferViewObject *viewsHead); - void setViewListNoBarrier(ArrayBufferViewObject *viewsHead); - uint32_t flags() const; void setFlags(uint32_t flags); - bool inLiveList() const { return flags() & IN_LIVE_LIST; } - void setInLiveList(bool value) { - setFlags(value ? (flags() | IN_LIVE_LIST) : (flags() & ~IN_LIVE_LIST)); - } - bool ownsData() const { return flags() & OWNS_DATA; } void setOwnsData(OwnsState owns) { setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA)); } void setIsAsmJSArrayBuffer() { setFlags(flags() | ASMJS_BUFFER); } void setIsMappedArrayBuffer() { setFlags(flags() | MAPPED_BUFFER); } void setIsNeutered() { setFlags(flags() | NEUTERED_BUFFER); } void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) { setByteLength(byteLength); setFlags(0); - setViewListNoBarrier(nullptr); + setFirstView(nullptr); setDataPointer(contents, ownsState); } void releaseAsmJSArray(FreeOp *fop); void releaseAsmJSArrayNoSignals(FreeOp *fop); void releaseMappedArray(); }; @@ -337,32 +330,21 @@ class ArrayBufferViewObject : public JSO static const size_t BYTEOFFSET_SLOT = JS_BUFVIEW_SLOT_BYTEOFFSET; /* Byte length of view */ static const size_t LENGTH_SLOT = JS_BUFVIEW_SLOT_LENGTH; /* Underlying ArrayBufferObject */ static const size_t BUFFER_SLOT = JS_BUFVIEW_SLOT_OWNER; - /* ArrayBufferObjects point to a linked list of views, chained through this slot */ - static const size_t NEXT_VIEW_SLOT = JS_BUFVIEW_SLOT_NEXT_VIEW; - public: static ArrayBufferObject *bufferObject(JSContext *cx, Handle<ArrayBufferViewObject *> obj); - ArrayBufferViewObject *nextView() const { - return static_cast<ArrayBufferViewObject*>(getFixedSlot(NEXT_VIEW_SLOT).toPrivate()); - } - - inline void setNextView(ArrayBufferViewObject *view); - void neuter(void *newData); - static void trace(JSTracer *trc, JSObject *obj); - uint8_t *dataPointer() { return static_cast<uint8_t *>(getPrivate()); } }; bool ToClampedIndex(JSContext *cx, HandleValue v, uint32_t length, uint32_t *out); @@ -395,23 +377,16 @@ InitArrayBufferViewDataPointer(ArrayBuff * Tests for ArrayBufferObject, like obj->is<ArrayBufferObject>(). */ bool IsArrayBuffer(HandleValue v); bool IsArrayBuffer(HandleObject obj); bool IsArrayBuffer(JSObject *obj); ArrayBufferObject &AsArrayBuffer(HandleObject obj); ArrayBufferObject &AsArrayBuffer(JSObject *obj); -inline void -ArrayBufferViewObject::setNextView(ArrayBufferViewObject *view) -{ - setFixedSlot(NEXT_VIEW_SLOT, PrivateValue(view)); - PostBarrierTypedArrayObject(this); -} - extern uint32_t JS_FASTCALL ClampDoubleToUint8(const double x); struct uint8_clamped { uint8_t val; uint8_clamped() { } uint8_clamped(const uint8_clamped& other) : val(other.val) { } @@ -488,11 +463,65 @@ template<typename T> inline bool TypeIsF template<> inline bool TypeIsFloatingPoint<float>() { return true; } template<> inline bool TypeIsFloatingPoint<double>() { return true; } template<typename T> inline bool TypeIsUnsigned() { return false; } template<> inline bool TypeIsUnsigned<uint8_t>() { return true; } template<> inline bool TypeIsUnsigned<uint16_t>() { return true; } template<> inline bool TypeIsUnsigned<uint32_t>() { return true; } +// Per-compartment table that manages the relationship between array buffers +// and the views that use their storage. +class InnerViewTable +{ + public: + typedef Vector<ArrayBufferViewObject *, 1, SystemAllocPolicy> ViewVector; + + friend class ArrayBufferObject; + + private: + typedef HashMap<JSObject *, + ViewVector, + DefaultHasher<JSObject *>, + SystemAllocPolicy> Map; + + // For all objects sharing their storage with some other view, this maps + // the object to the list of such views. All entries in this map are weak. + Map map; + + // List of keys from innerViews where either the source or at least one + // target is in the nursery. + Vector<JSObject *, 0, SystemAllocPolicy> nurseryKeys; + + // Whether nurseryKeys is a complete list. + bool nurseryKeysValid; + + // Sweep an entry during GC, returning whether the entry should be removed. + bool sweepEntry(JSObject **pkey, ViewVector &views); + + bool addView(JSContext *cx, ArrayBufferObject *obj, ArrayBufferViewObject *view); + ViewVector *maybeViewsUnbarriered(ArrayBufferObject *obj); + void removeViews(ArrayBufferObject *obj); + + public: + InnerViewTable() + : nurseryKeysValid(true) + {} + + // Remove references to dead objects in the table and update table entries + // to reflect moved objects. + void sweep(JSRuntime *rt); + void sweepAfterMinorGC(JSRuntime *rt); + + bool needsSweepAfterMinorGC() { + return !nurseryKeys.empty() || !nurseryKeysValid; + } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); +}; + } // namespace js +template <> +bool +JSObject::is<js::ArrayBufferViewObject>() const; + #endif // vm_ArrayBufferObject_h
--- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -331,16 +331,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.crossCompartmentWrappersTable, &cStats.regexpCompartment, &cStats.savedStacksSet); } static void StatsArenaCallback(JSRuntime *rt, void *data, gc::Arena *arena, JSGCTraceKind traceKind, size_t thingSize)
--- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -102,17 +102,18 @@ TypedArrayObject::ensureHasBuffer(JSCont { if (tarray->buffer()) return true; Rooted<ArrayBufferObject *> buffer(cx, ArrayBufferObject::create(cx, tarray->byteLength())); if (!buffer) return false; - buffer->addView(tarray); + if (!buffer->addView(cx, tarray)) + return false; memcpy(buffer->dataPointer(), tarray->viewData(), tarray->byteLength()); InitArrayBufferViewDataPointer(tarray, buffer, 0); tarray->setSlot(BUFFER_SLOT, ObjectValue(*buffer)); return true; } @@ -317,34 +318,35 @@ class TypedArrayObjectTemplate : public } else { void *data = obj->fixedData(FIXED_DATA_START); obj->initPrivate(data); memset(data, 0, len * sizeof(NativeType)); } obj->setSlot(LENGTH_SLOT, Int32Value(len)); obj->setSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset)); - obj->setSlot(NEXT_VIEW_SLOT, PrivateValue(nullptr)); #ifdef DEBUG if (buffer) { uint32_t arrayByteLength = obj->byteLength(); uint32_t arrayByteOffset = obj->byteOffset(); uint32_t bufferByteLength = buffer->byteLength(); JS_ASSERT_IF(!buffer->isNeutered(), buffer->dataPointer() <= obj->viewData()); JS_ASSERT(bufferByteLength - arrayByteOffset >= arrayByteLength); JS_ASSERT(arrayByteOffset <= bufferByteLength); } // Verify that the private slot is at the expected place JS_ASSERT(obj->numFixedSlots() == DATA_SLOT); #endif - if (buffer) - buffer->addView(obj); + if (buffer) { + if (!buffer->addView(cx, obj)) + return nullptr; + } return obj; } static JSObject * makeInstance(JSContext *cx, Handle<ArrayBufferObject *> bufobj, uint32_t byteOffset, uint32_t len) { RootedObject nullproto(cx, nullptr); @@ -897,24 +899,24 @@ DataViewObject::create(JSContext *cx, ui return nullptr; } } DataViewObject &dvobj = obj->as<DataViewObject>(); dvobj.setFixedSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset)); dvobj.setFixedSlot(LENGTH_SLOT, Int32Value(byteLength)); dvobj.setFixedSlot(BUFFER_SLOT, ObjectValue(*arrayBuffer)); - dvobj.setFixedSlot(NEXT_VIEW_SLOT, PrivateValue(nullptr)); InitArrayBufferViewDataPointer(&dvobj, arrayBuffer, byteOffset); JS_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength()); // Verify that the private slot is at the expected place JS_ASSERT(dvobj.numFixedSlots() == DATA_SLOT); - arrayBuffer->addView(&dvobj); + if (!arrayBuffer->addView(cx, &dvobj)) + return nullptr; return &dvobj; } bool DataViewObject::construct(JSContext *cx, JSObject *bufobj, const CallArgs &args, HandleObject proto) { if (!IsArrayBuffer(bufobj)) { @@ -1742,17 +1744,17 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Flo JS_StrictPropertyStub, /* setProperty */ \ JS_EnumerateStub, \ JS_ResolveStub, \ JS_ConvertStub, \ nullptr, /* finalize */ \ nullptr, /* call */ \ nullptr, /* hasInstance */ \ nullptr, /* construct */ \ - ArrayBufferViewObject::trace, /* trace */ \ + nullptr, /* trace */ \ TYPED_ARRAY_CLASS_SPEC(_typedArray), \ { \ nullptr, /* outerObject */ \ nullptr, /* innerObject */ \ nullptr, /* iteratorObject */ \ false, /* isWrappedNative */ \ nullptr, /* weakmapKeyDelegateOp */ \ TypedArrayObject::ObjectMoved \ @@ -1924,17 +1926,17 @@ const Class DataViewObject::class_ = { JS_StrictPropertyStub, /* setProperty */ JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ - ArrayBufferViewObject::trace, /* trace */ + nullptr, /* trace */ }; const JSFunctionSpec DataViewObject::jsfuncs[] = { JS_FN("getInt8", DataViewObject::fun_getInt8, 1,0), JS_FN("getUint8", DataViewObject::fun_getUint8, 1,0), JS_FN("getInt16", DataViewObject::fun_getInt16, 2,0), JS_FN("getUint16", DataViewObject::fun_getUint16, 2,0), JS_FN("getInt32", DataViewObject::fun_getInt32, 2,0),
--- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -406,16 +406,9 @@ ClampIntForUint8Array(int32_t x) template <> inline bool JSObject::is<js::TypedArrayObject>() const { return js::IsTypedArrayClass(getClass()); } -template <> -inline bool -JSObject::is<js::ArrayBufferViewObject>() const -{ - return is<js::DataViewObject>() || is<js::TypedArrayObject>(); -} - #endif /* vm_TypedArrayObject_h */
--- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2224,16 +2224,20 @@ ReportCompartmentStats(const JS::Compart ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-object"), cStats.compartmentObject, "The JSCompartment object itself."); 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("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.");