зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1131429 - Add a shell function to dump all of a function's tracked optimizations. (r=djvj)
This commit is contained in:
Родитель
df02b37be7
Коммит
ed68d9433a
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче