mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1061909: Implement 'allocationStack' breakdown for Debugger.Memory.prototype.takeCensus. r=fitzgen
This commit is contained in:
parent
5ceab24634
commit
048f2b5f03
74
js/src/jit-test/tests/debug/Memory-takeCensus-09.js
Normal file
74
js/src/jit-test/tests/debug/Memory-takeCensus-09.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Debugger.Memory.prototype.takeCensus: by: allocationStack breakdown
|
||||||
|
|
||||||
|
var g = newGlobal();
|
||||||
|
var dbg = new Debugger(g);
|
||||||
|
|
||||||
|
g.evaluate(`
|
||||||
|
var log = [];
|
||||||
|
function f() { log.push(allocationMarker()); }
|
||||||
|
function g() { f(); }
|
||||||
|
function h() { f(); }
|
||||||
|
`,
|
||||||
|
{ fileName: "Rockford", lineNumber: 1000 });
|
||||||
|
|
||||||
|
// Create one allocationMarker with tracking turned off,
|
||||||
|
// so it will have no associated stack.
|
||||||
|
g.f();
|
||||||
|
|
||||||
|
dbg.memory.allocationSamplingProbability = 1;
|
||||||
|
dbg.memory.trackingAllocationSites = true;
|
||||||
|
|
||||||
|
for ([f, n] of [[g.f, 20], [g.g, 10], [g.h, 5]])
|
||||||
|
for (let i = 0; i < n; i++)
|
||||||
|
f(); // all allocations of allocationMarker occur with this line as the
|
||||||
|
// oldest stack frame.
|
||||||
|
|
||||||
|
let census = dbg.memory.takeCensus({ breakdown: { by: 'objectClass',
|
||||||
|
then: { by: 'allocationStack',
|
||||||
|
then: { by: 'count',
|
||||||
|
label: 'haz stack'
|
||||||
|
},
|
||||||
|
noStack: { by: 'count',
|
||||||
|
label: 'no haz stack'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let map = census.AllocationMarker;
|
||||||
|
assertEq(map instanceof Map, true);
|
||||||
|
|
||||||
|
// Gather the stacks we are expecting to appear as keys, and
|
||||||
|
// check that there are no unexpected keys.
|
||||||
|
let stacks = { };
|
||||||
|
|
||||||
|
map.forEach((v, k) => {
|
||||||
|
if (k === 'noStack') {
|
||||||
|
// No need to save this key.
|
||||||
|
} else if (k.functionDisplayName === 'f' &&
|
||||||
|
k.parent.functionDisplayName === null) {
|
||||||
|
stacks.f = k;
|
||||||
|
} else if (k.functionDisplayName === 'f' &&
|
||||||
|
k.parent.functionDisplayName === 'g' &&
|
||||||
|
k.parent.parent.functionDisplayName === null) {
|
||||||
|
stacks.fg = k;
|
||||||
|
} else if (k.functionDisplayName === 'f' &&
|
||||||
|
k.parent.functionDisplayName === 'h' &&
|
||||||
|
k.parent.parent.functionDisplayName === null) {
|
||||||
|
stacks.fh = k;
|
||||||
|
} else {
|
||||||
|
assertEq(true, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEq(map.get('noStack').label, 'no haz stack');
|
||||||
|
assertEq(map.get('noStack').count, 1);
|
||||||
|
|
||||||
|
assertEq(map.get(stacks.f).label, 'haz stack');
|
||||||
|
assertEq(map.get(stacks.f).count, 20);
|
||||||
|
|
||||||
|
assertEq(map.get(stacks.fg).label, 'haz stack');
|
||||||
|
assertEq(map.get(stacks.fg).count, 10);
|
||||||
|
|
||||||
|
assertEq(map.get(stacks.fh).label, 'haz stack');
|
||||||
|
assertEq(map.get(stacks.fh).count, 5);
|
@ -4548,7 +4548,6 @@ EntryPoints(JSContext* cx, unsigned argc, Value* vp)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static const JSFunctionSpecWithHelp shell_functions[] = {
|
static const JSFunctionSpecWithHelp shell_functions[] = {
|
||||||
JS_FN_HELP("version", Version, 0, 0,
|
JS_FN_HELP("version", Version, 0, 0,
|
||||||
"version([number])",
|
"version([number])",
|
||||||
|
@ -151,6 +151,7 @@
|
|||||||
macro(NFKC, NFKC, "NFKC") \
|
macro(NFKC, NFKC, "NFKC") \
|
||||||
macro(NFKD, NFKD, "NFKD") \
|
macro(NFKD, NFKD, "NFKD") \
|
||||||
macro(nonincrementalReason, nonincrementalReason, "nonincrementalReason") \
|
macro(nonincrementalReason, nonincrementalReason, "nonincrementalReason") \
|
||||||
|
macro(noStack, noStack, "noStack") \
|
||||||
macro(noSuchMethod, noSuchMethod, "__noSuchMethod__") \
|
macro(noSuchMethod, noSuchMethod, "__noSuchMethod__") \
|
||||||
macro(NumberFormat, NumberFormat, "NumberFormat") \
|
macro(NumberFormat, NumberFormat, "NumberFormat") \
|
||||||
macro(NumberFormatFormatGet, NumberFormatFormatGet, "Intl_NumberFormat_format_get") \
|
macro(NumberFormatFormatGet, NumberFormatFormatGet, "Intl_NumberFormat_format_get") \
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "jsalloc.h"
|
#include "jsalloc.h"
|
||||||
#include "jscompartment.h"
|
#include "jscompartment.h"
|
||||||
|
|
||||||
|
#include "builtin/MapObject.h"
|
||||||
#include "gc/Marking.h"
|
#include "gc/Marking.h"
|
||||||
#include "js/Debug.h"
|
#include "js/Debug.h"
|
||||||
#include "js/TracingAPI.h"
|
#include "js/TracingAPI.h"
|
||||||
@ -976,6 +977,175 @@ class ByUbinodeType : public CountType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// A count type that categorizes nodes by the JS stack under which they were
|
||||||
|
// allocated.
|
||||||
|
class ByAllocationStack : public CountType {
|
||||||
|
typedef HashMap<SavedFrame*, CountBasePtr, DefaultHasher<SavedFrame*>,
|
||||||
|
SystemAllocPolicy> Table;
|
||||||
|
typedef Table::Entry Entry;
|
||||||
|
|
||||||
|
struct Count : public CountBase {
|
||||||
|
// NOTE: You may look up entries in this table by SavedFrame key only
|
||||||
|
// during traversal, NOT ONCE TRAVERSAL IS COMPLETE. Once traversal is
|
||||||
|
// complete, you may only iterate over it.
|
||||||
|
//
|
||||||
|
// In this hash table, keys are JSObjects, and we use JSObject identity
|
||||||
|
// (that is, address identity) as key identity. The normal way to
|
||||||
|
// support such a table is to make the trace function notice keys that
|
||||||
|
// have moved and re-key them in the table. However, our trace function
|
||||||
|
// does *not* rehash; the first GC may render the hash table
|
||||||
|
// unsearchable.
|
||||||
|
//
|
||||||
|
// This is as it should be:
|
||||||
|
//
|
||||||
|
// First, the heap traversal phase needs lookups by key to work. But no
|
||||||
|
// GC may ever occur during a traversal; this is enforced by the
|
||||||
|
// JS::ubi::BreadthFirst template. So the traceCount function doesn't
|
||||||
|
// need to do anything to help traversal; it never even runs then.
|
||||||
|
//
|
||||||
|
// Second, the report phase needs iteration over the table to work, but
|
||||||
|
// never looks up entries by key. GC may well occur during this phase:
|
||||||
|
// we allocate a Map object, and probably cross-compartment wrappers for
|
||||||
|
// SavedFrame instances as well. If a GC were to occur, it would call
|
||||||
|
// our traceCount function; if traceCount were to re-key, that would
|
||||||
|
// ruin the traversal in progress.
|
||||||
|
//
|
||||||
|
// So depending on the phase, we either don't need re-keying, or
|
||||||
|
// can't abide it.
|
||||||
|
Table table;
|
||||||
|
CountBasePtr noStack;
|
||||||
|
|
||||||
|
Count(CountType& type, CountBasePtr& noStack)
|
||||||
|
: CountBase(type),
|
||||||
|
noStack(Move(noStack))
|
||||||
|
{ }
|
||||||
|
bool init() { return table.init(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
CountTypePtr entryType;
|
||||||
|
CountTypePtr noStackType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ByAllocationStack(Census& census, CountTypePtr& entryType, CountTypePtr& noStackType)
|
||||||
|
: CountType(census),
|
||||||
|
entryType(Move(entryType)),
|
||||||
|
noStackType(Move(noStackType))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
CountBasePtr makeCount() override {
|
||||||
|
CountBasePtr noStackCount(noStackType->makeCount());
|
||||||
|
if (!noStackCount)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
UniquePtr<Count> count(census.new_<Count>(*this, noStackCount));
|
||||||
|
if (!count || !count->init())
|
||||||
|
return nullptr;
|
||||||
|
return CountBasePtr(count.release());
|
||||||
|
}
|
||||||
|
|
||||||
|
void traceCount(CountBase& countBase, JSTracer* trc) override {
|
||||||
|
Count& count= static_cast<Count&>(countBase);
|
||||||
|
for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
|
||||||
|
// Trace our child Counts.
|
||||||
|
r.front().value()->trace(trc);
|
||||||
|
|
||||||
|
// Trace the SavedFrame that is this entry's key. Do not re-key if
|
||||||
|
// it has moved; see comments for ByAllocationStack::Count::table.
|
||||||
|
SavedFrame** keyPtr = const_cast<SavedFrame**>(&r.front().key());
|
||||||
|
TraceRoot(trc, keyPtr, "Debugger.Memory.prototype.census byAllocationStack count key");
|
||||||
|
}
|
||||||
|
count.noStack->trace(trc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destructCount(CountBase& countBase) override {
|
||||||
|
Count& count = static_cast<Count&>(countBase);
|
||||||
|
count.~Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool count(CountBase& countBase, const Node& node) {
|
||||||
|
Count& count = static_cast<Count&>(countBase);
|
||||||
|
count.total_++;
|
||||||
|
|
||||||
|
SavedFrame* allocationStack = nullptr;
|
||||||
|
if (node.is<JSObject>()) {
|
||||||
|
JSObject* metadata = GetObjectMetadata(node.as<JSObject>());
|
||||||
|
if (metadata && metadata->is<SavedFrame>())
|
||||||
|
allocationStack = &metadata->as<SavedFrame>();
|
||||||
|
}
|
||||||
|
// If any other types had allocation site data, we could retrieve it
|
||||||
|
// here.
|
||||||
|
|
||||||
|
// If we do have an allocation stack for this node, include it in the
|
||||||
|
// count for that stack.
|
||||||
|
if (allocationStack) {
|
||||||
|
Table::AddPtr p = count.table.lookupForAdd(allocationStack);
|
||||||
|
if (!p) {
|
||||||
|
CountBasePtr stackCount(entryType->makeCount());
|
||||||
|
if (!stackCount || !count.table.add(p, allocationStack, Move(stackCount)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return p->value()->count(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, count it in the "no stack" category.
|
||||||
|
return count.noStack->count(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool report(CountBase& countBase, MutableHandleValue report) override {
|
||||||
|
Count& count = static_cast<Count&>(countBase);
|
||||||
|
JSContext* cx = census.cx;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
// Check that nothing rehashes our table while we hold pointers into it.
|
||||||
|
uint32_t generation = count.table.generation();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Build a vector of pointers to entries; sort by total; and then use
|
||||||
|
// that to build the result object. This makes the ordering of entries
|
||||||
|
// more interesting, and a little less non-deterministic.
|
||||||
|
mozilla::Vector<Entry*> entries;
|
||||||
|
if (!entries.reserve(count.table.count()))
|
||||||
|
return false;
|
||||||
|
for (Table::Range r = count.table.all(); !r.empty(); r.popFront())
|
||||||
|
entries.infallibleAppend(&r.front());
|
||||||
|
qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries<Entry>);
|
||||||
|
|
||||||
|
// Now build the result by iterating over the sorted vector.
|
||||||
|
Rooted<MapObject*> map(cx, MapObject::create(cx));
|
||||||
|
if (!map)
|
||||||
|
return false;
|
||||||
|
for (Entry** entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) {
|
||||||
|
Entry& entry = **entryPtr;
|
||||||
|
|
||||||
|
MOZ_ASSERT(entry.key());
|
||||||
|
RootedValue stack(cx, ObjectValue(*entry.key()));
|
||||||
|
if (!cx->compartment()->wrap(cx, &stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CountBasePtr& stackCount = entry.value();
|
||||||
|
RootedValue stackReport(cx);
|
||||||
|
if (!stackCount->report(&stackReport))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!MapObject::set(cx, map, stack, stackReport))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RootedValue noStackReport(cx);
|
||||||
|
if (!count.noStack->report(&noStackReport))
|
||||||
|
return false;
|
||||||
|
RootedValue noStack(cx, StringValue(cx->names().noStack));
|
||||||
|
if (!MapObject::set(cx, map, noStack, noStackReport))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
MOZ_ASSERT(generation == count.table.generation());
|
||||||
|
|
||||||
|
report.setObject(*map);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// A BreadthFirst handler type that conducts a census, using a CountBase to
|
// A BreadthFirst handler type that conducts a census, using a CountBase to
|
||||||
// categorize and count each node.
|
// categorize and count each node.
|
||||||
class CensusHandler {
|
class CensusHandler {
|
||||||
@ -1142,6 +1312,17 @@ ParseBreakdown(Census& census, HandleValue breakdownValue)
|
|||||||
return CountTypePtr(census.new_<ByUbinodeType>(census, thenType));
|
return CountTypePtr(census.new_<ByUbinodeType>(census, thenType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringEqualsAscii(by, "allocationStack")) {
|
||||||
|
CountTypePtr thenType(ParseChildBreakdown(census, breakdown, cx->names().then));
|
||||||
|
if (!thenType)
|
||||||
|
return nullptr;
|
||||||
|
CountTypePtr noStackType(ParseChildBreakdown(census, breakdown, cx->names().noStack));
|
||||||
|
if (!noStackType)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return CountTypePtr(census.new_<ByAllocationStack>(census, thenType, noStackType));
|
||||||
|
}
|
||||||
|
|
||||||
// We didn't recognize the breakdown type; complain.
|
// We didn't recognize the breakdown type; complain.
|
||||||
RootedString bySource(cx, ValueToSource(cx, byValue));
|
RootedString bySource(cx, ValueToSource(cx, byValue));
|
||||||
if (!bySource)
|
if (!bySource)
|
||||||
|
Loading…
Reference in New Issue
Block a user