Bug 1466458 part 2 - Refactor Realm::enterRealmDepth_ to account for Realms entered from JIT code. r=luke

This commit is contained in:
Jan de Mooij 2018-06-07 12:18:43 +02:00
parent a0f210fb97
commit 65d353413b
7 changed files with 84 additions and 36 deletions

View File

@ -4229,7 +4229,7 @@ ShouldCollectZone(Zone* zone, JS::gcreason::Reason reason)
// been collected, then only collect zones containing those compartments.
if (reason == JS::gcreason::COMPARTMENT_REVIVED) {
for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
if (comp->scheduledForDestruction)
if (comp->gcState.scheduledForDestruction)
return true;
}
@ -4298,14 +4298,17 @@ GCRuntime::prepareZonesForCollection(JS::gcreason::Reason reason, bool* isFullOu
bool canAllocateMoreCode = jit::CanLikelyAllocateMoreExecutableMemory();
for (CompartmentsIter c(rt); !c.done(); c.next()) {
c->scheduledForDestruction = false;
c->maybeAlive = false;
c->gcState.scheduledForDestruction = false;
c->gcState.maybeAlive = false;
c->gcState.hasEnteredRealm = false;
for (RealmsInCompartmentIter r(c); !r.done(); r.next()) {
r->unmark();
if (r->shouldTraceGlobal() || !r->zone()->isGCScheduled())
c->maybeAlive = true;
c->gcState.maybeAlive = true;
if (shouldPreserveJITCode(r, currentTime, reason, canAllocateMoreCode))
r->zone()->setPreservingCode(true);
if (r->hasBeenEnteredIgnoringJit())
c->gcState.hasEnteredRealm = true;
}
}
@ -4525,7 +4528,7 @@ GCRuntime::markCompartments()
Vector<JSCompartment*, 0, js::SystemAllocPolicy> workList;
for (CompartmentsIter comp(rt); !comp.done(); comp.next()) {
if (comp->maybeAlive) {
if (comp->gcState.maybeAlive) {
if (!workList.append(comp))
return;
}
@ -4535,8 +4538,8 @@ GCRuntime::markCompartments()
JSCompartment* comp = workList.popCopy();
for (JSCompartment::NonStringWrapperEnum e(comp); !e.empty(); e.popFront()) {
JSCompartment* dest = e.front().mutableKey().compartment();
if (dest && !dest->maybeAlive) {
dest->maybeAlive = true;
if (dest && !dest->gcState.maybeAlive) {
dest->gcState.maybeAlive = true;
if (!workList.append(dest))
return;
}
@ -4546,9 +4549,9 @@ GCRuntime::markCompartments()
/* Set scheduleForDestruction based on maybeAlive. */
for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) {
MOZ_ASSERT(!comp->scheduledForDestruction);
if (!comp->maybeAlive && !comp->zone()->isAtomsZone())
comp->scheduledForDestruction = true;
MOZ_ASSERT(!comp->gcState.scheduledForDestruction);
if (!comp->gcState.maybeAlive && !comp->zone()->isAtomsZone())
comp->gcState.scheduledForDestruction = true;
}
}
@ -6940,7 +6943,7 @@ GCRuntime::resetIncrementalGC(gc::AbortReason reason, AutoTraceSession& session)
marker.reset();
for (CompartmentsIter c(rt); !c.done(); c.next())
c->scheduledForDestruction = false;
c->gcState.scheduledForDestruction = false;
/* Finish sweeping the current sweep group, then abort. */
abortSweepAfterCurrentGroup = true;
@ -7668,7 +7671,7 @@ GCRuntime::shouldRepeatForDeadZone(JS::gcreason::Reason reason)
return false;
for (CompartmentsIter c(rt); !c.done(); c.next()) {
if (c->scheduledForDestruction)
if (c->gcState.scheduledForDestruction)
return true;
}
@ -8097,7 +8100,7 @@ GCRuntime::mergeRealms(Realm* source, Realm* target)
MOZ_ASSERT(source->creationOptions().mergeable());
MOZ_ASSERT(source->creationOptions().invisibleToDebugger());
MOZ_ASSERT(!source->hasBeenEntered());
MOZ_ASSERT(!source->hasBeenEnteredIgnoringJit());
MOZ_ASSERT(source->zone()->compartments().length() == 1);
JSContext* cx = rt->mainContextFromOwnThread();

View File

@ -3752,7 +3752,7 @@ Debugger::addAllGlobalsAsDebuggees(JSContext* cx, unsigned argc, Value* vp)
for (RealmsInZoneIter r(zone); !r.done(); r.next()) {
if (r == dbg->object->realm() || r->creationOptions().invisibleToDebugger())
continue;
r->compartment()->scheduledForDestruction = false;
r->compartment()->gcState.scheduledForDestruction = false;
GlobalObject* global = r->maybeGlobal();
if (global) {
Rooted<GlobalObject*> rg(cx, global);
@ -4991,7 +4991,7 @@ Debugger::findAllGlobals(JSContext* cx, unsigned argc, Value* vp)
if (r->creationOptions().invisibleToDebugger())
continue;
r->compartment()->scheduledForDestruction = false;
r->compartment()->gcState.scheduledForDestruction = false;
GlobalObject* global = r->maybeGlobal();

View File

@ -75,6 +75,8 @@ Realm::Realm(JSCompartment* comp, const JS::RealmOptions& options)
Realm::~Realm()
{
MOZ_ASSERT(!hasBeenEnteredIgnoringJit());
// Write the code coverage information in a file.
JSRuntime* rt = runtimeFromMainThread();
if (rt->lcovOutput().isEnabled())

View File

@ -573,12 +573,23 @@ struct JSCompartment
void* data = nullptr;
// These flags help us to discover if a compartment that shouldn't be alive
// manages to outlive a GC. Note that these flags have to be on the
// compartment, not the realm, because same-compartment realms can have
// cross-realm pointers without wrappers.
bool scheduledForDestruction = false;
bool maybeAlive = true;
// Fields set and used by the GC. Be careful, may be stale after we return
// to the mutator.
struct {
// These flags help us to discover if a compartment that shouldn't be
// alive manages to outlive a GC. Note that these flags have to be on
// the compartment, not the realm, because same-compartment realms can
// have cross-realm pointers without wrappers.
bool scheduledForDestruction = false;
bool maybeAlive = true;
// During GC, we may set this to |true| if we entered a realm in this
// compartment. Note that (without a stack walk) we don't know exactly
// *which* realms, because Realm::enterRealmDepthIgnoringJit_ does not
// account for cross-Realm calls in JIT code updating cx->realm_. See
// also the enterRealmDepthIgnoringJit_ comment.
bool hasEnteredRealm = false;
} gcState;
JS::Zone* zone() { return zone_; }
const JS::Zone* zone() const { return zone_; }
@ -815,7 +826,21 @@ class JS::Realm : public JS::shadow::Realm
js::ReadBarriered<js::ArgumentsObject*> unmappedArgumentsTemplate_ { nullptr };
js::ReadBarriered<js::NativeObject*> iterResultTemplate_ { nullptr };
unsigned enterRealmDepth_ = 0;
// There are two ways to enter a realm:
//
// (1) AutoRealm (and JSAutoRealm, JS::EnterRealm)
// (2) When calling a cross-realm (but same-compartment) function in JIT
// code.
//
// This field only accounts for (1), to keep the JIT code as simple as
// possible.
//
// An important invariant is that the JIT can only switch to a different
// realm within the same compartment, so whenever that happens there must
// always be a same-compartment realm with enterRealmDepthIgnoringJit_ > 0.
// This lets us set JSCompartment::hasEnteredRealm without walking the
// stack.
unsigned enterRealmDepthIgnoringJit_ = 0;
enum {
IsDebuggee = 1 << 0,
@ -1019,16 +1044,20 @@ class JS::Realm : public JS::shadow::Realm
}
void enter() {
enterRealmDepth_++;
enterRealmDepthIgnoringJit_++;
}
void leave() {
enterRealmDepth_--;
MOZ_ASSERT(enterRealmDepthIgnoringJit_ > 0);
enterRealmDepthIgnoringJit_--;
}
bool hasBeenEntered() const {
return enterRealmDepth_ > 0;
bool hasBeenEnteredIgnoringJit() const {
return enterRealmDepthIgnoringJit_ > 0;
}
bool shouldTraceGlobal() const {
return hasBeenEntered();
// If we entered this realm in JIT code, there must be a script and
// function on the stack for this realm, so the global will definitely
// be traced and it's safe to return false here.
return hasBeenEnteredIgnoringJit();
}
bool hasAllocationMetadataBuilder() const {
@ -1305,8 +1334,18 @@ namespace js {
// scheduledForDestruction will be set on the compartment, which will cause
// some extra GC activity to try to free the compartment.
template<typename T> inline void SetMaybeAliveFlag(T* thing) {}
template<> inline void SetMaybeAliveFlag(JSObject* thing) {thing->compartment()->maybeAlive = true;}
template<> inline void SetMaybeAliveFlag(JSScript* thing) {thing->compartment()->maybeAlive = true;}
template<> inline void
SetMaybeAliveFlag(JSObject* thing)
{
thing->compartment()->gcState.maybeAlive = true;
}
template<> inline void
SetMaybeAliveFlag(JSScript* thing)
{
thing->compartment()->gcState.maybeAlive = true;
}
} // namespace js

View File

@ -517,8 +517,8 @@ JSContext::setRealm(JS::Realm* realm)
{
// Both the current and the new realm should be properly marked as
// entered at this point.
MOZ_ASSERT_IF(realm_, realm_->hasBeenEntered());
MOZ_ASSERT_IF(realm, realm->hasBeenEntered());
MOZ_ASSERT_IF(realm_, realm_->hasBeenEnteredIgnoringJit());
MOZ_ASSERT_IF(realm, realm->hasBeenEnteredIgnoringJit());
// This thread must have exclusive access to the zone.
MOZ_ASSERT_IF(realm, CurrentThreadCanAccessZone(realm->zone()));

View File

@ -1679,10 +1679,14 @@ JSFunction::maybeRelazify(JSRuntime* rt)
if (!hasScript() || !u.scripted.s.script_)
return;
// Don't relazify functions in realms that are active.
// Don't relazify functions in compartments that are active.
Realm* realm = this->realm();
if (realm->hasBeenEntered() && !rt->allowRelazificationForTesting)
return;
if (!rt->allowRelazificationForTesting) {
if (realm->compartment()->gcState.hasEnteredRealm)
return;
MOZ_ASSERT(!realm->hasBeenEnteredIgnoringJit());
}
// The caller should have checked we're not in the self-hosting zone (it's
// shared with worker runtimes so relazifying functions in it will race).

View File

@ -237,7 +237,7 @@ AutoStopwatch::AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IM
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
JSCompartment* compartment = cx_->compartment();
if (MOZ_UNLIKELY(compartment->scheduledForDestruction))
if (MOZ_UNLIKELY(compartment->gcState.scheduledForDestruction))
return;
JSRuntime* runtime = cx_->runtime();
@ -276,7 +276,7 @@ AutoStopwatch::~AutoStopwatch()
}
JSCompartment* compartment = cx_->compartment();
if (MOZ_UNLIKELY(compartment->scheduledForDestruction))
if (MOZ_UNLIKELY(compartment->gcState.scheduledForDestruction))
return;
JSRuntime* runtime = cx_->runtime();