Bug 1040390 - Replace ad hoc methods with JS::ProfilingFrameIterator::label() (r=dougc)

--HG--
extra : rebase_source : 396c7925edac39e39d16dea7a53da3ba34f68ddf
This commit is contained in:
Luke Wagner 2014-07-29 09:56:21 -05:00
parent 2875ab4eb5
commit d082a048a2
9 changed files with 119 additions and 173 deletions

View File

@ -43,11 +43,10 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
public:
struct RegisterState
{
RegisterState() : pc(nullptr), sp(nullptr), lr(nullptr) {}
void *pc;
void *sp;
#if defined(JS_CODEGEN_ARM)
void *lr;
#endif
};
ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state);
@ -62,19 +61,9 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
// and less than older native and psuedo-stack frame addresses
void *stackAddress() const;
enum Kind {
Function,
AsmJSTrampoline,
CppFunction
};
Kind kind() const;
// Methods available if kind() == Function:
JSAtom *functionDisplayAtom() const;
const char *functionFilename() const;
// Methods available if kind() != Function
const char *nonFunctionDescription() const;
// Return a label suitable for regexp-matching as performed by
// browser/devtools/profiler/cleopatra/js/parserWorker.js
const char *label() const;
};
} // namespace JS

View File

@ -4,6 +4,17 @@ load(libdir + "asm.js");
if (!getBuildConfiguration()["arm-simulator"])
quit();
function assertEqualStacks(got, expect)
{
// Strip off the " (script/library info)"
got = String(got).replace(/ \([^\)]*\)/g, "");
// Shorten FFI/entry trampolines
got = got.replace(/FFI trampoline/g, "<").replace(/entry trampoline/g, ">");
assertEq(got, expect);
}
// Test profiling enablement while asm.js is running.
var stacks;
var ffi = function(enable) {
@ -16,15 +27,15 @@ var ffi = function(enable) {
}
var f = asmLink(asmCompile('global','ffis',USE_ASM + "var ffi=ffis.ffi; function g(i) { i=i|0; ffi(i|0) } function f(i) { i=i|0; g(i|0) } return f"), null, {ffi});
f(0);
assertEq(String(stacks), "");
assertEqualStacks(stacks, "");
f(+1);
assertEq(String(stacks), "");
assertEqualStacks(stacks, "");
f(0);
assertEq(String(stacks), "*gf*");
assertEqualStacks(stacks, "<gf>");
f(-1);
assertEq(String(stacks), "*gf*");
assertEqualStacks(stacks, "<gf>");
f(0);
assertEq(String(stacks), "");
assertEqualStacks(stacks, "");
// Enable profiling for the rest of the tests.
enableSPSProfiling();
@ -33,27 +44,27 @@ var f = asmLink(asmCompile(USE_ASM + "function f() { return 42 } return f"));
enableSingleStepProfiling();
assertEq(f(), 42);
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f*,*,");
assertEqualStacks(stacks, ",>,f>,>,");
var f = asmLink(asmCompile(USE_ASM + "function g(i) { i=i|0; return (i+1)|0 } function f() { return g(42)|0 } return f"));
enableSingleStepProfiling();
assertEq(f(), 43);
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f*,gf*,f*,*,");
assertEqualStacks(stacks, ",>,f>,gf>,f>,>,");
var f = asmLink(asmCompile(USE_ASM + "function g1() { return 1 } function g2() { return 2 } function f(i) { i=i|0; return TBL[i&1]()|0 } var TBL=[g1,g2]; return f"));
enableSingleStepProfiling();
assertEq(f(0), 1);
assertEq(f(1), 2);
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f*,g1f*,f*,*,,*,f*,g2f*,f*,*,");
assertEqualStacks(stacks, ",>,f>,g1f>,f>,>,,>,f>,g2f>,f>,>,");
function testBuiltinD2D(name) {
var f = asmLink(asmCompile('g', USE_ASM + "var fun=g.Math." + name + "; function f(d) { d=+d; return +fun(d) } return f"), this);
enableSingleStepProfiling();
assertEq(f(.1), eval("Math." + name + "(.1)"));
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,");
}
for (name of ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'ceil', 'floor', 'exp', 'log'])
testBuiltinD2D(name);
@ -62,7 +73,7 @@ function testBuiltinF2F(name) {
enableSingleStepProfiling();
assertEq(f(.1), eval("Math.fround(Math." + name + "(Math.fround(.1)))"));
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,");
}
for (name of ['ceil', 'floor'])
testBuiltinF2F(name);
@ -71,7 +82,7 @@ function testBuiltinDD2D(name) {
enableSingleStepProfiling();
assertEq(f(.1, .2), eval("Math." + name + "(.1, .2)"));
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f*,Math." + name + "f*,f*,*,");
assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,");
}
for (name of ['atan2', 'pow'])
testBuiltinDD2D(name);
@ -88,14 +99,14 @@ var f = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2=ffis.
enableSingleStepProfiling();
assertEq(f(), 83);
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f*,*f*,f*,*f*,f*,*,");
assertEqualStacks(stacks, ",>,f>,<f>,f>,<f>,f>,>,");
// Ion FFI exit
for (var i = 0; i < 20; i++)
assertEq(f(), 83);
enableSingleStepProfiling();
assertEq(f(), 83);
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f*,*f*,f*,*f*,f*,*,");
assertEqualStacks(stacks, ",>,f>,<f>,f>,<f>,f>,>,");
var ffi1 = function() { return 15 }
var ffi2 = function() { return f2() + 17 }
@ -104,14 +115,14 @@ var {f1,f2} = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2
enableSingleStepProfiling();
assertEq(f1(), 32);
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f1*,*f1*,**f1*,f2**f1*,*f2**f1*,f2**f1*,**f1*,*f1*,f1*,*,");
assertEqualStacks(stacks, ",>,f1>,<f1>,><f1>,f2><f1>,<f2><f1>,f2><f1>,><f1>,<f1>,f1>,>,");
// Ion FFI exit
for (var i = 0; i < 20; i++)
assertEq(f1(), 32);
enableSingleStepProfiling();
assertEq(f1(), 32);
var stacks = disableSingleStepProfiling();
assertEq(String(stacks), ",*,f1*,*f1*,**f1*,f2**f1*,*f2**f1*,f2**f1*,**f1*,*f1*,f1*,*,");
assertEqualStacks(stacks, ",>,f1>,<f1>,><f1>,f2><f1>,<f2><f1>,f2><f1>,><f1>,<f1>,f1>,>,");
// This takes forever to run.
// Stack-overflow exit test

View File

@ -1018,6 +1018,10 @@ class MOZ_STACK_CLASS ModuleCompiler
private:
struct SlowFunction
{
SlowFunction(PropertyName *name, unsigned ms, unsigned line, unsigned column)
: name(name), ms(ms), line(line), column(column)
{}
PropertyName *name;
unsigned ms;
unsigned line;
@ -1423,7 +1427,10 @@ class MOZ_STACK_CLASS ModuleCompiler
bool finishGeneratingFunction(Func &func, CodeGenerator &codegen,
const AsmJSFunctionLabels &labels)
{
if (!module_->addFunctionCodeRange(func.name(), labels))
uint32_t line, column;
tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column);
if (!module_->addFunctionCodeRange(func.name(), line, labels))
return false;
jit::IonScriptCounts *counts = codegen.extractScriptCounts();
@ -1433,17 +1440,12 @@ class MOZ_STACK_CLASS ModuleCompiler
}
if (func.compileTime() >= 250) {
SlowFunction sf;
sf.name = func.name();
sf.ms = func.compileTime();
tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &sf.line, &sf.column);
SlowFunction sf(func.name(), func.compileTime(), line, column);
if (!slowFunctions_.append(sf))
return false;
}
#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
uint32_t line, column;
tokenStream().srcCoords.lineNumAndColumnIndex(func.srcBegin(), &line, &column);
unsigned begin = labels.begin.offset();
unsigned end = labels.end.offset();
if (!module_->addProfiledFunction(func.name(), begin, end, line, column))

View File

@ -599,90 +599,50 @@ AsmJSProfilingFrameIterator::operator++()
JS_ASSERT(!done());
}
AsmJSProfilingFrameIterator::Kind
AsmJSProfilingFrameIterator::kind() const
{
JS_ASSERT(!done());
switch (AsmJSExit::ExtractReasonKind(exitReason_)) {
case AsmJSExit::Reason_None:
break;
case AsmJSExit::Reason_Interrupt:
case AsmJSExit::Reason_FFI:
return JS::ProfilingFrameIterator::AsmJSTrampoline;
case AsmJSExit::Reason_Builtin:
return JS::ProfilingFrameIterator::CppFunction;
}
auto codeRange = reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_);
switch (codeRange->kind()) {
case AsmJSModule::CodeRange::Function:
return JS::ProfilingFrameIterator::Function;
case AsmJSModule::CodeRange::Entry:
case AsmJSModule::CodeRange::FFI:
case AsmJSModule::CodeRange::Interrupt:
case AsmJSModule::CodeRange::Inline:
return JS::ProfilingFrameIterator::AsmJSTrampoline;
case AsmJSModule::CodeRange::Thunk:
return JS::ProfilingFrameIterator::CppFunction;
}
MOZ_ASSUME_UNREACHABLE("Bad kind");
}
JSAtom *
AsmJSProfilingFrameIterator::functionDisplayAtom() const
{
JS_ASSERT(kind() == JS::ProfilingFrameIterator::Function);
return reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_)->functionName(*module_);
}
const char *
AsmJSProfilingFrameIterator::functionFilename() const
{
JS_ASSERT(kind() == JS::ProfilingFrameIterator::Function);
return module_->scriptSource()->filename();
}
static const char *
BuiltinToName(AsmJSExit::BuiltinKind builtin)
{
// Note: this label is regexp-matched by
// browser/devtools/profiler/cleopatra/js/parserWorker.js.
switch (builtin) {
case AsmJSExit::Builtin_ToInt32: return "ToInt32";
case AsmJSExit::Builtin_ToInt32: return "ToInt32 (in asm.js)";
#if defined(JS_CODEGEN_ARM)
case AsmJSExit::Builtin_IDivMod: return "software idivmod";
case AsmJSExit::Builtin_UDivMod: return "software uidivmod";
case AsmJSExit::Builtin_IDivMod: return "software idivmod (in asm.js)";
case AsmJSExit::Builtin_UDivMod: return "software uidivmod (in asm.js)";
#endif
case AsmJSExit::Builtin_ModD: return "fmod";
case AsmJSExit::Builtin_SinD: return "Math.sin";
case AsmJSExit::Builtin_CosD: return "Math.cos";
case AsmJSExit::Builtin_TanD: return "Math.tan";
case AsmJSExit::Builtin_ASinD: return "Math.asin";
case AsmJSExit::Builtin_ACosD: return "Math.acos";
case AsmJSExit::Builtin_ATanD: return "Math.atan";
case AsmJSExit::Builtin_ModD: return "fmod (in asm.js)";
case AsmJSExit::Builtin_SinD: return "Math.sin (in asm.js)";
case AsmJSExit::Builtin_CosD: return "Math.cos (in asm.js)";
case AsmJSExit::Builtin_TanD: return "Math.tan (in asm.js)";
case AsmJSExit::Builtin_ASinD: return "Math.asin (in asm.js)";
case AsmJSExit::Builtin_ACosD: return "Math.acos (in asm.js)";
case AsmJSExit::Builtin_ATanD: return "Math.atan (in asm.js)";
case AsmJSExit::Builtin_CeilD:
case AsmJSExit::Builtin_CeilF: return "Math.ceil";
case AsmJSExit::Builtin_CeilF: return "Math.ceil (in asm.js)";
case AsmJSExit::Builtin_FloorD:
case AsmJSExit::Builtin_FloorF: return "Math.floor";
case AsmJSExit::Builtin_ExpD: return "Math.exp";
case AsmJSExit::Builtin_LogD: return "Math.log";
case AsmJSExit::Builtin_PowD: return "Math.pow";
case AsmJSExit::Builtin_ATan2D: return "Math.atan2";
case AsmJSExit::Builtin_FloorF: return "Math.floor (in asm.js)";
case AsmJSExit::Builtin_ExpD: return "Math.exp (in asm.js)";
case AsmJSExit::Builtin_LogD: return "Math.log (in asm.js)";
case AsmJSExit::Builtin_PowD: return "Math.pow (in asm.js)";
case AsmJSExit::Builtin_ATan2D: return "Math.atan2 (in asm.js)";
case AsmJSExit::Builtin_Limit: break;
}
MOZ_ASSUME_UNREACHABLE("Bad builtin kind");
}
const char *
AsmJSProfilingFrameIterator::nonFunctionDescription() const
AsmJSProfilingFrameIterator::label() const
{
JS_ASSERT(!done());
JS_ASSERT(kind() != JS::ProfilingFrameIterator::Function);
// Note: this label is regexp-matched by
// browser/devtools/profiler/cleopatra/js/parserWorker.js.
// Use the same string for both time inside and under so that the two
// entries will be coalesced by the profiler.
const char *ffiDescription = "asm.js FFI trampoline";
const char *interruptDescription = "asm.js slow script interrupt";
const char *ffiDescription = "FFI trampoline (in asm.js)";
const char *interruptDescription = "slow script interrupt trampoline (in asm.js)";
switch (AsmJSExit::ExtractReasonKind(exitReason_)) {
case AsmJSExit::Reason_None:
@ -697,11 +657,11 @@ AsmJSProfilingFrameIterator::nonFunctionDescription() const
auto codeRange = reinterpret_cast<const AsmJSModule::CodeRange*>(codeRange_);
switch (codeRange->kind()) {
case AsmJSModule::CodeRange::Function: MOZ_ASSUME_UNREACHABLE("non-functions only");
case AsmJSModule::CodeRange::Entry: return "asm.js entry trampoline";
case AsmJSModule::CodeRange::Function: return codeRange->functionProfilingLabel(*module_);
case AsmJSModule::CodeRange::Entry: return "entry trampoline (in asm.js)";
case AsmJSModule::CodeRange::FFI: return ffiDescription;
case AsmJSModule::CodeRange::Interrupt: return interruptDescription;
case AsmJSModule::CodeRange::Inline: return "asm.js inline stub";
case AsmJSModule::CodeRange::Inline: return "inline stub (in asm.js)";
case AsmJSModule::CodeRange::Thunk: return BuiltinToName(codeRange->thunkTarget());
}

View File

@ -133,15 +133,7 @@ class AsmJSProfilingFrameIterator
bool done() const { return !codeRange_; }
void *stackAddress() const { JS_ASSERT(!done()); return stackAddress_; }
typedef JS::ProfilingFrameIterator::Kind Kind;
Kind kind() const;
JSAtom *functionDisplayAtom() const;
const char *functionFilename() const;
unsigned functionLine() const;
const char *nonFunctionDescription() const;
const char *label() const;
};
/******************************************************************************/

View File

@ -1173,8 +1173,10 @@ AsmJSModule::ExportedFunction::clone(ExclusiveContext *cx, ExportedFunction *out
return true;
}
AsmJSModule::CodeRange::CodeRange(uint32_t nameIndex, const AsmJSFunctionLabels &l)
AsmJSModule::CodeRange::CodeRange(uint32_t nameIndex, uint32_t lineNumber,
const AsmJSFunctionLabels &l)
: nameIndex_(nameIndex),
lineNumber_(lineNumber),
begin_(l.begin.offset()),
profilingReturn_(l.profilingReturn.offset()),
end_(l.end.offset())
@ -1535,6 +1537,29 @@ AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx)
if (profilingEnabled_ == enabled)
return;
// When enabled, generate profiling labels for every name in names_ that is
// the name of some Function CodeRange. This involves malloc() so do it now
// since, once we start sampling, we'll be in a signal-handing context where
// we cannot malloc.
if (enabled) {
profilingLabels_.resize(names_.length());
const char *filename = scriptSource_->filename();
JS::AutoCheckCannotGC nogc;
for (size_t i = 0; i < codeRanges_.length(); i++) {
CodeRange &cr = codeRanges_[i];
if (!cr.isFunction())
continue;
unsigned lineno = cr.functionLineNumber();
PropertyName *name = names_[cr.functionNameIndex()].name();
profilingLabels_[cr.functionNameIndex()].reset(
name->hasLatin1Chars()
? JS_smprintf("%s (%s:%u)", name->latin1Chars(nogc), filename, lineno)
: JS_smprintf("%hs (%s:%u)", name->twoByteChars(nogc), filename, lineno));
}
} else {
profilingLabels_.clear();
}
// Conservatively flush the icache for the entire module.
AutoFlushICache afc("AsmJSModule::setProfilingEnabled");
setAutoFlushICacheRange();

View File

@ -335,6 +335,7 @@ class AsmJSModule
class CodeRange
{
uint32_t nameIndex_;
uint32_t lineNumber_;
uint32_t begin_;
uint32_t profilingReturn_;
uint32_t end_;
@ -358,7 +359,7 @@ class AsmJSModule
enum Kind { Function, Entry, FFI, Interrupt, Thunk, Inline };
CodeRange() {}
CodeRange(uint32_t nameIndex, const AsmJSFunctionLabels &l);
CodeRange(uint32_t nameIndex, uint32_t lineNumber, const AsmJSFunctionLabels &l);
CodeRange(Kind kind, uint32_t begin, uint32_t end);
CodeRange(Kind kind, uint32_t begin, uint32_t profilingReturn, uint32_t end);
CodeRange(AsmJSExit::BuiltinKind builtin, uint32_t begin, uint32_t pret, uint32_t end);
@ -393,10 +394,22 @@ class AsmJSModule
JS_ASSERT(isFunction() || isFFI() || isInterrupt() || isThunk());
return profilingReturn_;
}
uint32_t functionNameIndex() const {
JS_ASSERT(isFunction());
return nameIndex_;
}
PropertyName *functionName(const AsmJSModule &module) const {
JS_ASSERT(isFunction());
return module.names_[nameIndex_].name();
}
const char *functionProfilingLabel(const AsmJSModule &module) const {
JS_ASSERT(isFunction());
return module.profilingLabels_[nameIndex_].get();
}
uint32_t functionLineNumber() const {
JS_ASSERT(isFunction());
return lineNumber_;
}
AsmJSExit::BuiltinKind thunkTarget() const {
JS_ASSERT(isThunk());
return AsmJSExit::BuiltinKind(u.thunk.target_);
@ -430,6 +443,8 @@ class AsmJSModule
bool clone(ExclusiveContext *cx, Name *out) const;
};
typedef mozilla::UniquePtr<char, JS::FreePolicy> ProfilingLabel;
#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
// Function information to add to the VTune JIT profiler following linking.
struct ProfiledFunction
@ -603,6 +618,7 @@ class AsmJSModule
Vector<FuncPtrTable, 0, SystemAllocPolicy> funcPtrTables_;
Vector<uint32_t, 0, SystemAllocPolicy> builtinThunkOffsets_;
Vector<Name, 0, SystemAllocPolicy> names_;
Vector<ProfilingLabel, 0, SystemAllocPolicy> profilingLabels_;
Vector<jit::AsmJSHeapAccess, 0, SystemAllocPolicy> heapAccesses_;
Vector<jit::IonScriptCounts*, 0, SystemAllocPolicy> functionCounts_;
#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
@ -801,13 +817,15 @@ class AsmJSModule
if (len > pod.minHeapLength_)
pod.minHeapLength_ = len;
}
bool addFunctionCodeRange(PropertyName *name, const AsmJSFunctionLabels &labels) {
bool addFunctionCodeRange(PropertyName *name, uint32_t lineNumber,
const AsmJSFunctionLabels &labels)
{
JS_ASSERT(!isFinished());
JS_ASSERT(name->isTenured());
if (names_.length() >= UINT32_MAX)
return false;
uint32_t nameIndex = names_.length();
return names_.append(name) && codeRanges_.append(CodeRange(nameIndex, labels));
return names_.append(name) && codeRanges_.append(CodeRange(nameIndex, lineNumber, labels));
}
bool addEntryCodeRange(uint32_t begin, uint32_t end) {
return codeRanges_.append(CodeRange(CodeRange::Entry, begin, end));

View File

@ -4273,26 +4273,8 @@ SingleStepCallback(void *arg, jit::Simulator *sim, void *pc)
JS_ASSERT(i.stackAddress() != nullptr);
JS_ASSERT(lastStackAddress <= i.stackAddress());
lastStackAddress = i.stackAddress();
switch (i.kind()) {
case JS::ProfilingFrameIterator::Function: {
JS::AutoCheckCannotGC nogc;
JSAtom *atom = i.functionDisplayAtom();
if (atom->hasLatin1Chars())
stack.append(atom->latin1Chars(nogc), atom->length());
else
stack.append(atom->twoByteChars(nogc), atom->length());
break;
}
case JS::ProfilingFrameIterator::AsmJSTrampoline: {
stack.append('*');
break;
}
case JS::ProfilingFrameIterator::CppFunction: {
const char *desc = i.nonFunctionDescription();
stack.append(desc, strlen(desc));
break;
}
}
const char *label = i.label();
stack.append(label, strlen(label));
}
// Only append the stack if it differs from the last stack.

View File

@ -1842,44 +1842,11 @@ JS::ProfilingFrameIterator::stackAddress() const
#endif
}
JS::ProfilingFrameIterator::Kind
JS::ProfilingFrameIterator::kind() const
{
#ifdef JS_ION
return iter().kind();
#else
MOZ_CRASH("Shouldn't have any frames");
#endif
}
JSAtom *
JS::ProfilingFrameIterator::functionDisplayAtom() const
{
#ifdef JS_ION
JS_ASSERT(kind() == Function);
return iter().functionDisplayAtom();
#else
MOZ_CRASH("Shouldn't have any frames");
#endif
}
const char *
JS::ProfilingFrameIterator::functionFilename() const
JS::ProfilingFrameIterator::label() const
{
#ifdef JS_ION
JS_ASSERT(kind() == Function);
return iter().functionFilename();
#else
MOZ_CRASH("Shouldn't have any frames");
#endif
}
const char *
JS::ProfilingFrameIterator::nonFunctionDescription() const
{
#ifdef JS_ION
JS_ASSERT(kind() != Function);
return iter().nonFunctionDescription();
return iter().label();
#else
MOZ_CRASH("Shouldn't have any frames");
#endif