Bug 1147180 - Introduce a new, strongly-typed tracing path; r=jonco, r=sfink

This commit is contained in:
Terrence Cole 2015-02-26 14:15:26 -08:00
parent 23e404d42b
commit b1eef6cb11
3 changed files with 409 additions and 29 deletions

View File

@ -119,7 +119,7 @@ class JS_PUBLIC_API(JSTracer)
} }
void setTracingName(const char* name) { void setTracingName(const char* name) {
setTracingDetails(nullptr, (void*)name, size_t(-1)); setTracingDetails(nullptr, (void*)name, InvalidIndex);
} }
// Remove the currently set tracing details. // Remove the currently set tracing details.
@ -128,6 +128,8 @@ class JS_PUBLIC_API(JSTracer)
debugPrintArg_ = nullptr; debugPrintArg_ = nullptr;
} }
const static size_t InvalidIndex = size_t(-1);
// Return true if tracing details are currently set. // Return true if tracing details are currently set.
bool hasTracingDetails() const; bool hasTracingDetails() const;

View File

@ -7,6 +7,8 @@
#include "gc/Marking.h" #include "gc/Marking.h"
#include "mozilla/DebugOnly.h" #include "mozilla/DebugOnly.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/TypeTraits.h"
#include "jsprf.h" #include "jsprf.h"
@ -31,6 +33,9 @@ using namespace js;
using namespace js::gc; using namespace js::gc;
using mozilla::DebugOnly; using mozilla::DebugOnly;
using mozilla::IsBaseOf;
using mozilla::IsSame;
using mozilla::MakeRange;
void * const js::NullPtr::constNullValue = nullptr; void * const js::NullPtr::constNullValue = nullptr;
@ -147,24 +152,33 @@ AsGCMarker(JSTracer* trc)
return static_cast<GCMarker*>(trc); return static_cast<GCMarker*>(trc);
} }
template <typename T> bool ThingIsPermanentAtom(T* thing) { return false; } template <typename T> bool ThingIsPermanentAtomOrWellKnownSymbol(T* thing) { return false; }
template <> bool ThingIsPermanentAtom<JSString>(JSString* str) { return str->isPermanentAtom(); } template <> bool ThingIsPermanentAtomOrWellKnownSymbol<JSString>(JSString* str) {
template <> bool ThingIsPermanentAtom<JSFlatString>(JSFlatString* str) { return str->isPermanentAtom(); } return str->isPermanentAtom();
template <> bool ThingIsPermanentAtom<JSLinearString>(JSLinearString* str) { return str->isPermanentAtom(); } }
template <> bool ThingIsPermanentAtom<JSAtom>(JSAtom* atom) { return atom->isPermanent(); } template <> bool ThingIsPermanentAtomOrWellKnownSymbol<JSFlatString>(JSFlatString* str) {
template <> bool ThingIsPermanentAtom<PropertyName>(PropertyName* name) { return name->isPermanent(); } return str->isPermanentAtom();
template <> bool ThingIsPermanentAtom<JS::Symbol>(JS::Symbol* sym) { return sym->isWellKnownSymbol(); } }
template <> bool ThingIsPermanentAtomOrWellKnownSymbol<JSLinearString>(JSLinearString* str) {
return str->isPermanentAtom();
}
template <> bool ThingIsPermanentAtomOrWellKnownSymbol<JSAtom>(JSAtom* atom) {
return atom->isPermanent();
}
template <> bool ThingIsPermanentAtomOrWellKnownSymbol<PropertyName>(PropertyName* name) {
return name->isPermanent();
}
template <> bool ThingIsPermanentAtomOrWellKnownSymbol<JS::Symbol>(JS::Symbol* sym) {
return sym->isWellKnownSymbol();
}
template<typename T> template<typename T>
static inline void static inline void
CheckMarkedThing(JSTracer* trc, T** thingp) CheckMarkedThing(JSTracer* trc, T thing)
{ {
#ifdef DEBUG #ifdef DEBUG
MOZ_ASSERT(trc); MOZ_ASSERT(trc);
MOZ_ASSERT(thingp); MOZ_ASSERT(thing);
T* thing = *thingp;
MOZ_ASSERT(*thingp);
thing = MaybeForwarded(thing); thing = MaybeForwarded(thing);
@ -173,13 +187,13 @@ CheckMarkedThing(JSTracer* trc, T** thingp)
return; return;
MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc) && !Nursery::IsMinorCollectionTracer(trc), 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 * Permanent atoms are not associated with this runtime, but will be ignored
* during marking. * during marking.
*/ */
if (ThingIsPermanentAtom(thing)) if (ThingIsPermanentAtomOrWellKnownSymbol(thing))
return; return;
Zone* zone = thing->zoneFromAnyThread(); Zone* zone = thing->zoneFromAnyThread();
@ -189,10 +203,10 @@ CheckMarkedThing(JSTracer* trc, T** thingp)
MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc), CurrentThreadCanAccessRuntime(rt)); MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc), CurrentThreadCanAccessRuntime(rt));
MOZ_ASSERT(zone->runtimeFromAnyThread() == trc->runtime()); MOZ_ASSERT(zone->runtimeFromAnyThread() == trc->runtime());
MOZ_ASSERT(trc->hasTracingDetails());
MOZ_ASSERT(thing->isAligned()); MOZ_ASSERT(thing->isAligned());
MOZ_ASSERT(MapTypeToTraceKind<T>::kind == GetGCThingTraceKind(thing)); MOZ_ASSERT(MapTypeToTraceKind<typename mozilla::RemovePointer<T>::Type>::kind ==
GetGCThingTraceKind(thing));
/* /*
* Do not check IsMarkingTracer directly -- it should only be used in paths * Do not check IsMarkingTracer directly -- it should only be used in paths
@ -226,6 +240,37 @@ CheckMarkedThing(JSTracer* trc, T** thingp)
#endif #endif
} }
template<>
void
CheckMarkedThing<Value>(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<jsid>(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, * 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 * 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; 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<T>::type
//
// Examples:
// BaseGCType<JSFunction>::type => JSObject
// BaseGCType<UnownedBaseShape>::type => BaseShape
// etc.
template <typename T,
TraceKind = IsBaseOf<JSObject, T>::value ? TraceKind::Object
: IsBaseOf<JSString, T>::value ? TraceKind::String
: IsBaseOf<JS::Symbol, T>::value ? TraceKind::Symbol
: IsBaseOf<JSScript, T>::value ? TraceKind::Script
: IsBaseOf<Shape, T>::value ? TraceKind::Shape
: IsBaseOf<BaseShape, T>::value ? TraceKind::BaseShape
: IsBaseOf<jit::JitCode, T>::value ? TraceKind::JitCode
: IsBaseOf<LazyScript, T>::value ? TraceKind::LazyScript
: TraceKind::ObjectGroup>
struct BaseGCType;
#define IMPL_BASE_GC_TYPE(name, type_) \
template <typename T> struct BaseGCType<T, TraceKind:: name> { 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 <typename T> struct PtrBaseGCType {};
template <> struct PtrBaseGCType<Value> { typedef Value type; };
template <> struct PtrBaseGCType<jsid> { typedef jsid type; };
template <typename T> struct PtrBaseGCType<T*> { typedef typename BaseGCType<T>::type* type; };
template <typename T> void DispatchToTracer(JSTracer* trc, T* thingp, const char* name, size_t i);
template <typename T> void DoTracing(JS::CallbackTracer* trc, T* thingp, const char* name, size_t i);
template <typename T> void DoMarking(GCMarker* gcmarker, T thing);
template <typename T>
void
js::TraceEdge(JSTracer* trc, BarrieredBase<T>* thingp, const char* name)
{
auto layout = reinterpret_cast<typename PtrBaseGCType<T>::type*>(thingp->unsafeGet());
DispatchToTracer(trc, layout, name, JSTracer::InvalidIndex);
}
template <typename T>
void
js::TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp, const char* name)
{
auto layout = reinterpret_cast<typename PtrBaseGCType<T>::type*>(thingp);
DispatchToTracer(trc, layout, name, JSTracer::InvalidIndex);
}
template <typename T>
void
js::TraceRoot(JSTracer* trc, T* thingp, const char* name)
{
JS_ROOT_MARKING_ASSERT(trc);
auto layout = reinterpret_cast<typename PtrBaseGCType<T>::type*>(thingp);
DispatchToTracer(trc, layout, name, JSTracer::InvalidIndex);
}
template <typename T>
void
js::TraceRange(JSTracer* trc, size_t len, BarrieredBase<T>* thingp, const char* name)
{
for (auto i : MakeRange(len)) {
auto layout = reinterpret_cast<typename PtrBaseGCType<T>::type*>(&thingp[i]);
DispatchToTracer(trc, layout, name, i);
}
}
template <typename T>
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<typename PtrBaseGCType<T>::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<type>(JSTracer*, BarrieredBase<type>*, const char*); \
template void js::TraceManuallyBarrieredEdge<type>(JSTracer*, type*, const char*); \
template void js::TraceRoot<type>(JSTracer*, type*, const char*); \
template void js::TraceRange<type>(JSTracer*, size_t, BarrieredBase<type>*, const char*); \
template void js::TraceRootRange<type>(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 <typename T>
void
DispatchToTracer(JSTracer* trc, T* thingp, const char* name, size_t i)
{
#define IS_SAME_TYPE_OR(name, type) mozilla::IsSame<type*, T>::value ||
static_assert(
FOR_EACH_GC_LAYOUT(IS_SAME_TYPE_OR)
mozilla::IsSame<T, JS::Value>::value ||
mozilla::IsSame<T, jsid>::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<GCMarker*>(trc), *thingp);
return DoTracing(static_cast<JS::CallbackTracer*>(trc), thingp, name, i);
}
template <typename T>
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*>(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*>(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*>(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 <typename T>
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<Value>(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<jsid>(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 <typename T>
void
DoTracing(JS::CallbackTracer* trc, T* thingp, const char* name, size_t i)
{
JSGCTraceKind kind = MapTypeToTraceKind<typename mozilla::RemovePointer<T>::Type>::kind;
trc->setTracingIndex(name, i);
trc->invoke((void**)thingp, kind);
trc->unsetTracingLocation();
}
template <>
void
DoTracing<Value>(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<jsid>(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<JSAtom*>(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<typename T> template<typename T>
static void static void
MarkInternal(JSTracer* trc, T** thingp) MarkInternal(JSTracer* trc, T** thingp)
{ {
CheckMarkedThing(trc, thingp);
T* thing = *thingp; T* thing = *thingp;
CheckMarkedThing(trc, thing);
if (trc->isMarkingTracer()) { if (trc->isMarkingTracer()) {
/* /*
@ -282,7 +643,7 @@ MarkInternal(JSTracer* trc, T** thingp)
* runtime. Note that PushMarkStack() also checks this, but the tests * runtime. Note that PushMarkStack() also checks this, but the tests
* and maybeAlive write below should only be done on the main thread. * and maybeAlive write below should only be done on the main thread.
*/ */
if (ThingIsPermanentAtom(thing)) if (ThingIsPermanentAtomOrWellKnownSymbol(thing))
return; return;
/* /*
@ -302,11 +663,6 @@ MarkInternal(JSTracer* trc, T** thingp)
trc->clearTracingDetails(); 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 js {
namespace gc { namespace gc {
@ -333,7 +689,7 @@ MarkPermanentAtom(JSTracer* trc, JSAtom* atom, const char* name)
MOZ_ASSERT(atom->isPermanent()); MOZ_ASSERT(atom->isPermanent());
CheckMarkedThing(trc, &atom); CheckMarkedThing(trc, atom);
if (trc->isMarkingTracer()) { if (trc->isMarkingTracer()) {
// Atoms do not refer to other GC things so don't need to go on the mark stack. // 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"); trc->setTracingName("wellKnownSymbols");
MOZ_ASSERT(sym->isWellKnownSymbol()); MOZ_ASSERT(sym->isWellKnownSymbol());
CheckMarkedThing(trc, &sym); CheckMarkedThing(trc, sym);
if (trc->isMarkingTracer()) { if (trc->isMarkingTracer()) {
// Permanent atoms are marked before well-known symbols. // Permanent atoms are marked before well-known symbols.
MOZ_ASSERT(sym->description()->isMarked()); MOZ_ASSERT(sym->description()->isMarked());
@ -417,7 +773,7 @@ template <typename T>
static bool static bool
IsMarked(T** thingp) IsMarked(T** thingp)
{ {
MOZ_ASSERT_IF(!ThingIsPermanentAtom(*thingp), MOZ_ASSERT_IF(!ThingIsPermanentAtomOrWellKnownSymbol(*thingp),
CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread())); CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread()));
return IsMarkedFromAnyThread(thingp); return IsMarkedFromAnyThread(thingp);
} }
@ -447,7 +803,7 @@ template <typename T>
static bool static bool
IsAboutToBeFinalized(T** thingp) IsAboutToBeFinalized(T** thingp)
{ {
MOZ_ASSERT_IF(!ThingIsPermanentAtom(*thingp), MOZ_ASSERT_IF(!ThingIsPermanentAtomOrWellKnownSymbol(*thingp),
CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread())); CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread()));
return IsAboutToBeFinalizedFromAnyThread(thingp); return IsAboutToBeFinalizedFromAnyThread(thingp);
} }
@ -463,7 +819,7 @@ IsAboutToBeFinalizedFromAnyThread(T** thingp)
JSRuntime* rt = thing->runtimeFromAnyThread(); JSRuntime* rt = thing->runtimeFromAnyThread();
/* Permanent atoms are never finalized by non-owning runtimes. */ /* 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; return false;
Nursery& nursery = rt->gc.nursery; Nursery& nursery = rt->gc.nursery;

View File

@ -37,6 +37,28 @@ struct IonScript;
struct VMFunction; struct VMFunction;
} }
/*** Tracing ***/
template <typename T>
void
TraceEdge(JSTracer* trc, BarrieredBase<T>* thingp, const char* name);
template <typename T>
void
TraceRoot(JSTracer* trc, T* thingp, const char* name);
template <typename T>
void
TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp, const char* name);
template <typename T>
void
TraceRange(JSTracer* trc, size_t len, BarrieredBase<T>* thingp, const char* name);
template <typename T>
void
TraceRootRange(JSTracer* trc, size_t len, T* thingp, const char* name);
namespace gc { namespace gc {
/*** Object Marking ***/ /*** Object Marking ***/