Bug 778979 - Part 3: Track the line number in the JS engine for SPS profiling. r=bhackett

This commit is contained in:
Alex Crichton 2012-08-06 11:48:12 -07:00
parent 59d5ea8043
commit d4a2443740
11 changed files with 494 additions and 120 deletions

View File

@ -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;
}

View File

@ -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); }

View File

@ -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);

View File

@ -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 */

View File

@ -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;
}

View File

@ -368,6 +368,7 @@ class Compiler : public BaseCompiler
Rooted<GlobalObject*> globalObj;
const HeapSlot *globalSlots; /* Original slots pointer. */
SPSInstrumentation sps;
Assembler masm;
FrameState frame;

View File

@ -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];

View File

@ -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);

View File

@ -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)),

View File

@ -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;
}

View File

@ -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);