gecko-dev/js/src/gc/Verifier.cpp

413 строки
11 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef MOZ_VALGRIND
# include <valgrind/memcheck.h>
#endif
#include "jscntxt.h"
#include "jsgc.h"
#include "jsprf.h"
#include "gc/GCInternals.h"
#include "gc/Zone.h"
#include "js/GCAPI.h"
#include "js/HashTable.h"
#include "jscntxtinlines.h"
#include "jsgcinlines.h"
using namespace js;
using namespace js::gc;
#ifdef JS_GC_ZEAL
/*
* Write barrier verification
*
* The next few functions are for write barrier verification.
*
* The VerifyBarriers function is a shorthand. It checks if a verification phase
* is currently running. If not, it starts one. Otherwise, it ends the current
* phase and starts a new one.
*
* The user can adjust the frequency of verifications, which causes
* VerifyBarriers to be a no-op all but one out of N calls. However, if the
* |always| parameter is true, it starts a new phase no matter what.
*
* Pre-Barrier Verifier:
* When StartVerifyBarriers is called, a snapshot is taken of all objects in
* the GC heap and saved in an explicit graph data structure. Later,
* EndVerifyBarriers traverses the heap again. Any pointer values that were in
* the snapshot and are no longer found must be marked; otherwise an assertion
* triggers. Note that we must not GC in between starting and finishing a
* verification phase.
*/
struct EdgeValue
{
void* thing;
JS::TraceKind kind;
const char* label;
};
struct VerifyNode
{
void* thing;
JS::TraceKind kind;
uint32_t count;
EdgeValue edges[1];
};
typedef HashMap<void*, VerifyNode*, DefaultHasher<void*>, SystemAllocPolicy> NodeMap;
/*
* The verifier data structures are simple. The entire graph is stored in a
* single block of memory. At the beginning is a VerifyNode for the root
* node. It is followed by a sequence of EdgeValues--the exact number is given
* in the node. After the edges come more nodes and their edges.
*
* The edgeptr and term fields are used to allocate out of the block of memory
* for the graph. If we run out of memory (i.e., if edgeptr goes beyond term),
* we just abandon the verification.
*
* The nodemap field is a hashtable that maps from the address of the GC thing
* to the VerifyNode that represents it.
*/
class js::VerifyPreTracer : public JS::CallbackTracer
{
JS::AutoDisableGenerationalGC noggc;
void trace(void** thingp, JS::TraceKind kind) override;
public:
/* The gcNumber when the verification began. */
uint64_t number;
/* This counts up to gcZealFrequency to decide whether to verify. */
int count;
/* This graph represents the initial GC "snapshot". */
VerifyNode* curnode;
VerifyNode* root;
char* edgeptr;
char* term;
NodeMap nodemap;
explicit VerifyPreTracer(JSRuntime* rt)
: JS::CallbackTracer(rt), noggc(rt), number(rt->gc.gcNumber()), count(0), curnode(nullptr),
root(nullptr), edgeptr(nullptr), term(nullptr)
{}
~VerifyPreTracer() {
js_free(root);
}
};
/*
* This function builds up the heap snapshot by adding edges to the current
* node.
*/
void
VerifyPreTracer::trace(void** thingp, JS::TraceKind kind)
{
MOZ_ASSERT(!IsInsideNursery(*reinterpret_cast<Cell**>(thingp)));
edgeptr += sizeof(EdgeValue);
if (edgeptr >= term) {
edgeptr = term;
return;
}
VerifyNode* node = curnode;
uint32_t i = node->count;
node->edges[i].thing = *thingp;
node->edges[i].kind = kind;
node->edges[i].label = contextName();
node->count++;
}
static VerifyNode*
MakeNode(VerifyPreTracer* trc, void* thing, JS::TraceKind kind)
{
NodeMap::AddPtr p = trc->nodemap.lookupForAdd(thing);
if (!p) {
VerifyNode* node = (VerifyNode*)trc->edgeptr;
trc->edgeptr += sizeof(VerifyNode) - sizeof(EdgeValue);
if (trc->edgeptr >= trc->term) {
trc->edgeptr = trc->term;
return nullptr;
}
node->thing = thing;
node->count = 0;
node->kind = kind;
trc->nodemap.add(p, thing, node);
return node;
}
return nullptr;
}
static VerifyNode*
NextNode(VerifyNode* node)
{
if (node->count == 0)
return (VerifyNode*)((char*)node + sizeof(VerifyNode) - sizeof(EdgeValue));
else
return (VerifyNode*)((char*)node + sizeof(VerifyNode) +
sizeof(EdgeValue)*(node->count - 1));
}
void
gc::GCRuntime::startVerifyPreBarriers()
{
if (verifyPreData || isIncrementalGCInProgress())
return;
evictNursery();
AutoPrepareForTracing prep(rt, WithAtoms);
if (!IsIncrementalGCSafe(rt))
return;
for (auto chunk = allNonEmptyChunks(); !chunk.done(); chunk.next())
chunk->bitmap.clear();
number++;
VerifyPreTracer* trc = js_new<VerifyPreTracer>(rt);
if (!trc)
return;
gcstats::AutoPhase ap(stats, gcstats::PHASE_TRACE_HEAP);
const size_t size = 64 * 1024 * 1024;
trc->root = (VerifyNode*)js_malloc(size);
if (!trc->root)
goto oom;
trc->edgeptr = (char*)trc->root;
trc->term = trc->edgeptr + size;
if (!trc->nodemap.init())
goto oom;
/* Create the root node. */
trc->curnode = MakeNode(trc, nullptr, JS::TraceKind(0));
incrementalState = MARK_ROOTS;
/* Make all the roots be edges emanating from the root node. */
markRuntime(trc);
VerifyNode* node;
node = trc->curnode;
if (trc->edgeptr == trc->term)
goto oom;
/* For each edge, make a node for it if one doesn't already exist. */
while ((char*)node < trc->edgeptr) {
for (uint32_t i = 0; i < node->count; i++) {
EdgeValue& e = node->edges[i];
VerifyNode* child = MakeNode(trc, e.thing, e.kind);
if (child) {
trc->curnode = child;
JS_TraceChildren(trc, e.thing, e.kind);
}
if (trc->edgeptr == trc->term)
goto oom;
}
node = NextNode(node);
}
verifyPreData = trc;
incrementalState = MARK;
marker.start();
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
PurgeJITCaches(zone);
zone->setNeedsIncrementalBarrier(true, Zone::UpdateJit);
zone->arenas.purge();
}
return;
oom:
incrementalState = NO_INCREMENTAL;
js_delete(trc);
verifyPreData = nullptr;
}
static bool
IsMarkedOrAllocated(TenuredCell* cell)
{
return cell->isMarked() || cell->arenaHeader()->allocatedDuringIncremental;
}
struct CheckEdgeTracer : public JS::CallbackTracer {
VerifyNode* node;
explicit CheckEdgeTracer(JSRuntime* rt) : JS::CallbackTracer(rt), node(nullptr) {}
void trace(void** thingp, JS::TraceKind kind) override;
};
static const uint32_t MAX_VERIFIER_EDGES = 1000;
/*
* This function is called by EndVerifyBarriers for every heap edge. If the edge
* already existed in the original snapshot, we "cancel it out" by overwriting
* it with nullptr. EndVerifyBarriers later asserts that the remaining
* non-nullptr edges (i.e., the ones from the original snapshot that must have
* been modified) must point to marked objects.
*/
void
CheckEdgeTracer::trace(void** thingp, JS::TraceKind kind)
{
/* Avoid n^2 behavior. */
if (node->count > MAX_VERIFIER_EDGES)
return;
for (uint32_t i = 0; i < node->count; i++) {
if (node->edges[i].thing == *thingp) {
MOZ_ASSERT(node->edges[i].kind == kind);
node->edges[i].thing = nullptr;
return;
}
}
}
static void
AssertMarkedOrAllocated(const EdgeValue& edge)
{
if (!edge.thing || IsMarkedOrAllocated(TenuredCell::fromPointer(edge.thing)))
return;
// Permanent atoms and well-known symbols aren't marked during graph traversal.
if (edge.kind == JS::TraceKind::String && static_cast<JSString*>(edge.thing)->isPermanentAtom())
return;
if (edge.kind == JS::TraceKind::Symbol && static_cast<JS::Symbol*>(edge.thing)->isWellKnownSymbol())
return;
char msgbuf[1024];
JS_snprintf(msgbuf, sizeof(msgbuf), "[barrier verifier] Unmarked edge: %s", edge.label);
MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__);
MOZ_CRASH();
}
bool
gc::GCRuntime::endVerifyPreBarriers()
{
VerifyPreTracer* trc = verifyPreData;
if (!trc)
return false;
MOZ_ASSERT(!JS::IsGenerationalGCEnabled(rt));
AutoPrepareForTracing prep(rt, SkipAtoms);
bool compartmentCreated = false;
/* We need to disable barriers before tracing, which may invoke barriers. */
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
if (!zone->needsIncrementalBarrier())
compartmentCreated = true;
zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit);
PurgeJITCaches(zone);
}
/*
* We need to bump gcNumber so that the methodjit knows that jitcode has
* been discarded.
*/
MOZ_ASSERT(trc->number == number);
number++;
verifyPreData = nullptr;
incrementalState = NO_INCREMENTAL;
if (!compartmentCreated && IsIncrementalGCSafe(rt)) {
CheckEdgeTracer cetrc(rt);
/* Start after the roots. */
VerifyNode* node = NextNode(trc->root);
while ((char*)node < trc->edgeptr) {
cetrc.node = node;
JS_TraceChildren(&cetrc, node->thing, node->kind);
if (node->count <= MAX_VERIFIER_EDGES) {
for (uint32_t i = 0; i < node->count; i++)
AssertMarkedOrAllocated(node->edges[i]);
}
node = NextNode(node);
}
}
marker.reset();
marker.stop();
js_delete(trc);
return true;
}
/*** Barrier Verifier Scheduling ***/
void
gc::GCRuntime::verifyPreBarriers()
{
if (verifyPreData)
endVerifyPreBarriers();
else
startVerifyPreBarriers();
}
void
gc::VerifyBarriers(JSRuntime* rt, VerifierType type)
{
if (type == PreBarrierVerifier)
rt->gc.verifyPreBarriers();
}
void
gc::GCRuntime::maybeVerifyPreBarriers(bool always)
{
if (zealMode != ZealVerifierPreValue)
return;
if (rt->mainThread.suppressGC)
return;
if (verifyPreData) {
if (++verifyPreData->count < zealFrequency && !always)
return;
endVerifyPreBarriers();
}
startVerifyPreBarriers();
}
void
js::gc::MaybeVerifyBarriers(JSContext* cx, bool always)
{
GCRuntime* gc = &cx->runtime()->gc;
gc->maybeVerifyPreBarriers(always);
}
void
js::gc::GCRuntime::finishVerifier()
{
if (verifyPreData) {
js_delete(verifyPreData);
verifyPreData = nullptr;
}
}
#endif /* JS_GC_ZEAL */