Bug 1131429 - Add a shell function to dump all of a function's tracked optimizations. (r=djvj)

This commit is contained in:
Shu-yu Guo 2015-02-22 20:05:35 -08:00
parent defb6666ab
commit 28a375a748
5 changed files with 272 additions and 75 deletions

View File

@ -313,6 +313,9 @@ struct ForEachTrackedOptimizationTypeInfoOp
// The lineno parameter is the line number if the type is keyed by
// "constructor", "alloc site", or if the type itself refers to a scripted
// function. Otherwise it is UINT32_MAX.
//
// The location parameter is the only one that may need escaping if being
// quoted.
virtual void readType(const char *keyedBy, const char *name,
const char *location, unsigned lineno) = 0;

View File

@ -217,6 +217,16 @@ class JitcodeGlobalEntry
return !!optsRegionTable_;
}
const IonTrackedOptimizationsRegionTable *trackedOptimizationsRegionTable() const {
MOZ_ASSERT(hasTrackedOptimizations());
return optsRegionTable_;
}
uint8_t numOptimizationAttempts() const {
MOZ_ASSERT(hasTrackedOptimizations());
return optsAttemptsTable_->numEntries();
}
IonTrackedOptimizationsAttempts trackedOptimizationAttempts(uint8_t index) {
MOZ_ASSERT(hasTrackedOptimizations());
return optsAttemptsTable_->entry(index);

View File

@ -1164,85 +1164,78 @@ FunctionFromTrackedType(const IonTrackedTypeWithAddendum &tracked)
return ty.group()->maybeInterpretedFunction();
}
// This adapter is needed as the internal API can deal with engine-internal
// data structures directly, while the public API cannot.
class ForEachTypeInfoAdapter : public IonTrackedOptimizationsTypeInfo::ForEachOp
void
IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::readType(const IonTrackedTypeWithAddendum &tracked)
{
ForEachTrackedOptimizationTypeInfoOp &op_;
TypeSet::Type ty = tracked.type;
public:
explicit ForEachTypeInfoAdapter(ForEachTrackedOptimizationTypeInfoOp &op)
: op_(op)
{ }
void readType(const IonTrackedTypeWithAddendum &tracked) MOZ_OVERRIDE {
TypeSet::Type ty = tracked.type;
if (ty.isPrimitive() || ty.isUnknown() || ty.isAnyObject()) {
op_.readType("primitive", TypeSet::NonObjectTypeString(ty), nullptr, 0);
return;
}
char buf[512];
const uint32_t bufsize = mozilla::ArrayLength(buf);
if (JSFunction *fun = FunctionFromTrackedType(tracked)) {
if (fun->isNative()) {
//
// Print out the absolute address of the function pointer.
//
// Note that this address is not usable without knowing the
// starting address at which our shared library is loaded. Shared
// library information is exposed by the profiler. If this address
// needs to be symbolicated manually (e.g., when it is gotten via
// debug spewing of all optimization information), it needs to be
// converted to an offset from the beginning of the shared library
// for use with utilities like `addr2line` on Linux and `atos` on
// OS X. Converting to an offset may be done via dladdr():
//
// void *addr = JS_FUNC_TO_DATA_PTR(void *, fun->native());
// uintptr_t offset;
// Dl_info info;
// if (dladdr(addr, &info) != 0)
// offset = uintptr_t(addr) - uintptr_t(info.dli_fbase);
//
uintptr_t addr = JS_FUNC_TO_DATA_PTR(uintptr_t, fun->native());
JS_snprintf(buf, bufsize, "%llx", addr);
op_.readType("native", nullptr, buf, UINT32_MAX);
return;
}
PutEscapedString(buf, bufsize, fun->displayAtom(), 0);
const char *filename;
unsigned lineno;
InterpretedFunctionFilenameAndLineNumber(fun, &filename, &lineno);
op_.readType(tracked.constructor ? "constructor" : "function", buf, filename, lineno);
return;
}
const char *className = ty.objectKey()->clasp()->name;
JS_snprintf(buf, bufsize, "[object %s]", className);
if (tracked.hasAllocationSite()) {
JSScript *script = tracked.script;
op_.readType("alloc site", buf,
script->maybeForwardedScriptSource()->filename(),
PCToLineNumber(script, script->offsetToPC(tracked.offset)));
return;
}
if (ty.isGroup()) {
op_.readType("prototype", buf, nullptr, UINT32_MAX);
return;
}
op_.readType("singleton", buf, nullptr, UINT32_MAX);
if (ty.isPrimitive() || ty.isUnknown() || ty.isAnyObject()) {
op_.readType("primitive", TypeSet::NonObjectTypeString(ty), nullptr, 0);
return;
}
void operator()(JS::TrackedTypeSite site, MIRType mirType) MOZ_OVERRIDE {
op_(site, StringFromMIRType(mirType));
char buf[512];
const uint32_t bufsize = mozilla::ArrayLength(buf);
if (JSFunction *fun = FunctionFromTrackedType(tracked)) {
if (fun->isNative()) {
//
// Print out the absolute address of the function pointer.
//
// Note that this address is not usable without knowing the
// starting address at which our shared library is loaded. Shared
// library information is exposed by the profiler. If this address
// needs to be symbolicated manually (e.g., when it is gotten via
// debug spewing of all optimization information), it needs to be
// converted to an offset from the beginning of the shared library
// for use with utilities like `addr2line` on Linux and `atos` on
// OS X. Converting to an offset may be done via dladdr():
//
// void *addr = JS_FUNC_TO_DATA_PTR(void *, fun->native());
// uintptr_t offset;
// Dl_info info;
// if (dladdr(addr, &info) != 0)
// offset = uintptr_t(addr) - uintptr_t(info.dli_fbase);
//
uintptr_t addr = JS_FUNC_TO_DATA_PTR(uintptr_t, fun->native());
JS_snprintf(buf, bufsize, "%llx", addr);
op_.readType("native", nullptr, buf, UINT32_MAX);
return;
}
PutEscapedString(buf, bufsize, fun->displayAtom(), 0);
const char *filename;
unsigned lineno;
InterpretedFunctionFilenameAndLineNumber(fun, &filename, &lineno);
op_.readType(tracked.constructor ? "constructor" : "function", buf, filename, lineno);
return;
}
};
const char *className = ty.objectKey()->clasp()->name;
JS_snprintf(buf, bufsize, "[object %s]", className);
if (tracked.hasAllocationSite()) {
JSScript *script = tracked.script;
op_.readType("alloc site", buf,
script->maybeForwardedScriptSource()->filename(),
PCToLineNumber(script, script->offsetToPC(tracked.offset)));
return;
}
if (ty.isGroup()) {
op_.readType("prototype", buf, nullptr, UINT32_MAX);
return;
}
op_.readType("singleton", buf, nullptr, UINT32_MAX);
}
void
IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::operator()(JS::TrackedTypeSite site,
MIRType mirType)
{
op_(site, StringFromMIRType(mirType));
}
JS_PUBLIC_API(void)
JS::ForEachTrackedOptimizationTypeInfo(JSRuntime *rt, void *addr,
@ -1251,7 +1244,7 @@ JS::ForEachTrackedOptimizationTypeInfo(JSRuntime *rt, void *addr,
JitcodeGlobalTable *table = rt->jitRuntime()->getJitcodeGlobalTable();
JitcodeGlobalEntry entry;
table->lookupInfallible(addr, &entry, rt);
ForEachTypeInfoAdapter adapter(op);
IonTrackedOptimizationsTypeInfo::ForEachOpAdapter adapter(op);
Maybe<uint8_t> index = entry.trackedOptimizationIndexAtAddr(addr);
entry.trackedOptimizationTypeInfo(index.value()).forEach(adapter, entry.allTrackedTypes());
}

View File

@ -484,12 +484,27 @@ class IonTrackedOptimizationsTypeInfo
// JS::ForEachTrackedOptimizaitonTypeInfoOp cannot be used directly. The
// internal API needs to deal with engine-internal data structures (e.g.,
// TypeSet::Type) directly.
//
// An adapter is provided below.
struct ForEachOp
{
virtual void readType(const IonTrackedTypeWithAddendum &tracked) = 0;
virtual void operator()(JS::TrackedTypeSite site, MIRType mirType) = 0;
};
class ForEachOpAdapter : public ForEachOp
{
JS::ForEachTrackedOptimizationTypeInfoOp &op_;
public:
explicit ForEachOpAdapter(JS::ForEachTrackedOptimizationTypeInfoOp &op)
: op_(op)
{ }
void readType(const IonTrackedTypeWithAddendum &tracked) MOZ_OVERRIDE;
void operator()(JS::TrackedTypeSite site, MIRType mirType) MOZ_OVERRIDE;
};
void forEach(ForEachOp &op, const IonTrackedTypeVector *allTypes);
};

View File

@ -60,9 +60,12 @@
#include "frontend/Parser.h"
#include "jit/arm/Simulator-arm.h"
#include "jit/Ion.h"
#include "jit/JitcodeMap.h"
#include "jit/OptimizationTracking.h"
#include "js/Debug.h"
#include "js/GCAPI.h"
#include "js/StructuredClone.h"
#include "js/TrackedOptimizationInfo.h"
#include "perf/jsperf.h"
#include "shell/jsheaptools.h"
#include "shell/jsoptparse.h"
@ -4340,6 +4343,173 @@ SetSharedArrayBuffer(JSContext *cx, unsigned argc, Value *vp)
return true;
}
class SprintOptimizationTypeInfoOp : public ForEachTrackedOptimizationTypeInfoOp
{
Sprinter *sp;
bool startedTypes_;
public:
explicit SprintOptimizationTypeInfoOp(Sprinter *sp)
: sp(sp),
startedTypes_(false)
{ }
void readType(const char *keyedBy, const char *name,
const char *location, unsigned lineno) MOZ_OVERRIDE
{
if (!startedTypes_) {
startedTypes_ = true;
Sprint(sp, "{\"typeset\": [");
}
Sprint(sp, "{\"keyedBy\":\"%s\"", keyedBy);
if (name)
Sprint(sp, ",\"name\":\"%s\"", name);
if (location) {
char buf[512];
PutEscapedString(buf, mozilla::ArrayLength(buf), location, strlen(location), '"');
Sprint(sp, ",\"location\":%s", buf);
}
if (lineno != UINT32_MAX)
Sprint(sp, ",\"line\":%u", lineno);
Sprint(sp, "},");
}
void operator()(TrackedTypeSite site, const char *mirType) MOZ_OVERRIDE {
if (startedTypes_) {
// Clear trailing ,
if ((*sp)[sp->getOffset() - 1] == ',')
(*sp)[sp->getOffset() - 1] = ' ';
Sprint(sp, "],");
startedTypes_ = false;
} else {
Sprint(sp, "{");
}
Sprint(sp, "\"site\":\"%s\",\"mirType\":\"%s\"},",
TrackedTypeSiteString(site), mirType);
}
};
class SprintOptimizationAttemptsOp : public ForEachTrackedOptimizationAttemptOp
{
Sprinter *sp;
public:
explicit SprintOptimizationAttemptsOp(Sprinter *sp)
: sp(sp)
{ }
void operator()(TrackedStrategy strategy, TrackedOutcome outcome) MOZ_OVERRIDE {
Sprint(sp, "{\"strategy\":\"%s\",\"outcome\":\"%s\"},",
TrackedStrategyString(strategy), TrackedOutcomeString(outcome));
}
};
static bool
ReflectTrackedOptimizations(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
JSRuntime *rt = cx->runtime();
if (!rt->hasJitRuntime() || !rt->jitRuntime()->isOptimizationTrackingEnabled(rt)) {
JS_ReportError(cx, "Optimization tracking is off.");
return false;
}
if (args.length() != 1) {
ReportUsageError(cx, callee, "Wrong number of arguments");
return false;
}
if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
ReportUsageError(cx, callee, "Argument must be a function");
return false;
}
RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
if (!fun->hasScript() || !fun->nonLazyScript()->hasIonScript()) {
args.rval().setNull();
return true;
}
jit::JitcodeGlobalTable *table = rt->jitRuntime()->getJitcodeGlobalTable();
jit::JitcodeGlobalEntry entry;
jit::IonScript *ion = fun->nonLazyScript()->ionScript();
table->lookupInfallible(ion->method()->raw(), &entry, rt);
if (!entry.hasTrackedOptimizations()) {
JSObject *obj = JS_NewPlainObject(cx);
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
Sprinter sp(cx);
if (!sp.init())
return false;
const jit::IonTrackedOptimizationsRegionTable *regions =
entry.ionEntry().trackedOptimizationsRegionTable();
Sprint(&sp, "{\"regions\": [");
for (uint32_t i = 0; i < regions->numEntries(); i++) {
jit::IonTrackedOptimizationsRegion region = regions->entry(i);
jit::IonTrackedOptimizationsRegion::RangeIterator iter = region.ranges();
while (iter.more()) {
uint32_t startOffset, endOffset;
uint8_t index;
iter.readNext(&startOffset, &endOffset, &index);
JSScript *script;
jsbytecode *pc;
// Use endOffset, as startOffset may be associated with a
// previous, adjacent region ending exactly at startOffset. That
// is, suppose we have two regions [0, startOffset], [startOffset,
// endOffset]. Since we are not querying a return address, we want
// the second region and not the first.
uint8_t *addr = ion->method()->raw() + endOffset;
entry.youngestFrameLocationAtAddr(rt, addr, &script, &pc);
Sprint(&sp, "{\"location\":\"%s:%u\",\"offset\":%u,\"index\":%u}%s",
script->filename(), script->lineno(), script->pcToOffset(pc), index,
iter.more() ? "," : "");
}
}
Sprint(&sp, "],");
Sprint(&sp, "\"opts\": [");
for (uint8_t i = 0; i < entry.ionEntry().numOptimizationAttempts(); i++) {
Sprint(&sp, "%s{\"typeinfo\":[", i == 0 ? "" : ",");
SprintOptimizationTypeInfoOp top(&sp);
jit::IonTrackedOptimizationsTypeInfo::ForEachOpAdapter adapter(top);
entry.trackedOptimizationTypeInfo(i).forEach(adapter, entry.allTrackedTypes());
// Clear the trailing ,
if (sp[sp.getOffset() - 1] == ',')
sp[sp.getOffset() - 1] = ' ';
Sprint(&sp, "],\"attempts\":[");
SprintOptimizationAttemptsOp aop(&sp);
entry.trackedOptimizationAttempts(i).forEach(aop);
// Clear the trailing ,
if (sp[sp.getOffset() - 1] == ',')
sp[sp.getOffset() - 1] = ' ';
Sprint(&sp, "]}");
}
Sprint(&sp, "]}");
if (sp.hadOutOfMemory())
return false;
RootedString str(cx, JS_NewStringCopyZ(cx, sp.string()));
if (!str)
return false;
RootedValue jsonVal(cx);
if (!JS_ParseJSON(cx, str, &jsonVal))
return false;
args.rval().set(jsonVal);
return true;
}
static const JSFunctionSpecWithHelp shell_functions[] = {
JS_FN_HELP("version", Version, 0, 0,
"version([number])",
@ -4784,6 +4954,12 @@ static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
" Note: This is not fuzzing safe because it can be used to construct\n"
" deeply nested wrapper chains that cannot exist in the wild."),
JS_FN_HELP("trackedOpts", ReflectTrackedOptimizations, 1, 0,
"trackedOpts(fun)",
" Returns an object describing the tracked optimizations of |fun|, if\n"
" any. If |fun| is not a scripted function or has not been compiled by\n"
" Ion, null is returned."),
JS_FS_HELP_END
};