From b1eef6cb11b7af5a69b6208c7c1e61d13b8564ac Mon Sep 17 00:00:00 2001 From: Terrence Cole Date: Thu, 26 Feb 2015 14:15:26 -0800 Subject: [PATCH] Bug 1147180 - Introduce a new, strongly-typed tracing path; r=jonco, r=sfink --- js/public/TracingAPI.h | 4 +- js/src/gc/Marking.cpp | 412 ++++++++++++++++++++++++++++++++++++++--- js/src/gc/Marking.h | 22 +++ 3 files changed, 409 insertions(+), 29 deletions(-) diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index 6aaf5f66544..97f605ef788 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -119,7 +119,7 @@ class JS_PUBLIC_API(JSTracer) } void setTracingName(const char* name) { - setTracingDetails(nullptr, (void*)name, size_t(-1)); + setTracingDetails(nullptr, (void*)name, InvalidIndex); } // Remove the currently set tracing details. @@ -128,6 +128,8 @@ class JS_PUBLIC_API(JSTracer) debugPrintArg_ = nullptr; } + const static size_t InvalidIndex = size_t(-1); + // Return true if tracing details are currently set. bool hasTracingDetails() const; diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 07797cc66f9..f18a8eb08de 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -7,6 +7,8 @@ #include "gc/Marking.h" #include "mozilla/DebugOnly.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/TypeTraits.h" #include "jsprf.h" @@ -31,6 +33,9 @@ using namespace js; using namespace js::gc; using mozilla::DebugOnly; +using mozilla::IsBaseOf; +using mozilla::IsSame; +using mozilla::MakeRange; void * const js::NullPtr::constNullValue = nullptr; @@ -147,24 +152,33 @@ AsGCMarker(JSTracer* trc) return static_cast(trc); } -template bool ThingIsPermanentAtom(T* thing) { return false; } -template <> bool ThingIsPermanentAtom(JSString* str) { return str->isPermanentAtom(); } -template <> bool ThingIsPermanentAtom(JSFlatString* str) { return str->isPermanentAtom(); } -template <> bool ThingIsPermanentAtom(JSLinearString* str) { return str->isPermanentAtom(); } -template <> bool ThingIsPermanentAtom(JSAtom* atom) { return atom->isPermanent(); } -template <> bool ThingIsPermanentAtom(PropertyName* name) { return name->isPermanent(); } -template <> bool ThingIsPermanentAtom(JS::Symbol* sym) { return sym->isWellKnownSymbol(); } +template bool ThingIsPermanentAtomOrWellKnownSymbol(T* thing) { return false; } +template <> bool ThingIsPermanentAtomOrWellKnownSymbol(JSString* str) { + return str->isPermanentAtom(); +} +template <> bool ThingIsPermanentAtomOrWellKnownSymbol(JSFlatString* str) { + return str->isPermanentAtom(); +} +template <> bool ThingIsPermanentAtomOrWellKnownSymbol(JSLinearString* str) { + return str->isPermanentAtom(); +} +template <> bool ThingIsPermanentAtomOrWellKnownSymbol(JSAtom* atom) { + return atom->isPermanent(); +} +template <> bool ThingIsPermanentAtomOrWellKnownSymbol(PropertyName* name) { + return name->isPermanent(); +} +template <> bool ThingIsPermanentAtomOrWellKnownSymbol(JS::Symbol* sym) { + return sym->isWellKnownSymbol(); +} template static inline void -CheckMarkedThing(JSTracer* trc, T** thingp) +CheckMarkedThing(JSTracer* trc, T thing) { #ifdef DEBUG MOZ_ASSERT(trc); - MOZ_ASSERT(thingp); - - T* thing = *thingp; - MOZ_ASSERT(*thingp); + MOZ_ASSERT(thing); thing = MaybeForwarded(thing); @@ -173,13 +187,13 @@ CheckMarkedThing(JSTracer* trc, T** thingp) return; MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc) && !Nursery::IsMinorCollectionTracer(trc), - !IsForwarded(*thingp)); + !IsForwarded(thing)); /* * Permanent atoms are not associated with this runtime, but will be ignored * during marking. */ - if (ThingIsPermanentAtom(thing)) + if (ThingIsPermanentAtomOrWellKnownSymbol(thing)) return; Zone* zone = thing->zoneFromAnyThread(); @@ -189,10 +203,10 @@ CheckMarkedThing(JSTracer* trc, T** thingp) MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc), CurrentThreadCanAccessRuntime(rt)); MOZ_ASSERT(zone->runtimeFromAnyThread() == trc->runtime()); - MOZ_ASSERT(trc->hasTracingDetails()); MOZ_ASSERT(thing->isAligned()); - MOZ_ASSERT(MapTypeToTraceKind::kind == GetGCThingTraceKind(thing)); + MOZ_ASSERT(MapTypeToTraceKind::Type>::kind == + GetGCThingTraceKind(thing)); /* * Do not check IsMarkingTracer directly -- it should only be used in paths @@ -226,6 +240,37 @@ CheckMarkedThing(JSTracer* trc, T** thingp) #endif } +template<> +void +CheckMarkedThing(JSTracer* trc, Value val) +{ +#ifdef DEBUG + if (val.isString()) + CheckMarkedThing(trc, val.toString()); + else if (val.isObject()) + CheckMarkedThing(trc, &val.toObject()); + else if (val.isSymbol()) + CheckMarkedThing(trc, val.toSymbol()); +#endif +} + +template <> +void +CheckMarkedThing(JSTracer* trc, jsid id) +{ +#ifdef DEBUG + if (JSID_IS_STRING(id)) + CheckMarkedThing(trc, JSID_TO_STRING(id)); + else if (JSID_IS_SYMBOL(id)) + CheckMarkedThing(trc, JSID_TO_SYMBOL(id)); +#endif +} + +#define JS_ROOT_MARKING_ASSERT(trc) \ + MOZ_ASSERT_IF(trc->isMarkingTracer(), \ + trc->runtime()->gc.state() == NO_INCREMENTAL || \ + trc->runtime()->gc.state() == MARK_ROOTS); + /* * We only set the maybeAlive flag for objects and scripts. It's assumed that, * if a compartment is alive, then it will have at least some live object or @@ -260,12 +305,328 @@ SetMaybeAliveFlag(JSScript* thing) thing->compartment()->maybeAlive = true; } +#define FOR_EACH_GC_LAYOUT(D) \ + D(Object, JSObject) \ + D(String, JSString) \ + D(Symbol, JS::Symbol) \ + D(Script, JSScript) \ + D(Shape, js::Shape) \ + D(BaseShape, js::BaseShape) \ + D(JitCode, js::jit::JitCode) \ + D(LazyScript, js::LazyScript) \ + D(ObjectGroup, js::ObjectGroup) + +// A C++ version of JSGCTraceKind +enum class TraceKind { +#define NAMES(name, _) name, +FOR_EACH_GC_LAYOUT(NAMES) +#undef NAMES +}; + +#define FOR_EACH_GC_POINTER_TYPE(D) \ + D(BaseShape*) \ + D(UnownedBaseShape*) \ + D(jit::JitCode*) \ + D(NativeObject*) \ + D(ArrayObject*) \ + D(ArgumentsObject*) \ + D(ArrayBufferObject*) \ + D(ArrayBufferObjectMaybeShared*) \ + D(ArrayBufferViewObject*) \ + D(DebugScopeObject*) \ + D(GlobalObject*) \ + D(JSObject*) \ + D(JSFunction*) \ + D(NestedScopeObject*) \ + D(PlainObject*) \ + D(SavedFrame*) \ + D(ScopeObject*) \ + D(SharedArrayBufferObject*) \ + D(SharedTypedArrayObject*) \ + D(JSScript*) \ + D(LazyScript*) \ + D(Shape*) \ + D(JSAtom*) \ + D(JSString*) \ + D(JSFlatString*) \ + D(JSLinearString*) \ + D(PropertyName*) \ + D(JS::Symbol*) \ + D(js::ObjectGroup*) \ + D(Value) \ + D(jsid) + +// The second parameter to BaseGCType is derived automatically based on T. The +// relation here is that for any T, the TraceKind will automatically, +// statically select the correct Cell layout for marking. Below, we instantiate +// each override with a declaration of the most derived layout type. +// +// Usage: +// BaseGCType::type +// +// Examples: +// BaseGCType::type => JSObject +// BaseGCType::type => BaseShape +// etc. +template ::value ? TraceKind::Object + : IsBaseOf::value ? TraceKind::String + : IsBaseOf::value ? TraceKind::Symbol + : IsBaseOf::value ? TraceKind::Script + : IsBaseOf::value ? TraceKind::Shape + : IsBaseOf::value ? TraceKind::BaseShape + : IsBaseOf::value ? TraceKind::JitCode + : IsBaseOf::value ? TraceKind::LazyScript + : TraceKind::ObjectGroup> +struct BaseGCType; +#define IMPL_BASE_GC_TYPE(name, type_) \ + template struct BaseGCType { typedef type_ type; }; +FOR_EACH_GC_LAYOUT(IMPL_BASE_GC_TYPE); +#undef IMPL_BASE_GC_TYPE + +// Our barrier templates are parameterized on the pointer types so that we can +// share the definitions with Value and jsid. Thus, we need to strip the +// pointer before sending the type to BaseGCType and re-add it on the other +// side. As such: +template struct PtrBaseGCType {}; +template <> struct PtrBaseGCType { typedef Value type; }; +template <> struct PtrBaseGCType { typedef jsid type; }; +template struct PtrBaseGCType { typedef typename BaseGCType::type* type; }; + +template void DispatchToTracer(JSTracer* trc, T* thingp, const char* name, size_t i); +template void DoTracing(JS::CallbackTracer* trc, T* thingp, const char* name, size_t i); +template void DoMarking(GCMarker* gcmarker, T thing); + +template +void +js::TraceEdge(JSTracer* trc, BarrieredBase* thingp, const char* name) +{ + auto layout = reinterpret_cast::type*>(thingp->unsafeGet()); + DispatchToTracer(trc, layout, name, JSTracer::InvalidIndex); +} + +template +void +js::TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp, const char* name) +{ + auto layout = reinterpret_cast::type*>(thingp); + DispatchToTracer(trc, layout, name, JSTracer::InvalidIndex); +} + +template +void +js::TraceRoot(JSTracer* trc, T* thingp, const char* name) +{ + JS_ROOT_MARKING_ASSERT(trc); + auto layout = reinterpret_cast::type*>(thingp); + DispatchToTracer(trc, layout, name, JSTracer::InvalidIndex); +} + +template +void +js::TraceRange(JSTracer* trc, size_t len, BarrieredBase* thingp, const char* name) +{ + for (auto i : MakeRange(len)) { + auto layout = reinterpret_cast::type*>(&thingp[i]); + DispatchToTracer(trc, layout, name, i); + } +} + +template +void +js::TraceRootRange(JSTracer* trc, size_t len, T* thingp, const char* name) +{ + JS_ROOT_MARKING_ASSERT(trc); + for (auto i : MakeRange(len)) { + auto layout = reinterpret_cast::type*>(&thingp[i]); + DispatchToTracer(trc, layout, name, i); + } +} + +// Instantiate a copy of the Tracing templates for each derived type. +#define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(type) \ + template void js::TraceEdge(JSTracer*, BarrieredBase*, const char*); \ + template void js::TraceManuallyBarrieredEdge(JSTracer*, type*, const char*); \ + template void js::TraceRoot(JSTracer*, type*, const char*); \ + template void js::TraceRange(JSTracer*, size_t, BarrieredBase*, const char*); \ + template void js::TraceRootRange(JSTracer*, size_t, type*, const char*); +FOR_EACH_GC_POINTER_TYPE(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS) +#undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS + +// This method is responsible for dynamic dispatch to the real tracer +// implementation. Consider replacing this choke point with virtual dispatch: +// a sufficiently smart C++ compiler may be able to devirtualize some paths. +template +void +DispatchToTracer(JSTracer* trc, T* thingp, const char* name, size_t i) +{ +#define IS_SAME_TYPE_OR(name, type) mozilla::IsSame::value || + static_assert( + FOR_EACH_GC_LAYOUT(IS_SAME_TYPE_OR) + mozilla::IsSame::value || + mozilla::IsSame::value, + "Only the base cell layout types are allowed into marking/tracing internals"); +#undef IS_SAME_TYPE_OR + CheckMarkedThing(trc, *thingp); + + if (trc->isMarkingTracer()) + return DoMarking(static_cast(trc), *thingp); + return DoTracing(static_cast(trc), thingp, name, i); +} + +template +static inline bool +MustSkipMarking(T thing) +{ + // Don't mark things outside a zone if we are in a per-zone GC. + return !thing->zone()->isGCMarking(); +} + +template <> +bool +MustSkipMarking(JSObject* obj) +{ + // We may mark a Nursery thing outside the context of the + // MinorCollectionTracer because of a pre-barrier. The pre-barrier is not + // needed in this case because we perform a minor collection before each + // incremental slice. + if (IsInsideNursery(obj)) + return true; + + // Don't mark things outside a zone if we are in a per-zone GC. It is + // faster to check our own arena header, which we can do since we know that + // the object is tenured. + return !TenuredCell::fromPointer(obj)->zone()->isGCMarking(); +} + +template <> +bool +MustSkipMarking(JSString* str) +{ + // Don't mark permanent atoms, as they may be associated with another + // runtime. Note that PushMarkStack() also checks this, but we need to not + // run the isGCMarking test from off-main-thread, so have to check it here + // too. + return str->isPermanentAtom() || + !str->zone()->isGCMarking(); +} + +template <> +bool +MustSkipMarking(JS::Symbol* sym) +{ + // As for JSString, don't touch a globally owned well-known symbol from + // off-main-thread. + return sym->isWellKnownSymbol() || + !sym->zone()->isGCMarking(); +} + +template +void +DoMarking(GCMarker* gcmarker, T thing) +{ + // Do per-type marking precondition checks. + if (MustSkipMarking(thing)) + return; + + PushMarkStack(gcmarker, thing); + + // Mark the compartment as live. + SetMaybeAliveFlag(thing); +} + +template <> +void +DoMarking(GCMarker* gcmarker, Value val) +{ + if (val.isString()) + DoMarking(gcmarker, val.toString()); + else if (val.isObject()) + DoMarking(gcmarker, &val.toObject()); + else if (val.isSymbol()) + DoMarking(gcmarker, val.toSymbol()); + else + gcmarker->clearTracingDetails(); +} + +template <> +void +DoMarking(GCMarker* gcmarker, jsid id) +{ + if (JSID_IS_STRING(id)) + DoMarking(gcmarker, JSID_TO_STRING(id)); + else if (JSID_IS_SYMBOL(id)) + DoMarking(gcmarker, JSID_TO_SYMBOL(id)); + else + gcmarker->clearTracingDetails(); +} + +template +void +DoTracing(JS::CallbackTracer* trc, T* thingp, const char* name, size_t i) +{ + JSGCTraceKind kind = MapTypeToTraceKind::Type>::kind; + trc->setTracingIndex(name, i); + trc->invoke((void**)thingp, kind); + trc->unsetTracingLocation(); +} + +template <> +void +DoTracing(JS::CallbackTracer* trc, Value* vp, const char* name, size_t i) +{ + if (vp->isObject()) { + JSObject* prior = &vp->toObject(); + JSObject* obj = prior; + DoTracing(trc, &obj, name, i); + if (obj != prior) + vp->setObjectOrNull(obj); + } else if (vp->isString()) { + JSString* prior = vp->toString(); + JSString* str = prior; + DoTracing(trc, &str, name, i); + if (str != prior) + vp->setString(str); + } else if (vp->isSymbol()) { + JS::Symbol* prior = vp->toSymbol(); + JS::Symbol* sym = prior; + DoTracing(trc, &sym, name, i); + if (sym != prior) + vp->setSymbol(sym); + } else { + /* Unset realLocation manually if we do not call MarkInternal. */ + trc->unsetTracingLocation(); + } +} + +template <> +void +DoTracing(JS::CallbackTracer* trc, jsid* idp, const char* name, size_t i) +{ + if (JSID_IS_STRING(*idp)) { + JSString* prior = JSID_TO_STRING(*idp); + JSString* str = prior; + DoTracing(trc, &str, name, i); + if (str != prior) + *idp = NON_INTEGER_ATOM_TO_JSID(reinterpret_cast(str)); + } else if (JSID_IS_SYMBOL(*idp)) { + JS::Symbol* prior = JSID_TO_SYMBOL(*idp); + JS::Symbol* sym = prior; + DoTracing(trc, &sym, name, i); + if (sym != prior) + *idp = SYMBOL_TO_JSID(sym); + } else { + /* Unset realLocation manually if we do not call MarkInternal. */ + trc->unsetTracingLocation(); + } +} + template static void MarkInternal(JSTracer* trc, T** thingp) { - CheckMarkedThing(trc, thingp); T* thing = *thingp; + CheckMarkedThing(trc, thing); if (trc->isMarkingTracer()) { /* @@ -282,7 +643,7 @@ MarkInternal(JSTracer* trc, T** thingp) * runtime. Note that PushMarkStack() also checks this, but the tests * and maybeAlive write below should only be done on the main thread. */ - if (ThingIsPermanentAtom(thing)) + if (ThingIsPermanentAtomOrWellKnownSymbol(thing)) return; /* @@ -302,11 +663,6 @@ MarkInternal(JSTracer* trc, T** thingp) trc->clearTracingDetails(); } -#define JS_ROOT_MARKING_ASSERT(trc) \ - MOZ_ASSERT_IF(trc->isMarkingTracer(), \ - trc->runtime()->gc.state() == NO_INCREMENTAL || \ - trc->runtime()->gc.state() == MARK_ROOTS); - namespace js { namespace gc { @@ -333,7 +689,7 @@ MarkPermanentAtom(JSTracer* trc, JSAtom* atom, const char* name) MOZ_ASSERT(atom->isPermanent()); - CheckMarkedThing(trc, &atom); + CheckMarkedThing(trc, atom); if (trc->isMarkingTracer()) { // Atoms do not refer to other GC things so don't need to go on the mark stack. @@ -358,7 +714,7 @@ MarkWellKnownSymbol(JSTracer* trc, JS::Symbol* sym) trc->setTracingName("wellKnownSymbols"); MOZ_ASSERT(sym->isWellKnownSymbol()); - CheckMarkedThing(trc, &sym); + CheckMarkedThing(trc, sym); if (trc->isMarkingTracer()) { // Permanent atoms are marked before well-known symbols. MOZ_ASSERT(sym->description()->isMarked()); @@ -417,7 +773,7 @@ template static bool IsMarked(T** thingp) { - MOZ_ASSERT_IF(!ThingIsPermanentAtom(*thingp), + MOZ_ASSERT_IF(!ThingIsPermanentAtomOrWellKnownSymbol(*thingp), CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread())); return IsMarkedFromAnyThread(thingp); } @@ -447,7 +803,7 @@ template static bool IsAboutToBeFinalized(T** thingp) { - MOZ_ASSERT_IF(!ThingIsPermanentAtom(*thingp), + MOZ_ASSERT_IF(!ThingIsPermanentAtomOrWellKnownSymbol(*thingp), CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread())); return IsAboutToBeFinalizedFromAnyThread(thingp); } @@ -463,7 +819,7 @@ IsAboutToBeFinalizedFromAnyThread(T** thingp) JSRuntime* rt = thing->runtimeFromAnyThread(); /* Permanent atoms are never finalized by non-owning runtimes. */ - if (ThingIsPermanentAtom(thing) && !TlsPerThreadData.get()->associatedWith(rt)) + if (ThingIsPermanentAtomOrWellKnownSymbol(thing) && !TlsPerThreadData.get()->associatedWith(rt)) return false; Nursery& nursery = rt->gc.nursery; diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 01b629b7d11..8816fb4f236 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -37,6 +37,28 @@ struct IonScript; struct VMFunction; } +/*** Tracing ***/ + +template +void +TraceEdge(JSTracer* trc, BarrieredBase* thingp, const char* name); + +template +void +TraceRoot(JSTracer* trc, T* thingp, const char* name); + +template +void +TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp, const char* name); + +template +void +TraceRange(JSTracer* trc, size_t len, BarrieredBase* thingp, const char* name); + +template +void +TraceRootRange(JSTracer* trc, size_t len, T* thingp, const char* name); + namespace gc { /*** Object Marking ***/