mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-02 15:15:23 +00:00
Bug 778979 - Part 3: Track the line number in the JS engine for SPS profiling. r=bhackett
This commit is contained in:
parent
9cb624c1fe
commit
0603f5b88e
@ -12,15 +12,15 @@
|
||||
|
||||
#include "jscntxt.h"
|
||||
|
||||
static js::ProfileEntry stack[10];
|
||||
static uint32_t size = 0;
|
||||
static js::ProfileEntry pstack[10];
|
||||
static uint32_t psize = 0;
|
||||
static uint32_t max_stack = 0;
|
||||
|
||||
static void
|
||||
reset(JSContext *cx)
|
||||
{
|
||||
size = max_stack = 0;
|
||||
memset(stack, 0, sizeof(stack));
|
||||
psize = max_stack = 0;
|
||||
memset(pstack, 0, sizeof(pstack));
|
||||
cx->runtime->spsProfiler.stringsReset();
|
||||
cx->runtime->spsProfiler.enableSlowAssertions(true);
|
||||
js::EnableRuntimeProfilingStack(cx->runtime, true);
|
||||
@ -34,7 +34,7 @@ static JSClass ptestClass = {
|
||||
static JSBool
|
||||
test_fn(JSContext *cx, unsigned argc, jsval *vp)
|
||||
{
|
||||
max_stack = size;
|
||||
max_stack = psize;
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
@ -81,13 +81,13 @@ static JSFunctionSpec ptestFunctions[] = {
|
||||
static JSObject*
|
||||
initialize(JSContext *cx)
|
||||
{
|
||||
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 10);
|
||||
js::SetRuntimeProfilingStack(cx->runtime, pstack, &psize, 10);
|
||||
JS::RootedObject global(cx, JS_GetGlobalObject(cx));
|
||||
return JS_InitClass(cx, global, NULL, &ptestClass, Prof, 0,
|
||||
NULL, ptestFunctions, NULL, NULL);
|
||||
}
|
||||
|
||||
BEGIN_TEST(testProfileStrings_isCalled)
|
||||
BEGIN_TEST(testProfileStrings_isCalledWithInterpreter)
|
||||
{
|
||||
CHECK(initialize(cx));
|
||||
|
||||
@ -106,13 +106,13 @@ BEGIN_TEST(testProfileStrings_isCalled)
|
||||
JS::RootedValue rval(cx);
|
||||
/* Make sure the stack resets and we have an entry for each stack */
|
||||
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address()));
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
CHECK(max_stack == 9);
|
||||
CHECK(cx->runtime->spsProfiler.stringsCount() == 8);
|
||||
/* Make sure the stack resets and we added no new entries */
|
||||
max_stack = 0;
|
||||
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address()));
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
CHECK(max_stack == 9);
|
||||
CHECK(cx->runtime->spsProfiler.stringsCount() == 8);
|
||||
}
|
||||
@ -122,22 +122,22 @@ BEGIN_TEST(testProfileStrings_isCalled)
|
||||
CHECK(JS_CallFunctionName(cx, global, "check2", 0, NULL, rval.address()));
|
||||
CHECK(cx->runtime->spsProfiler.stringsCount() == 5);
|
||||
CHECK(max_stack == 7);
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
}
|
||||
js::EnableRuntimeProfilingStack(cx->runtime, false);
|
||||
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 3);
|
||||
js::SetRuntimeProfilingStack(cx->runtime, pstack, &psize, 3);
|
||||
reset(cx);
|
||||
{
|
||||
JS::RootedValue rval(cx);
|
||||
stack[3].string = (char*) 1234;
|
||||
pstack[3].setLabel((char*) 1234);
|
||||
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address()));
|
||||
CHECK((size_t) stack[3].string == 1234);
|
||||
CHECK((size_t) pstack[3].label() == 1234);
|
||||
CHECK(max_stack == 9);
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
END_TEST(testProfileStrings_isCalled)
|
||||
END_TEST(testProfileStrings_isCalledWithInterpreter)
|
||||
|
||||
BEGIN_TEST(testProfileStrings_isCalledWithJIT)
|
||||
{
|
||||
@ -160,29 +160,29 @@ BEGIN_TEST(testProfileStrings_isCalledWithJIT)
|
||||
JS::RootedValue rval(cx);
|
||||
/* Make sure the stack resets and we have an entry for each stack */
|
||||
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address()));
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
CHECK(max_stack == 9);
|
||||
|
||||
/* Make sure the stack resets and we added no new entries */
|
||||
uint32_t cnt = cx->runtime->spsProfiler.stringsCount();
|
||||
max_stack = 0;
|
||||
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address()));
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
CHECK(cx->runtime->spsProfiler.stringsCount() == cnt);
|
||||
CHECK(max_stack == 9);
|
||||
}
|
||||
|
||||
js::EnableRuntimeProfilingStack(cx->runtime, false);
|
||||
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 3);
|
||||
js::SetRuntimeProfilingStack(cx->runtime, pstack, &psize, 3);
|
||||
reset(cx);
|
||||
{
|
||||
/* Limit the size of the stack and make sure we don't overflow */
|
||||
JS::RootedValue rval(cx);
|
||||
stack[3].string = (char*) 1234;
|
||||
pstack[3].setLabel((char*) 1234);
|
||||
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.address()));
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
CHECK(max_stack == 9);
|
||||
CHECK((size_t) stack[3].string == 1234);
|
||||
CHECK((size_t) pstack[3].label() == 1234);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -200,7 +200,7 @@ BEGIN_TEST(testProfileStrings_isCalledWhenError)
|
||||
JS::RootedValue rval(cx);
|
||||
/* Make sure the stack resets and we have an entry for each stack */
|
||||
JS_CallFunctionName(cx, global, "check2", 0, NULL, rval.address());
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
CHECK(cx->runtime->spsProfiler.stringsCount() == 1);
|
||||
}
|
||||
return true;
|
||||
@ -220,7 +220,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
|
||||
/* enable it in the middle of JS and make sure things check out */
|
||||
JS::RootedValue rval(cx);
|
||||
JS_CallFunctionName(cx, global, "a", 0, NULL, rval.address());
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
CHECK(max_stack == 1);
|
||||
CHECK(cx->runtime->spsProfiler.stringsCount() == 1);
|
||||
}
|
||||
@ -232,7 +232,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
|
||||
/* now disable in the middle of js */
|
||||
JS::RootedValue rval(cx);
|
||||
JS_CallFunctionName(cx, global, "c", 0, NULL, rval.address());
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
}
|
||||
|
||||
EXEC("function e() { var p = new Prof(); d(p); p.enable(); b(p); }");
|
||||
@ -241,7 +241,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
|
||||
/* now disable in the middle of js, but re-enable before final exit */
|
||||
JS::RootedValue rval(cx);
|
||||
JS_CallFunctionName(cx, global, "e", 0, NULL, rval.address());
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
CHECK(max_stack == 3);
|
||||
}
|
||||
|
||||
@ -255,7 +255,7 @@ BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
|
||||
/* disable, and make sure that if we try to re-enter the JIT the pop
|
||||
* will still happen */
|
||||
JS_CallFunctionName(cx, global, "f", 0, NULL, rval.address());
|
||||
CHECK(size == 0);
|
||||
CHECK(psize == 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -588,17 +588,19 @@ class ProfileEntry
|
||||
}
|
||||
|
||||
uint32_t line() volatile { JS_ASSERT(!js()); return idx; }
|
||||
jsbytecode *pc() volatile;
|
||||
JSScript *script() volatile { JS_ASSERT(js()); return script_; }
|
||||
void *stackAddress() volatile { return sp; }
|
||||
const char *label() volatile { return string; }
|
||||
|
||||
void setLine(uint32_t line) volatile { JS_ASSERT(!js()); idx = line; }
|
||||
void setPC(jsbytecode *pc) volatile;
|
||||
void setLabel(const char *string) volatile { this->string = string; }
|
||||
void setStackAddress(void *sp) volatile { this->sp = sp; }
|
||||
void setScript(JSScript *script) volatile { script_ = script; }
|
||||
|
||||
/* we can't know the layout of JSScript, so look in vm/SPSProfiler.cpp */
|
||||
JS_FRIEND_API(jsbytecode *) pc() volatile;
|
||||
JS_FRIEND_API(void) setPC(jsbytecode *pc) volatile;
|
||||
|
||||
static size_t offsetOfString() { return offsetof(ProfileEntry, string); }
|
||||
static size_t offsetOfStackAddress() { return offsetof(ProfileEntry, sp); }
|
||||
static size_t offsetOfPCIdx() { return offsetof(ProfileEntry, idx); }
|
||||
|
@ -2393,6 +2393,8 @@ BEGIN_CASE(JSOP_NEW)
|
||||
BEGIN_CASE(JSOP_CALL)
|
||||
BEGIN_CASE(JSOP_FUNCALL)
|
||||
{
|
||||
if (regs.fp()->hasPushedSPSFrame())
|
||||
cx->runtime->spsProfiler.updatePC(script, regs.pc);
|
||||
JS_ASSERT(regs.stackDepth() >= 2 + GET_ARGC(regs.pc));
|
||||
CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
|
||||
|
||||
|
@ -23,6 +23,8 @@
|
||||
namespace js {
|
||||
namespace mjit {
|
||||
|
||||
class Assembler;
|
||||
|
||||
// Represents an int32_t property name in generated code, which must be either
|
||||
// a RegisterID or a constant value.
|
||||
struct Int32Key {
|
||||
@ -76,6 +78,128 @@ struct StackMarker {
|
||||
{ }
|
||||
};
|
||||
|
||||
/*
|
||||
* SPS is the profiling backend used by the JS engine to enable time profiling.
|
||||
* More information can be found in vm/SPSProfiler.{h,cpp}. This class manages
|
||||
* the instrumentation portion of the profiling for JIT code.
|
||||
*
|
||||
* The instrumentation tracks entry into functions, leaving those functions via
|
||||
* a function call, reentering the functions from a function call, and exiting
|
||||
* the functions from returning. This class also handles inline frames and
|
||||
* manages the instrumentation which needs to be attached to them as well.
|
||||
*
|
||||
* The basic methods which emit instrumentation are at the end of this class,
|
||||
* and the management functions are all described in the middle.
|
||||
*/
|
||||
class SPSInstrumentation {
|
||||
typedef JSC::MacroAssembler::RegisterID RegisterID;
|
||||
|
||||
/* Because of inline frames, this is a nested structure in a vector */
|
||||
struct FrameState {
|
||||
bool pushed; // has sps pushed a frame yet?
|
||||
bool skipNext; // should the next call to reenter be skipped?
|
||||
int left; // number of leave() calls made without a matching reenter()
|
||||
};
|
||||
|
||||
SPSProfiler *profiler_; // Instrumentation location management
|
||||
JSScript **script_; // Used from Compiler.cpp
|
||||
jsbytecode **pc_; // same purpose as script_
|
||||
VMFrame *vmframe; // Used in PolyIC/MonoIC compilations
|
||||
|
||||
Vector<FrameState, 1, SystemAllocPolicy> frames;
|
||||
FrameState *frame;
|
||||
|
||||
/*
|
||||
* When the instrumentation pushes some information, it needs to know about
|
||||
* the script/pc current in play. When originally compiling via
|
||||
* Compiler.cpp, the script and pc change rapidly, hence the **. During a
|
||||
* recompilation or some form of IC, the script/pc don't change, hence using
|
||||
* the VMFrame as the source of this information.
|
||||
*/
|
||||
JSScript *script() { return script_ ? *script_ : vmframe->script(); }
|
||||
jsbytecode *pc() { return pc_ ? *pc_ : vmframe->pc(); }
|
||||
|
||||
public:
|
||||
/* Constructor meant to be used from the compilers */
|
||||
SPSInstrumentation(SPSProfiler *profiler, JSScript **script, jsbytecode **pc)
|
||||
: profiler_(profiler),
|
||||
script_(script),
|
||||
pc_(pc),
|
||||
vmframe(NULL),
|
||||
frame(NULL)
|
||||
{
|
||||
enterInlineFrame();
|
||||
}
|
||||
|
||||
/* Constructor used for recompilations and ICs */
|
||||
SPSInstrumentation(VMFrame *f)
|
||||
: profiler_(&f->cx->runtime->spsProfiler),
|
||||
script_(NULL),
|
||||
pc_(NULL),
|
||||
vmframe(f),
|
||||
frame(NULL)
|
||||
{
|
||||
enterInlineFrame();
|
||||
setPushed();
|
||||
}
|
||||
|
||||
/* Small proxies around SPSProfiler */
|
||||
bool enabled() { return profiler_ && profiler_->enabled(); }
|
||||
SPSProfiler *profiler() { JS_ASSERT(enabled()); return profiler_; }
|
||||
bool slowAssertions() { return enabled() && profiler_->slowAssertionsEnabled(); }
|
||||
|
||||
/* Signals an inline function returned, reverting to the previous state */
|
||||
void leaveInlineFrame() {
|
||||
if (!enabled())
|
||||
return;
|
||||
frames.shrinkBy(1);
|
||||
JS_ASSERT(frames.length() > 0);
|
||||
frame = &frames[frames.length() - 1];
|
||||
}
|
||||
|
||||
/* Saves the current state and assumes a fresh one for the inline function */
|
||||
bool enterInlineFrame() {
|
||||
if (!enabled())
|
||||
return true;
|
||||
if (!frames.growBy(1))
|
||||
return false;
|
||||
frame = &frames[frames.length() - 1];
|
||||
frame->pushed = frame->skipNext = false;
|
||||
frame->left = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* When debugging or with slow assertions, sometimes a C++ method will be
|
||||
* invoked to perform the pop operation from the SPS stack. When we leave
|
||||
* JIT code, we need to record the current PC, but upon reentering JIT code,
|
||||
* no update back to NULL should happen. This method exists to flag this
|
||||
* behavior. The next leave() will emit instrumentation, but the following
|
||||
* reenter() will be a no-op.
|
||||
*/
|
||||
void skipNextReenter() {
|
||||
JS_ASSERT(!frame->skipNext && frame->left == 0);
|
||||
frame->skipNext = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* In some cases, a frame needs to be flagged as having been pushed, but no
|
||||
* instrumentation should be emitted. This updates internal state to flag
|
||||
* that further instrumentation should actually be emitted.
|
||||
*/
|
||||
void setPushed() {
|
||||
JS_ASSERT(!frame->pushed);
|
||||
frame->pushed = true;
|
||||
}
|
||||
|
||||
/* Actual instrumentation emitters, for more information see below */
|
||||
bool push(JSContext *cx, Assembler &masm, RegisterID scratch);
|
||||
void pushManual(Assembler &masm, RegisterID scratch);
|
||||
void leave(Assembler &masm, RegisterID scratch);
|
||||
void reenter(Assembler &masm, RegisterID scratch);
|
||||
void pop(Assembler &masm);
|
||||
};
|
||||
|
||||
class Assembler : public ValueAssembler
|
||||
{
|
||||
struct CallPatch {
|
||||
@ -116,15 +240,20 @@ class Assembler : public ValueAssembler
|
||||
bool callIsAligned;
|
||||
#endif
|
||||
|
||||
// When instrumentation is enabled, these fields are used to manage the
|
||||
// instrumentation which occurs at call() locations
|
||||
SPSInstrumentation *sps;
|
||||
|
||||
public:
|
||||
Assembler()
|
||||
Assembler(SPSInstrumentation *sps = NULL)
|
||||
: callPatches(SystemAllocPolicy()),
|
||||
availInCall(0),
|
||||
extraStackSpace(0),
|
||||
stackAdjust(0)
|
||||
stackAdjust(0),
|
||||
#ifdef DEBUG
|
||||
, callIsAligned(false)
|
||||
callIsAligned(false),
|
||||
#endif
|
||||
sps(sps)
|
||||
{
|
||||
startLabel = label();
|
||||
}
|
||||
@ -574,7 +703,17 @@ static const JSC::MacroAssembler::RegisterID JSParamReg_Argc = JSC::MIPSRegiste
|
||||
|
||||
JS_ASSERT(callIsAligned);
|
||||
|
||||
Call cl = call();
|
||||
Call cl;
|
||||
if (sps && sps->enabled()) {
|
||||
RegisterID reg = availInCall.takeAnyReg().reg();
|
||||
sps->leave(*this, reg);
|
||||
cl = call();
|
||||
sps->reenter(*this, reg);
|
||||
availInCall.putReg(reg);
|
||||
} else {
|
||||
cl = call();
|
||||
}
|
||||
|
||||
callPatches.append(CallPatch(cl, fun));
|
||||
#ifdef JS_CPU_ARM
|
||||
JS_ASSERT(initFlushCount == flushCount());
|
||||
@ -1371,6 +1510,49 @@ static const JSC::MacroAssembler::RegisterID JSParamReg_Argc = JSC::MIPSRegiste
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/*
|
||||
* Performs address arithmetic to return the base of the ProfileEntry into
|
||||
* the register provided. The Jump returned is taken if the SPS stack is
|
||||
* overflowing and no data should be written to it.
|
||||
*/
|
||||
Jump spsProfileEntryAddress(SPSProfiler *p, int offset, RegisterID reg)
|
||||
{
|
||||
load32(p->size(), reg);
|
||||
if (offset != 0)
|
||||
add32(Imm32(offset), reg);
|
||||
Jump j = branch32(Assembler::GreaterThanOrEqual, reg, Imm32(p->maxSize()));
|
||||
JS_STATIC_ASSERT(sizeof(ProfileEntry) == 4 * sizeof(void*));
|
||||
// 4 * sizeof(void*) * idx = idx << (2 + log(sizeof(void*)))
|
||||
lshift32(Imm32(2 + (sizeof(void*) == 4 ? 2 : 3)), reg);
|
||||
addPtr(ImmPtr(p->stack()), reg);
|
||||
return j;
|
||||
}
|
||||
|
||||
public:
|
||||
void spsUpdatePCIdx(SPSProfiler *p, uint32_t idx, RegisterID reg) {
|
||||
Jump j = spsProfileEntryAddress(p, -1, reg);
|
||||
store32(Imm32(idx), Address(reg, ProfileEntry::offsetOfPCIdx()));
|
||||
j.linkTo(label(), this);
|
||||
}
|
||||
|
||||
void spsPushFrame(SPSProfiler *p, const char *str, JSScript *s, RegisterID reg) {
|
||||
Jump j = spsProfileEntryAddress(p, 0, reg);
|
||||
|
||||
storePtr(ImmPtr(str), Address(reg, ProfileEntry::offsetOfString()));
|
||||
storePtr(ImmPtr(s), Address(reg, ProfileEntry::offsetOfScript()));
|
||||
storePtr(ImmPtr(NULL), Address(reg, ProfileEntry::offsetOfStackAddress()));
|
||||
store32(Imm32(0), Address(reg, ProfileEntry::offsetOfPCIdx()));
|
||||
|
||||
/* Always increment the stack size, regardless if we actually pushed */
|
||||
j.linkTo(label(), this);
|
||||
add32(Imm32(1), AbsoluteAddress(p->size()));
|
||||
}
|
||||
|
||||
void spsPopFrame(SPSProfiler *p) {
|
||||
sub32(Imm32(1), AbsoluteAddress(p->size()));
|
||||
}
|
||||
|
||||
static const double oneDouble;
|
||||
};
|
||||
|
||||
@ -1418,6 +1600,86 @@ class PreserveRegisters {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Flags entry into a JS function for the first time. Before this is called, no
|
||||
* instrumentation is emitted, but after this instrumentation is emitted.
|
||||
*/
|
||||
inline bool
|
||||
SPSInstrumentation::push(JSContext *cx, Assembler &masm, RegisterID scratch)
|
||||
{
|
||||
JS_ASSERT(!frame->pushed);
|
||||
JS_ASSERT(frame->left == 0);
|
||||
if (!enabled())
|
||||
return true;
|
||||
JSScript *s = script();
|
||||
const char *string = profiler_->profileString(cx, s, s->function());
|
||||
if (string == NULL)
|
||||
return false;
|
||||
masm.spsPushFrame(profiler_, string, script(), scratch);
|
||||
frame->pushed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Signifies that C++ performed the push() for this function. C++ always sets
|
||||
* the current PC to something non-null, however, so as soon as JIT code is
|
||||
* reentered this updates the current pc to NULL.
|
||||
*/
|
||||
inline void
|
||||
SPSInstrumentation::pushManual(Assembler &masm, RegisterID scratch)
|
||||
{
|
||||
JS_ASSERT(!frame->pushed);
|
||||
JS_ASSERT(frame->left == 0);
|
||||
if (!enabled())
|
||||
return;
|
||||
masm.spsUpdatePCIdx(profiler_, 0, scratch);
|
||||
frame->pushed = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Signals that the current function is leaving for a function call. This can
|
||||
* happen both on JS function calls and also calls to C++. This internally
|
||||
* manages how many leave() calls have been seen, and only the first leave()
|
||||
* emits instrumentation. Similarly, only the last corresponding reenter()
|
||||
* actually emits instrumentation.
|
||||
*/
|
||||
inline void
|
||||
SPSInstrumentation::leave(Assembler &masm, RegisterID scratch)
|
||||
{
|
||||
if (enabled() && frame->pushed && frame->left++ == 0)
|
||||
masm.spsUpdatePCIdx(profiler_, pc() - script()->code, scratch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Flags that the leaving of the current function has returned. This tracks
|
||||
* state with leave() to only emit instrumentation at proper times.
|
||||
*/
|
||||
inline void
|
||||
SPSInstrumentation::reenter(Assembler &masm, RegisterID scratch)
|
||||
{
|
||||
if (!enabled() || !frame->pushed || frame->left-- != 1)
|
||||
return;
|
||||
if (frame->skipNext)
|
||||
frame->skipNext = false;
|
||||
else
|
||||
masm.spsUpdatePCIdx(profiler_, 0, scratch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Signifies exiting a JS frame, popping the SPS entry. Because there can be
|
||||
* multiple return sites of a function, this does not cease instrumentation
|
||||
* emission.
|
||||
*/
|
||||
inline void
|
||||
SPSInstrumentation::pop(Assembler &masm)
|
||||
{
|
||||
if (enabled()) {
|
||||
JS_ASSERT(frame->left == 0);
|
||||
JS_ASSERT(frame->pushed);
|
||||
masm.spsPopFrame(profiler_);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace mjit */
|
||||
} /* namespace js */
|
||||
|
||||
|
@ -61,6 +61,8 @@ mjit::Compiler::Compiler(JSContext *cx, JSScript *outerScript,
|
||||
ssa(cx, outerScript),
|
||||
globalObj(cx, outerScript->hasGlobal() ? &outerScript->global() : NULL),
|
||||
globalSlots(globalObj ? globalObj->getRawSlots() : NULL),
|
||||
sps(&cx->runtime->spsProfiler, &script, &PC),
|
||||
masm(&sps),
|
||||
frame(cx, *thisFromCtor(), masm, stubcc),
|
||||
a(NULL), outer(NULL), script(NULL), PC(NULL), loop(NULL),
|
||||
inlineFrames(CompilerAllocPolicy(cx, *thisFromCtor())),
|
||||
@ -447,6 +449,9 @@ mjit::Compiler::pushActiveFrame(JSScript *script, uint32_t argc)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!sps.enterInlineFrame())
|
||||
return Compile_Error;
|
||||
|
||||
this->script = script;
|
||||
this->analysis = newAnalysis;
|
||||
this->PC = script->code;
|
||||
@ -467,6 +472,7 @@ mjit::Compiler::popActiveFrame()
|
||||
this->analysis = this->script->analysis();
|
||||
|
||||
frame.popActiveFrame();
|
||||
sps.leaveInlineFrame();
|
||||
}
|
||||
|
||||
#define CHECK_STATUS(expr) \
|
||||
@ -524,6 +530,8 @@ mjit::Compiler::performCompilation()
|
||||
|
||||
if (chunkIndex == 0)
|
||||
CHECK_STATUS(generatePrologue());
|
||||
else
|
||||
sps.setPushed();
|
||||
CHECK_STATUS(generateMethod());
|
||||
if (outerJIT() && chunkIndex == outerJIT()->nchunks - 1)
|
||||
CHECK_STATUS(generateEpilogue());
|
||||
@ -883,9 +891,12 @@ MakeJITScript(JSContext *cx, JSScript *script)
|
||||
}
|
||||
|
||||
/* Generate a pool with all cross chunk shims, and set shimLabel for each edge. */
|
||||
Assembler masm;
|
||||
jsbytecode *pc;
|
||||
SPSInstrumentation sps(&cx->runtime->spsProfiler, &script, &pc);
|
||||
Assembler masm(&sps);
|
||||
sps.setPushed();
|
||||
for (unsigned i = 0; i < jit->nedges; i++) {
|
||||
jsbytecode *pc = script->code + jitEdges[i].target;
|
||||
pc = script->code + jitEdges[i].target;
|
||||
jitEdges[i].shimLabel = (void *) masm.distanceOf(masm.label());
|
||||
masm.move(JSC::MacroAssembler::ImmPtr(&jitEdges[i]), Registers::ArgReg1);
|
||||
masm.fallibleVMCall(true, JS_FUNC_TO_DATA_PTR(void *, stubs::CrossChunkShim),
|
||||
@ -3789,6 +3800,7 @@ mjit::Compiler::emitReturn(FrameEntry *fe)
|
||||
|
||||
/* Inline StackFrame::epilogue. */
|
||||
if (debugMode()) {
|
||||
sps.skipNextReenter();
|
||||
prepareStubCall(Uses(0));
|
||||
INLINE_STUBCALL(stubs::Epilogue, REJOIN_NONE);
|
||||
} else {
|
||||
@ -3892,51 +3904,38 @@ mjit::Compiler::methodEntryHelper()
|
||||
if (debugMode()) {
|
||||
prepareStubCall(Uses(0));
|
||||
INLINE_STUBCALL(stubs::ScriptDebugPrologue, REJOIN_RESUME);
|
||||
} else if (Probes::callTrackingActive(cx)) {
|
||||
|
||||
/*
|
||||
* If necessary, call the tracking probe to trigger SPS assertions. We can
|
||||
* only do this when not inlining because the same StackFrame instance will
|
||||
* be used to enter a function, triggering an assertion in enterScript
|
||||
*/
|
||||
} else if (Probes::callTrackingActive(cx) ||
|
||||
(sps.slowAssertions() && a->inlineIndex == UINT32_MAX)) {
|
||||
prepareStubCall(Uses(0));
|
||||
INLINE_STUBCALL(stubs::ScriptProbeOnlyPrologue, REJOIN_RESUME);
|
||||
} else {
|
||||
return profilingPushHelper();
|
||||
}
|
||||
/* Ensure that we've flagged that the push has happened */
|
||||
if (sps.enabled()) {
|
||||
RegisterID reg = frame.allocReg();
|
||||
sps.pushManual(masm, reg);
|
||||
frame.freeReg(reg);
|
||||
}
|
||||
return Compile_Okay;
|
||||
}
|
||||
|
||||
CompileStatus
|
||||
mjit::Compiler::profilingPushHelper()
|
||||
{
|
||||
SPSProfiler *p = &cx->runtime->spsProfiler;
|
||||
if (!p->enabled())
|
||||
if (!sps.enabled())
|
||||
return Compile_Okay;
|
||||
/* If allocation fails, make sure no PopHelper() is emitted */
|
||||
const char *str = p->profileString(cx, script, script->function());
|
||||
if (str == NULL)
|
||||
RegisterID reg = frame.allocReg();
|
||||
if (!sps.push(cx, masm, reg))
|
||||
return Compile_Error;
|
||||
|
||||
/* Check if there's still space on the stack */
|
||||
RegisterID size = frame.allocReg();
|
||||
RegisterID base = frame.allocReg();
|
||||
masm.load32(p->size(), size);
|
||||
Jump j = masm.branch32(Assembler::GreaterThanOrEqual, size,
|
||||
Imm32(p->maxSize()));
|
||||
|
||||
/* With room, store our string onto the stack */
|
||||
masm.move(ImmPtr(p->stack()), base);
|
||||
JS_STATIC_ASSERT(sizeof(ProfileEntry) == 2 * sizeof(void*));
|
||||
masm.lshift32(Imm32(sizeof(void*) == 4 ? 3 : 4), size);
|
||||
masm.addPtr(size, base);
|
||||
|
||||
masm.storePtr(ImmPtr(str), Address(base, offsetof(ProfileEntry, string)));
|
||||
masm.storePtr(ImmPtr(NULL), Address(base, offsetof(ProfileEntry, sp)));
|
||||
|
||||
frame.freeReg(base);
|
||||
frame.freeReg(size);
|
||||
|
||||
/* Always increment the stack size (paired with a decrement below) */
|
||||
j.linkTo(masm.label(), &masm);
|
||||
masm.add32(Imm32(1), AbsoluteAddress(p->size()));
|
||||
|
||||
/* Set the flags that we've pushed information onto the SPS stack */
|
||||
RegisterID reg = frame.allocReg();
|
||||
masm.load32(FrameFlagsAddress(), reg);
|
||||
masm.or32(Imm32(StackFrame::HAS_PUSHED_SPS_FRAME), reg);
|
||||
masm.store32(reg, FrameFlagsAddress());
|
||||
@ -3948,13 +3947,12 @@ mjit::Compiler::profilingPushHelper()
|
||||
void
|
||||
mjit::Compiler::profilingPopHelper()
|
||||
{
|
||||
if (Probes::callTrackingActive(cx) ||
|
||||
cx->runtime->spsProfiler.slowAssertionsEnabled())
|
||||
{
|
||||
if (Probes::callTrackingActive(cx) || sps.slowAssertions()) {
|
||||
sps.skipNextReenter();
|
||||
prepareStubCall(Uses(0));
|
||||
INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME);
|
||||
} else if (cx->runtime->spsProfiler.enabled()) {
|
||||
masm.sub32(Imm32(1), AbsoluteAddress(cx->runtime->spsProfiler.size()));
|
||||
} else {
|
||||
sps.pop(masm);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4007,6 +4005,11 @@ mjit::Compiler::emitUncachedCall(uint32_t argc, bool callingNew)
|
||||
callPatches.append(callPatch);
|
||||
|
||||
finishBarrier(barrier, REJOIN_FALLTHROUGH, 0);
|
||||
if (sps.enabled()) {
|
||||
RegisterID reg = frame.allocReg();
|
||||
sps.reenter(masm, reg);
|
||||
frame.freeReg(reg);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -4076,6 +4079,11 @@ mjit::Compiler::inlineCallHelper(uint32_t argc, bool callingNew, FrameSize &call
|
||||
* do the interrupt check at the start of the JSOP_ARGUMENTS.
|
||||
*/
|
||||
interruptCheckHelper();
|
||||
if (sps.enabled()) {
|
||||
RegisterID reg = frame.allocReg();
|
||||
sps.leave(masm, reg);
|
||||
frame.freeReg(reg);
|
||||
}
|
||||
|
||||
FrameEntry *origCallee = frame.peek(-(int(argc) + 2));
|
||||
FrameEntry *origThis = frame.peek(-(int(argc) + 1));
|
||||
@ -4401,6 +4409,11 @@ mjit::Compiler::inlineCallHelper(uint32_t argc, bool callingNew, FrameSize &call
|
||||
callPatches.append(uncachedCallPatch);
|
||||
|
||||
finishBarrier(barrier, REJOIN_FALLTHROUGH, 0);
|
||||
if (sps.enabled()) {
|
||||
RegisterID reg = frame.allocReg();
|
||||
sps.reenter(masm, reg);
|
||||
frame.freeReg(reg);
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
@ -4428,6 +4441,12 @@ mjit::Compiler::inlineScriptedFunction(uint32_t argc, bool callingNew)
|
||||
if (inlineCallees.empty())
|
||||
return Compile_InlineAbort;
|
||||
|
||||
if (sps.enabled()) {
|
||||
RegisterID reg = frame.allocReg();
|
||||
sps.leave(masm, reg);
|
||||
frame.freeReg(reg);
|
||||
}
|
||||
|
||||
JS_ASSERT(!monitored(PC));
|
||||
|
||||
/*
|
||||
@ -4576,6 +4595,12 @@ mjit::Compiler::inlineScriptedFunction(uint32_t argc, bool callingNew)
|
||||
JaegerSpew(JSpew_Inlining, "finished inlining call to script (file \"%s\") (line \"%d\")\n",
|
||||
script->filename, script->lineno);
|
||||
|
||||
if (sps.enabled()) {
|
||||
RegisterID reg = frame.allocReg();
|
||||
sps.reenter(masm, reg);
|
||||
frame.freeReg(reg);
|
||||
}
|
||||
|
||||
return Compile_Okay;
|
||||
}
|
||||
|
||||
|
@ -368,6 +368,7 @@ class Compiler : public BaseCompiler
|
||||
Rooted<GlobalObject*> globalObj;
|
||||
const HeapSlot *globalSlots; /* Original slots pointer. */
|
||||
|
||||
SPSInstrumentation sps;
|
||||
Assembler masm;
|
||||
FrameState frame;
|
||||
|
||||
|
@ -342,7 +342,8 @@ class EqualityCompiler : public BaseCompiler
|
||||
bool update()
|
||||
{
|
||||
if (!ic.generated) {
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
Value rval = f.regs.sp[-1];
|
||||
Value lval = f.regs.sp[-2];
|
||||
|
||||
|
@ -257,7 +257,8 @@ class SetPropCompiler : public PICStubCompiler
|
||||
Vector<Jump, 8> slowExits(cx);
|
||||
Vector<Jump, 8> otherGuards(cx);
|
||||
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
// Shape guard.
|
||||
if (pic.shapeNeedsRemat()) {
|
||||
@ -776,7 +777,8 @@ class GetPropCompiler : public PICStubCompiler
|
||||
|
||||
LookupStatus generateArrayLengthStub()
|
||||
{
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
masm.loadObjClass(pic.objReg, pic.shapeReg);
|
||||
Jump isDense = masm.testClass(Assembler::Equal, pic.shapeReg, &ArrayClass);
|
||||
@ -817,7 +819,8 @@ class GetPropCompiler : public PICStubCompiler
|
||||
|
||||
LookupStatus generateStringObjLengthStub()
|
||||
{
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
Jump notStringObj = masm.guardShape(pic.objReg, obj);
|
||||
|
||||
@ -877,7 +880,8 @@ class GetPropCompiler : public PICStubCompiler
|
||||
if (hadGC())
|
||||
return Lookup_Uncacheable;
|
||||
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
/* Only strings are allowed. */
|
||||
Jump notString = masm.branchPtr(Assembler::NotEqual, pic.typeReg(),
|
||||
@ -931,7 +935,8 @@ class GetPropCompiler : public PICStubCompiler
|
||||
{
|
||||
JS_ASSERT(pic.hasTypeCheck());
|
||||
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
Jump notString = masm.branchPtr(Assembler::NotEqual, pic.typeReg(),
|
||||
ImmType(JSVAL_TYPE_STRING));
|
||||
masm.loadPtr(Address(pic.objReg, JSString::offsetOfLengthAndFlags()), pic.objReg);
|
||||
@ -1208,7 +1213,8 @@ class GetPropCompiler : public PICStubCompiler
|
||||
{
|
||||
Vector<Jump, 8> shapeMismatches(cx);
|
||||
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
// Ignore GC pointers baked into assembly visible on the stack.
|
||||
SkipRoot skip(cx, &masm);
|
||||
@ -1542,7 +1548,8 @@ class ScopeNameCompiler : public PICStubCompiler
|
||||
|
||||
LookupStatus generateGlobalStub(JSObject *obj)
|
||||
{
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
JumpList fails(cx);
|
||||
ScopeNameLabels &labels = pic.scopeNameLabels();
|
||||
|
||||
@ -1614,7 +1621,8 @@ class ScopeNameCompiler : public PICStubCompiler
|
||||
|
||||
LookupStatus generateCallStub(JSObject *obj)
|
||||
{
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
Vector<Jump, 8> fails(cx);
|
||||
ScopeNameLabels &labels = pic.scopeNameLabels();
|
||||
|
||||
@ -1800,7 +1808,8 @@ class BindNameCompiler : public PICStubCompiler
|
||||
|
||||
LookupStatus generateStub(JSObject *obj)
|
||||
{
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
Vector<Jump, 8> fails(cx);
|
||||
|
||||
BindNameLabels &labels = pic.bindNameLabels();
|
||||
@ -2246,7 +2255,8 @@ GetElementIC::attachGetProp(VMFrame &f, HandleObject obj, HandleValue v, HandleP
|
||||
if (cx->typeInferenceEnabled() && !forcedTypeBarrier)
|
||||
return disable(f, "string element access may not have type barrier");
|
||||
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
// Guard on the string's type and identity.
|
||||
MaybeJump atomTypeGuard;
|
||||
@ -2417,7 +2427,8 @@ GetElementIC::attachTypedArray(VMFrame &f, HandleObject obj, HandleValue v, Hand
|
||||
// known to be int32, either via type inference or the inline type check.
|
||||
JS_ASSERT(hasInlineTypeGuard() || idRemat.knownType() == JSVAL_TYPE_INT32);
|
||||
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
// Guard on this typed array's shape/class.
|
||||
Jump shapeGuard = masm.guardShape(objReg, obj);
|
||||
@ -2644,7 +2655,8 @@ SetElementIC::attachHoleStub(VMFrame &f, JSObject *obj, int32_t keyval)
|
||||
if (js_PrototypeHasIndexedProperties(cx, obj))
|
||||
return disable(f, "prototype has indexed properties");
|
||||
|
||||
Assembler masm;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
Vector<Jump, 8> fails(cx);
|
||||
|
||||
@ -2738,8 +2750,9 @@ SetElementIC::attachTypedArray(VMFrame &f, JSObject *obj, int32_t key)
|
||||
// Right now, only one shape guard extension is supported.
|
||||
JS_ASSERT(!inlineShapeGuardPatched);
|
||||
|
||||
Assembler masm;
|
||||
JSContext *cx = f.cx;
|
||||
SPSInstrumentation sps(&f);
|
||||
Assembler masm(&sps);
|
||||
|
||||
// Restore |obj|.
|
||||
masm.rematPayload(StateRemat::FromInt32(objRemat), objReg);
|
||||
|
@ -18,6 +18,7 @@ StubCompiler::StubCompiler(JSContext *cx, mjit::Compiler &cc, FrameState &frame)
|
||||
: cx(cx),
|
||||
cc(cc),
|
||||
frame(frame),
|
||||
masm(&cc.sps),
|
||||
generation(1),
|
||||
lastGeneration(0),
|
||||
exits(CompilerAllocPolicy(cx, cc)),
|
||||
|
@ -6,17 +6,29 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "jsnum.h"
|
||||
#include "jsscript.h"
|
||||
|
||||
#include "vm/SPSProfiler.h"
|
||||
#include "vm/StringBuffer.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
SPSProfiler::SPSProfiler(JSRuntime *rt)
|
||||
: rt(rt),
|
||||
stack_(NULL),
|
||||
size_(NULL),
|
||||
max_(0),
|
||||
slowAssertions(false),
|
||||
enabled_(false)
|
||||
{
|
||||
JS_ASSERT(rt != NULL);
|
||||
}
|
||||
|
||||
SPSProfiler::~SPSProfiler()
|
||||
{
|
||||
if (strings.initialized()) {
|
||||
for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront())
|
||||
js_free((void*) e.front().value);
|
||||
rt->array_delete(e.front().value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +67,7 @@ SPSProfiler::profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
if (!strings.add(s, script, str)) {
|
||||
js_free((void*) str);
|
||||
rt->array_delete(str);
|
||||
return NULL;
|
||||
}
|
||||
return str;
|
||||
@ -76,7 +88,7 @@ SPSProfiler::onScriptFinalized(JSScript *script)
|
||||
if (ProfileStringMap::Ptr entry = strings.lookup(script)) {
|
||||
const char *tofree = entry->value;
|
||||
strings.remove(entry);
|
||||
js_free((void*) tofree);
|
||||
rt->array_delete(tofree);
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +99,9 @@ SPSProfiler::enter(JSContext *cx, JSScript *script, JSFunction *maybeFun)
|
||||
if (str == NULL)
|
||||
return false;
|
||||
|
||||
push(str, NULL);
|
||||
JS_ASSERT_IF(*size_ > 0 && *size_ - 1 < max_ && stack_[*size_ - 1].js(),
|
||||
stack_[*size_ - 1].pc() != NULL);
|
||||
push(str, NULL, script, script->code);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -102,22 +116,31 @@ SPSProfiler::exit(JSContext *cx, JSScript *script, JSFunction *maybeFun)
|
||||
const char *str = profileString(cx, script, maybeFun);
|
||||
/* Can't fail lookup because we should already be in the set */
|
||||
JS_ASSERT(str != NULL);
|
||||
JS_ASSERT(strcmp((const char*) stack_[*size_].string, str) == 0);
|
||||
stack_[*size_].string = NULL;
|
||||
stack_[*size_].sp = NULL;
|
||||
JS_ASSERT(stack_[*size_].js());
|
||||
JS_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0);
|
||||
stack_[*size_].setLabel(NULL);
|
||||
stack_[*size_].setPC(NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
SPSProfiler::push(const char *string, void *sp)
|
||||
SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc)
|
||||
{
|
||||
/* these operations cannot be re-ordered, so volatile-ize operations */
|
||||
volatile ProfileEntry *stack = stack_;
|
||||
volatile uint32_t *size = size_;
|
||||
uint32_t current = *size;
|
||||
|
||||
JS_ASSERT(enabled());
|
||||
if (*size_ < max_) {
|
||||
stack_[*size_].string = string;
|
||||
stack_[*size_].sp = sp;
|
||||
if (current < max_) {
|
||||
stack[current].setLabel(string);
|
||||
stack[current].setStackAddress(sp);
|
||||
stack[current].setScript(script);
|
||||
if (pc != NULL)
|
||||
stack_[current].setPC(pc);
|
||||
}
|
||||
(*size_)++;
|
||||
*size = current + 1;
|
||||
}
|
||||
|
||||
void
|
||||
@ -160,7 +183,7 @@ SPSProfiler::allocProfileString(JSContext *cx, JSScript *script, JSFunction *may
|
||||
return NULL;
|
||||
|
||||
size_t len = buf.length();
|
||||
char *cstr = (char*) js_malloc(len + 1);
|
||||
char *cstr = rt->array_new<char>(len + 1);
|
||||
if (cstr == NULL)
|
||||
return NULL;
|
||||
|
||||
@ -181,7 +204,7 @@ SPSEntryMarker::SPSEntryMarker(JSRuntime *rt JS_GUARD_OBJECT_NOTIFIER_PARAM_NO_I
|
||||
profiler = NULL;
|
||||
return;
|
||||
}
|
||||
profiler->push("js::RunScript", this);
|
||||
profiler->push("js::RunScript", this, NULL, NULL);
|
||||
}
|
||||
|
||||
SPSEntryMarker::~SPSEntryMarker()
|
||||
@ -189,3 +212,13 @@ SPSEntryMarker::~SPSEntryMarker()
|
||||
if (profiler != NULL)
|
||||
profiler->pop();
|
||||
}
|
||||
|
||||
JS_FRIEND_API(jsbytecode*)
|
||||
ProfileEntry::pc() volatile {
|
||||
return script()->code + idx;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(void)
|
||||
ProfileEntry::setPC(jsbytecode *pc) volatile {
|
||||
idx = pc - script()->code;
|
||||
}
|
||||
|
@ -8,13 +8,10 @@
|
||||
#ifndef SPSProfiler_h__
|
||||
#define SPSProfiler_h__
|
||||
|
||||
#include "mozilla/HashFunctions.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
#include "jsfun.h"
|
||||
#include "jsscript.h"
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "js/Utility.h"
|
||||
|
||||
/*
|
||||
* SPS Profiler integration with the JS Engine
|
||||
@ -44,13 +41,14 @@
|
||||
* but it will still maintain the size of the stack. SPS code is aware of this
|
||||
* and iterates the stack accordingly.
|
||||
*
|
||||
* There are two pointers of information pushed on the SPS stack for every JS
|
||||
* function that is entered. First is a char* pointer of a description of what
|
||||
* function was entered. Currently this string is of the form
|
||||
* "function (file:line)" if there's a function name, or just "file:line" if
|
||||
* there's no function name available. The other bit of information is the
|
||||
* relevant C++ (native) stack pointer. This stack pointer is what enables the
|
||||
* interleaving of the C++ and the JS stack.
|
||||
* There is some information pushed on the SPS stack for every JS function that
|
||||
* is entered. First is a char* pointer of a description of what function was
|
||||
* entered. Currently this string is of the form "function (file:line)" if
|
||||
* there's a function name, or just "file:line" if there's no function name
|
||||
* available. The other bit of information is the relevant C++ (native) stack
|
||||
* pointer. This stack pointer is what enables the interleaving of the C++ and
|
||||
* the JS stack. Finally, throughout execution of the function, some extra
|
||||
* information may be updated on the ProfileEntry structure.
|
||||
*
|
||||
* = Profile Strings
|
||||
*
|
||||
@ -80,10 +78,35 @@
|
||||
* interleaving C++ and JS, if SPS sees a NULL native stack pointer on the SPS
|
||||
* stack, it looks backwards for the first non-NULL pointer and uses that for
|
||||
* all subsequent NULL native stack pointers.
|
||||
*
|
||||
* = Line Numbers
|
||||
*
|
||||
* One goal of sampling is to get both a backtrace of the JS stack, but also
|
||||
* know where within each function on the stack execution currently is. For
|
||||
* this, each ProfileEntry has a 'pc' field to tell where its execution
|
||||
* currently is. This field is updated whenever a call is made to another JS
|
||||
* function, and for the JIT it is also updated whenever the JIT is left.
|
||||
*
|
||||
* This field is in a union with a uint32_t 'line' so that C++ can make use of
|
||||
* the field as well. It was observed that tracking 'line' via PCToLineNumber in
|
||||
* JS was far too expensive, so that is why the pc instead of the translated
|
||||
* line number is stored.
|
||||
*
|
||||
* As an invariant, if the pc is NULL, then the JIT is currently executing
|
||||
* generated code. Otherwise execution is in another JS function or in C++. With
|
||||
* this in place, only the top entry of the stack can ever have NULL as its pc.
|
||||
* Additionally with this invariant, it is possible to maintain mappings of JIT
|
||||
* code to pc which can be accessed safely because they will only be accessed
|
||||
* from a signal handler when the JIT code is executing.
|
||||
*/
|
||||
|
||||
struct JSFunction;
|
||||
struct JSScript;
|
||||
|
||||
namespace js {
|
||||
|
||||
class ProfileEntry;
|
||||
|
||||
typedef HashMap<JSScript*, const char*, DefaultHasher<JSScript*>, SystemAllocPolicy>
|
||||
ProfileStringMap;
|
||||
|
||||
@ -101,33 +124,44 @@ class SPSProfiler
|
||||
bool slowAssertions;
|
||||
bool enabled_;
|
||||
|
||||
static const char *allocProfileString(JSContext *cx, JSScript *script,
|
||||
JSFunction *function);
|
||||
void push(const char *string, void *sp);
|
||||
const char *allocProfileString(JSContext *cx, JSScript *script,
|
||||
JSFunction *function);
|
||||
void push(const char *string, void *sp, JSScript *script, jsbytecode *pc);
|
||||
void pop();
|
||||
|
||||
public:
|
||||
SPSProfiler(JSRuntime *rt)
|
||||
: rt(rt),
|
||||
stack_(NULL),
|
||||
size_(NULL),
|
||||
max_(0),
|
||||
slowAssertions(false),
|
||||
enabled_(false)
|
||||
{}
|
||||
SPSProfiler(JSRuntime *rt);
|
||||
~SPSProfiler();
|
||||
|
||||
uint32_t *size() { return size_; }
|
||||
uint32_t maxSize() { return max_; }
|
||||
ProfileEntry *stack() { return stack_; }
|
||||
|
||||
/* management of whether instrumentation is on or off */
|
||||
bool enabled() { JS_ASSERT_IF(enabled_, installed()); return enabled_; }
|
||||
bool installed() { return stack_ != NULL && size_ != NULL; }
|
||||
void enable(bool enabled);
|
||||
void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
|
||||
bool slowAssertionsEnabled() { return slowAssertions; }
|
||||
|
||||
/*
|
||||
* Functions which are the actual instrumentation to track run information
|
||||
*
|
||||
* - enter: a function has started to execute
|
||||
* - updatePC: updates the pc information about where a function
|
||||
* is currently executing
|
||||
* - exit: this function has ceased execution, and no further
|
||||
* entries/exits will be made
|
||||
*/
|
||||
bool enter(JSContext *cx, JSScript *script, JSFunction *maybeFun);
|
||||
void exit(JSContext *cx, JSScript *script, JSFunction *maybeFun);
|
||||
void updatePC(JSScript *script, jsbytecode *pc) {
|
||||
if (enabled() && *size_ - 1 < max_) {
|
||||
JS_ASSERT(*size_ > 0);
|
||||
stack_[*size_ - 1].setPC(pc);
|
||||
}
|
||||
}
|
||||
|
||||
void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max);
|
||||
const char *profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun);
|
||||
void onScriptFinalized(JSScript *script);
|
||||
|
Loading…
Reference in New Issue
Block a user