Bug 761261 - Add JS profiling to SPS (r=luke,ehsan)

--HG--
extra : rebase_source : 328a82697aa9a9f63d18c7a30a813f436e163922
This commit is contained in:
Alex Crichton 2012-06-20 17:58:55 -07:00
parent 0a379d77d8
commit f491495a41
23 changed files with 791 additions and 75 deletions

View File

@ -126,6 +126,7 @@ CPPSRCS = \
ParseNode.cpp \
Parser.cpp \
SemanticAnalysis.cpp \
SPSProfiler.cpp \
TokenStream.cpp \
TreeContext.cpp \
TestingFunctions.cpp \

View File

@ -389,8 +389,6 @@ js::DirectEval(JSContext *cx, const CallArgs &args)
JS_ASSERT(IsBuiltinEvalForScope(caller->scopeChain(), args.calleev()));
JS_ASSERT(JSOp(*cx->regs().pc) == JSOP_EVAL);
AutoFunctionCallProbe callProbe(cx, args.callee().toFunction(), caller->script());
if (!WarnOnTooManyArgs(cx, args))
return false;

View File

@ -63,6 +63,7 @@ CPPSRCS = \
testValueABI.cpp \
testVersion.cpp \
testXDR.cpp \
testProfileStrings.cpp \
$(NULL)
CSRCS = \

View File

@ -0,0 +1,215 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99:
*
* Tests the stack-based instrumentation profiler on a JSRuntime
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "tests.h"
#include "jscntxt.h"
static js::ProfileEntry stack[10];
static uint32_t size = 0;
static uint32_t max_stack = 0;
static void
reset(JSContext *cx)
{
size = max_stack = 0;
memset(stack, 0, sizeof(stack));
cx->runtime->spsProfiler.stringsReset();
}
static JSClass ptestClass = {
"Prof", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
};
static JSBool
test_fn(JSContext *cx, unsigned argc, jsval *vp)
{
max_stack = size;
return JS_TRUE;
}
static JSBool
test_fn2(JSContext *cx, unsigned argc, jsval *vp)
{
jsval r;
return JS_CallFunctionName(cx, JS_GetGlobalObject(cx), "d", 0, NULL, &r);
}
static JSBool
test_fn3(JSContext *cx, unsigned argc, jsval *vp)
{
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 10);
return JS_TRUE;
}
static JSBool
Prof(JSContext* cx, unsigned argc, jsval *vp)
{
JSObject *obj = JS_NewObjectForConstructor(cx, &ptestClass, vp);
if (!obj)
return JS_FALSE;
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj));
return JS_TRUE;
}
static JSFunctionSpec ptestFunctions[] = {
JS_FS("test_fn", test_fn, 0, 0),
JS_FS("test_fn2", test_fn2, 0, 0),
JS_FS("test_fn3", test_fn3, 0, 0),
JS_FS_END
};
static JSObject*
initialize(JSContext *cx)
{
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 10);
return JS_InitClass(cx, JS_GetGlobalObject(cx), NULL, &ptestClass, Prof, 0,
NULL, ptestFunctions, NULL, NULL);
}
BEGIN_TEST(testProfileStrings_isCalled)
{
CHECK(initialize(cx));
EXEC("function g() { var p = new Prof(); p.test_fn(); }");
EXEC("function f() { g(); }");
EXEC("function e() { f(); }");
EXEC("function d() { e(); }");
EXEC("function c() { d(); }");
EXEC("function b() { c(); }");
EXEC("function a() { b(); }");
EXEC("function check() { var p = new Prof(); p.test_fn(); a(); }");
EXEC("function check2() { var p = new Prof(); p.test_fn2(); }");
reset(cx);
{
jsvalRoot rval(cx);
/* Make sure the stack resets and we have an entry for each stack */
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK(size == 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.addr()));
CHECK(size == 0);
CHECK(max_stack == 9);
CHECK(cx->runtime->spsProfiler.stringsCount() == 8);
}
reset(cx);
{
jsvalRoot rval(cx);
CHECK(JS_CallFunctionName(cx, global, "check2", 0, NULL, rval.addr()));
CHECK(cx->runtime->spsProfiler.stringsCount() == 5);
CHECK(max_stack == 7);
CHECK(size == 0);
}
reset(cx);
{
jsvalRoot rval(cx);
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 3);
stack[3].string = (char*) 1234;
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK((size_t) stack[3].string == 1234);
CHECK(max_stack == 9);
CHECK(size == 0);
}
return true;
}
END_TEST(testProfileStrings_isCalled)
BEGIN_TEST(testProfileStrings_isCalledWithJIT)
{
CHECK(initialize(cx));
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_METHODJIT |
JSOPTION_METHODJIT_ALWAYS);
EXEC("function g() { var p = new Prof(); p.test_fn(); }");
EXEC("function f() { g(); }");
EXEC("function e() { f(); }");
EXEC("function d() { e(); }");
EXEC("function c() { d(); }");
EXEC("function b() { c(); }");
EXEC("function a() { b(); }");
EXEC("function check() { var p = new Prof(); p.test_fn(); a(); }");
EXEC("function check2() { var p = new Prof(); p.test_fn2(); }");
reset(cx);
{
jsvalRoot rval(cx);
/* Make sure the stack resets and we have an entry for each stack */
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK(size == 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.addr()));
CHECK(size == 0);
CHECK(cx->runtime->spsProfiler.stringsCount() == cnt);
CHECK(max_stack == 9);
}
reset(cx);
{
/* Limit the size of the stack and make sure we don't overflow */
jsvalRoot rval(cx);
js::SetRuntimeProfilingStack(cx->runtime, stack, &size, 3);
stack[3].string = (char*) 1234;
CHECK(JS_CallFunctionName(cx, global, "check", 0, NULL, rval.addr()));
CHECK(size == 0);
CHECK(max_stack == 9);
CHECK((size_t) stack[3].string == 1234);
}
return true;
}
END_TEST(testProfileStrings_isCalledWithJIT)
BEGIN_TEST(testProfileStrings_isCalledWhenError)
{
CHECK(initialize(cx));
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_METHODJIT |
JSOPTION_METHODJIT_ALWAYS);
EXEC("function check2() { throw 'a'; }");
reset(cx);
{
jsvalRoot rval(cx);
/* Make sure the stack resets and we have an entry for each stack */
JS_CallFunctionName(cx, global, "check2", 0, NULL, rval.addr());
CHECK(size == 0);
CHECK(cx->runtime->spsProfiler.stringsCount() == 1);
}
return true;
}
END_TEST(testProfileStrings_isCalledWhenError)
BEGIN_TEST(testProfileStrings_worksWhenEnabledOnTheFly)
{
CHECK(initialize(cx));
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_METHODJIT |
JSOPTION_METHODJIT_ALWAYS);
EXEC("function b() { }");
EXEC("function a() { var p = new Prof(); p.test_fn3(); b(); }");
reset(cx);
js::SetRuntimeProfilingStack(cx->runtime, NULL, NULL, 10);
{
jsvalRoot rval(cx);
JS_CallFunctionName(cx, global, "a", 0, NULL, rval.addr());
CHECK(size == 0);
CHECK(cx->runtime->spsProfiler.stringsCount() == 1);
}
return true;
}
END_TEST(testProfileStrings_worksWhenEnabledOnTheFly)

View File

@ -30,6 +30,7 @@
#include "js/HashTable.h"
#include "js/Vector.h"
#include "vm/Stack.h"
#include "vm/SPSProfiler.h"
#ifdef _MSC_VER
#pragma warning(push)
@ -690,6 +691,9 @@ struct JSRuntime : js::RuntimeFriendFields
/* If true, new compartments are initially in debug mode. */
bool debugMode;
/* SPS profiling metadata */
js::SPSProfiler spsProfiler;
/* If true, new scripts must be created with PC counter information. */
bool profilingScripts;

View File

@ -867,4 +867,12 @@ GetTestingFunctions(JSContext *cx)
return obj;
}
JS_FRIEND_API(void)
SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size,
uint32_t max)
{
rt->spsProfiler.setProfilingStack(stack, size, max);
ReleaseAllJITCode(rt->defaultFreeOp());
}
} // namespace js

View File

@ -524,6 +524,31 @@ GetPCCountScriptSummary(JSContext *cx, size_t script);
JS_FRIEND_API(JSString *)
GetPCCountScriptContents(JSContext *cx, size_t script);
/*
* A call stack can be specified to the JS engine such that all JS entry/exits
* to functions push/pop an entry to/from the specified stack.
*
* For more detailed information, see vm/SPSProfiler.h
*/
struct ProfileEntry {
/*
* These two fields are marked as 'volatile' so that the compiler doesn't
* re-order instructions which modify them. The operation in question is:
*
* stack[i].string = str;
* (*size)++;
*
* If the size increment were re-ordered before the store of the string,
* then if sampling occurred there would be a bogus entry on the stack.
*/
const char * volatile string;
void * volatile sp;
};
JS_FRIEND_API(void)
SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size,
uint32_t max);
#ifdef JS_THREADSAFE
JS_FRIEND_API(void *)
GetOwnerThread(const JSContext *cx);

View File

@ -4681,7 +4681,7 @@ MaybeVerifyBarriers(JSContext *cx, bool always)
} /* namespace gc */
static void ReleaseAllJITCode(FreeOp *fop)
void ReleaseAllJITCode(FreeOp *fop)
{
#ifdef JS_METHODJIT
for (CompartmentsIter c(fop->runtime()); !c.done(); c.next()) {

View File

@ -457,6 +457,9 @@ MaybeGC(JSContext *cx);
extern void
ShrinkGCBuffers(JSRuntime *rt);
extern void
ReleaseAllJITCode(FreeOp *op);
extern JS_FRIEND_API(void)
PrepareForFullGC(JSRuntime *rt);

View File

@ -285,6 +285,8 @@ js::RunScript(JSContext *cx, JSScript *script, StackFrame *fp)
} check(cx);
#endif
SPSEntryMarker marker(cx->runtime);
#ifdef JS_METHODJIT
mjit::CompileStatus status;
status = mjit::CanMethodJIT(cx, script, script->code, fp->isConstructing(),
@ -1306,8 +1308,10 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
* To support generator_throw and to catch ignored exceptions,
* fail if cx->isExceptionPending() is true.
*/
if (cx->isExceptionPending())
if (cx->isExceptionPending()) {
Probes::enterScript(cx, script, script->function(), regs.fp());
goto error;
}
}
#endif
@ -1317,8 +1321,12 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
/* Don't call the script prologue if executing between Method and Trace JIT. */
if (interpMode == JSINTERP_NORMAL) {
StackFrame *fp = regs.fp();
if (!fp->isGeneratorFrame() && !fp->prologue(cx, UseNewTypeAtEntry(cx, fp)))
goto error;
if (!fp->isGeneratorFrame()) {
if (!fp->prologue(cx, UseNewTypeAtEntry(cx, fp)))
goto error;
} else {
Probes::enterScript(cx, script, script->function(), fp);
}
if (cx->compartment->debugMode()) {
JSTrapStatus status = ScriptDebugPrologue(cx, fp);
switch (status) {
@ -1607,6 +1615,8 @@ BEGIN_CASE(JSOP_STOP)
if (!regs.fp()->isYielding())
regs.fp()->epilogue(cx);
else
Probes::exitScript(cx, script, script->function(), regs.fp());
/* The JIT inlines the epilogue. */
#ifdef JS_METHODJIT
@ -2513,7 +2523,6 @@ BEGIN_CASE(JSOP_FUNCALL)
if (!regs.fp()->prologue(cx, newType))
goto error;
if (cx->compartment->debugMode()) {
switch (ScriptDebugPrologue(cx, regs.fp())) {
case JSTRAP_CONTINUE:
@ -3974,6 +3983,8 @@ END_CASE(JSOP_ARRAYPUSH)
interpReturnOK = ScriptDebugEpilogue(cx, regs.fp(), interpReturnOK);
if (!regs.fp()->isYielding())
regs.fp()->epilogue(cx);
else
Probes::exitScript(cx, script, script->function(), regs.fp());
regs.fp()->setFinishedInInterpreter();
#ifdef JS_METHODJIT

View File

@ -92,10 +92,10 @@ bool callTrackingActive(JSContext *);
bool wantNativeAddressInfo(JSContext *);
/* Entering a JS function */
bool enterJSFun(JSContext *, JSFunction *, JSScript *, int counter = 1);
bool enterScript(JSContext *, JSScript *, JSFunction *, StackFrame *);
/* About to leave a JS function */
bool exitJSFun(JSContext *, JSFunction *, JSScript *, int counter = 0);
bool exitScript(JSContext *, JSScript *, JSFunction *, StackFrame *);
/* Executing a script */
bool startExecution(JSContext *cx, JSScript *script);
@ -303,8 +303,8 @@ void
discardExecutableRegion(void *start, size_t size);
/*
* Internal: DTrace-specific functions to be called during Probes::enterJSFun
* and Probes::exitJSFun. These will not be inlined, but the argument
* Internal: DTrace-specific functions to be called during Probes::enterScript
* and Probes::exitScript. These will not be inlined, but the argument
* marshalling required for these probe points is expensive enough that it
* shouldn't really matter.
*/
@ -380,43 +380,53 @@ Probes::wantNativeAddressInfo(JSContext *cx)
}
inline bool
Probes::enterJSFun(JSContext *cx, JSFunction *fun, JSScript *script, int counter)
Probes::enterScript(JSContext *cx, JSScript *script, JSFunction *maybeFun,
StackFrame *fp)
{
bool ok = true;
#ifdef INCLUDE_MOZILLA_DTRACE
if (JAVASCRIPT_FUNCTION_ENTRY_ENABLED())
DTraceEnterJSFun(cx, fun, script);
DTraceEnterJSFun(cx, maybeFun, script);
#endif
#ifdef MOZ_TRACE_JSCALLS
cx->doFunctionCallback(fun, script, counter);
cx->doFunctionCallback(maybeFun, script, 1);
#endif
#ifdef MOZ_ETW
if (ProfilingActive && !ETWEnterJSFun(cx, fun, script, counter))
if (ProfilingActive && !ETWEnterJSFun(cx, maybeFun, script, 1))
ok = false;
#endif
JSRuntime *rt = cx->runtime;
if (rt->spsProfiler.enabled()) {
rt->spsProfiler.enter(cx, script, maybeFun);
JS_ASSERT_IF(!fp->isGeneratorFrame(), !fp->hasPushedSPSFrame());
fp->setPushedSPSFrame();
}
return ok;
}
inline bool
Probes::exitJSFun(JSContext *cx, JSFunction *fun, JSScript *script, int counter)
Probes::exitScript(JSContext *cx, JSScript *script, JSFunction *maybeFun,
StackFrame *fp)
{
bool ok = true;
#ifdef INCLUDE_MOZILLA_DTRACE
if (JAVASCRIPT_FUNCTION_RETURN_ENABLED())
DTraceExitJSFun(cx, fun, script);
DTraceExitJSFun(cx, maybeFun, script);
#endif
#ifdef MOZ_TRACE_JSCALLS
if (counter > 0)
counter = -counter;
cx->doFunctionCallback(fun, script, counter);
cx->doFunctionCallback(maybeFun, script, 0);
#endif
#ifdef MOZ_ETW
if (ProfilingActive && !ETWExitJSFun(cx, fun, script, counter))
if (ProfilingActive && !ETWExitJSFun(cx, maybeFun, script, 0))
ok = false;
#endif
JSRuntime *rt = cx->runtime;
if (rt->spsProfiler.enabled() && fp->hasPushedSPSFrame())
rt->spsProfiler.exit(cx, script, maybeFun);
return ok;
}
@ -766,25 +776,6 @@ Probes::stopExecution(JSContext *cx, JSScript *script)
return ok;
}
struct AutoFunctionCallProbe {
JSContext * const cx;
JSFunction *fun;
JSScript *script;
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
AutoFunctionCallProbe(JSContext *cx, JSFunction *fun, JSScript *script
JS_GUARD_OBJECT_NOTIFIER_PARAM)
: cx(cx), fun(fun), script(script)
{
JS_GUARD_OBJECT_NOTIFIER_INIT;
Probes::enterJSFun(cx, fun, script);
}
~AutoFunctionCallProbe() {
Probes::exitJSFun(cx, fun, script);
}
};
} /* namespace js */
/*

View File

@ -1416,6 +1416,7 @@ JSScript::finalize(FreeOp *fop)
// fullyInitFromEmitter() or fullyInitTrivial().
CallDestroyScriptHook(fop, this);
fop->runtime()->spsProfiler.onScriptFinalized(this);
JS_ASSERT_IF(principals, originPrincipals);
if (principals)

View File

@ -1180,17 +1180,11 @@ mjit::Compiler::generatePrologue()
}
}
if (debugMode()) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptDebugPrologue, REJOIN_RESUME);
} else if (Probes::callTrackingActive(cx)) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptProbeOnlyPrologue, REJOIN_RESUME);
}
CompileStatus status = methodEntryHelper();
if (status == Compile_Okay)
recompileCheckHelper();
recompileCheckHelper();
return Compile_Okay;
return status;
}
void
@ -3746,7 +3740,7 @@ mjit::Compiler::emitReturn(FrameEntry *fe)
/* Only the top of the stack can be returned. */
JS_ASSERT_IF(fe, fe == frame.peek(-1));
if (debugMode() || Probes::callTrackingActive(cx)) {
if (debugMode()) {
/* If the return value isn't in the frame's rval slot, move it there. */
if (fe) {
frame.storeTo(fe, Address(JSFrameReg, StackFrame::offsetOfReturnValue()), true);
@ -3767,6 +3761,14 @@ mjit::Compiler::emitReturn(FrameEntry *fe)
}
if (a != outer) {
JS_ASSERT(!debugMode());
if (Probes::callTrackingActive(cx)) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME);
} else {
profilingPopHelper();
}
/*
* Returning from an inlined script. The checks we do for inlineability
* and recompilation triggered by args object construction ensure that
@ -3806,8 +3808,17 @@ mjit::Compiler::emitReturn(FrameEntry *fe)
if (debugMode()) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::Epilogue, REJOIN_NONE);
} else if (script->function() && script->nesting()) {
masm.sub32(Imm32(1), AbsoluteAddress(&script->nesting()->activeFrames));
} else {
if (Probes::callTrackingActive(cx)) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptProbeOnlyEpilogue, REJOIN_RESUME);
} else {
profilingPopHelper();
}
if (script->function() && script->nesting()) {
masm.sub32(Imm32(1), AbsoluteAddress(&script->nesting()->activeFrames));
}
}
emitReturnValue(&masm, fe);
@ -3899,6 +3910,73 @@ mjit::Compiler::recompileCheckHelper()
stubcc.rejoin(Changes(0));
}
CompileStatus
mjit::Compiler::methodEntryHelper()
{
if (debugMode()) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptDebugPrologue, REJOIN_RESUME);
} else if (Probes::callTrackingActive(cx)) {
prepareStubCall(Uses(0));
INLINE_STUBCALL(stubs::ScriptProbeOnlyPrologue, REJOIN_RESUME);
} else {
return profilingPushHelper();
}
return Compile_Okay;
}
CompileStatus
mjit::Compiler::profilingPushHelper()
{
SPSProfiler *p = &cx->runtime->spsProfiler;
if (!p->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)
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());
frame.freeReg(reg);
return Compile_Okay;
}
void
mjit::Compiler::profilingPopHelper()
{
if (!cx->runtime->spsProfiler.enabled())
return;
masm.sub32(Imm32(1), AbsoluteAddress(cx->runtime->spsProfiler.size()));
}
void
mjit::Compiler::addReturnSite()
{
@ -4462,7 +4540,10 @@ mjit::Compiler::inlineScriptedFunction(uint32_t argc, bool callingNew)
markUndefinedLocals();
status = generateMethod();
status = methodEntryHelper();
if (status == Compile_Okay)
status = generateMethod();
if (status != Compile_Okay) {
popActiveFrame();
if (status == Compile_Abort) {

View File

@ -637,6 +637,9 @@ private:
void dispatchCall(VoidPtrStubUInt32 stub, uint32_t argc);
void interruptCheckHelper();
void recompileCheckHelper();
CompileStatus methodEntryHelper();
CompileStatus profilingPushHelper();
void profilingPopHelper();
void emitUncachedCall(uint32_t argc, bool callingNew);
void checkCallApplySpeculation(uint32_t argc, FrameEntry *origCallee, FrameEntry *origThis,
MaybeRegisterID origCalleeType, RegisterID origCalleeData,

View File

@ -610,7 +610,7 @@ stubs::CreateThis(VMFrame &f, JSObject *proto)
void JS_FASTCALL
stubs::ScriptDebugPrologue(VMFrame &f)
{
Probes::enterJSFun(f.cx, f.fp()->maybeFun(), f.fp()->script());
Probes::enterScript(f.cx, f.script(), f.script()->function(), f.fp());
JSTrapStatus status = js::ScriptDebugPrologue(f.cx, f.fp());
switch (status) {
case JSTRAP_CONTINUE:
@ -629,7 +629,6 @@ stubs::ScriptDebugPrologue(VMFrame &f)
void JS_FASTCALL
stubs::ScriptDebugEpilogue(VMFrame &f)
{
Probes::exitJSFun(f.cx, f.fp()->maybeFun(), f.fp()->script());
if (!js::ScriptDebugEpilogue(f.cx, f.fp(), JS_TRUE))
THROW();
}
@ -637,13 +636,13 @@ stubs::ScriptDebugEpilogue(VMFrame &f)
void JS_FASTCALL
stubs::ScriptProbeOnlyPrologue(VMFrame &f)
{
Probes::enterJSFun(f.cx, f.fp()->fun(), f.fp()->script());
Probes::enterScript(f.cx, f.script(), f.script()->function(), f.fp());
}
void JS_FASTCALL
stubs::ScriptProbeOnlyEpilogue(VMFrame &f)
{
Probes::exitJSFun(f.cx, f.fp()->fun(), f.fp()->script());
Probes::exitScript(f.cx, f.script(), f.script()->function(), f.fp());
}
void JS_FASTCALL
@ -868,8 +867,7 @@ js_InternalInterpret(void *returnData, void *returnType, void *returnReg, js::VM
return js_InternalThrow(f);
fp->thisValue() = ObjectValue(*obj);
if (Probes::callTrackingActive(cx))
Probes::enterJSFun(f.cx, f.fp()->maybeFun(), f.fp()->script());
Probes::enterScript(f.cx, f.script(), f.script()->function(), fp);
if (script->debugMode) {
JSTrapStatus status = js::ScriptDebugPrologue(f.cx, f.fp());
@ -925,8 +923,8 @@ js_InternalInterpret(void *returnData, void *returnType, void *returnReg, js::VM
}
/* FALLTHROUGH */
case REJOIN_EVAL_PROLOGUE:
Probes::enterScript(cx, f.script(), f.script()->function(), fp);
if (cx->compartment->debugMode()) {
Probes::enterJSFun(cx, fp->maybeFun(), fp->script());
JSTrapStatus status = ScriptDebugPrologue(cx, fp);
switch (status) {
case JSTRAP_CONTINUE:

172
js/src/vm/SPSProfiler.cpp Normal file
View File

@ -0,0 +1,172 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=4 sw=4 et tw=99 ft=cpp:
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jsnum.h"
#include "vm/SPSProfiler.h"
#include "vm/StringBuffer.h"
using namespace js;
SPSProfiler::~SPSProfiler()
{
if (strings.initialized()) {
for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront())
js_free((void*) e.front().value);
}
}
void
SPSProfiler::setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max)
{
if (!strings.initialized())
strings.init(max);
stack_ = stack;
size_ = size;
max_ = max;
}
/* Lookup the string for the function/script, creating one if necessary */
const char*
SPSProfiler::profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun)
{
JS_ASSERT(enabled());
JS_ASSERT(strings.initialized());
ProfileStringMap::AddPtr s = strings.lookupForAdd(script);
if (s)
return s->value;
const char *str = allocProfileString(cx, script, maybeFun);
if (str == NULL)
return NULL;
if (!strings.add(s, script, str)) {
js_free((void*) str);
return NULL;
}
return str;
}
void
SPSProfiler::onScriptFinalized(JSScript *script)
{
/*
* This function is called whenever a script is destroyed, regardless of
* whether profiling has been turned on, so don't invoke a function on an
* invalid hash set. Also, even if profiling was enabled but then turned
* off, we still want to remove the string, so no check of enabled() is
* done.
*/
if (!strings.initialized())
return;
if (ProfileStringMap::Ptr entry = strings.lookup(script)) {
const char *tofree = entry->value;
strings.remove(entry);
js_free((void*) tofree);
}
}
bool
SPSProfiler::enter(JSContext *cx, JSScript *script, JSFunction *maybeFun)
{
JS_ASSERT(enabled());
const char *str = profileString(cx, script, maybeFun);
if (str == NULL)
return false;
if (*size_ < max_) {
stack_[*size_].string = str;
stack_[*size_].sp = NULL;
}
(*size_)++;
return true;
}
void
SPSProfiler::exit(JSContext *cx, JSScript *script, JSFunction *maybeFun)
{
JS_ASSERT(enabled());
(*size_)--;
JS_ASSERT(*(int*)size_ >= 0);
#ifdef DEBUG
/* Sanity check to make sure push/pop balanced */
if (*size_ < max_) {
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;
}
#endif
}
/*
* Serializes the script/function pair into a "descriptive string" which is
* allowed to fail. This function cannot trigger a GC because it could finalize
* some scripts, resize the hash table of profile strings, and invalidate the
* AddPtr held while invoking allocProfileString.
*/
const char*
SPSProfiler::allocProfileString(JSContext *cx, JSScript *script, JSFunction *maybeFun)
{
DebugOnly<uint64_t> gcBefore = cx->runtime->gcNumber;
StringBuffer buf(cx);
bool hasAtom = maybeFun != NULL && maybeFun->atom != NULL;
if (hasAtom) {
if (!buf.append(maybeFun->atom))
return NULL;
if (!buf.append(" ("))
return NULL;
}
if (script->filename) {
if (!buf.appendInflated(script->filename, strlen(script->filename)))
return NULL;
} else if (!buf.append("<unknown>")) {
return NULL;
}
if (!buf.append(":"))
return NULL;
if (!NumberValueToStringBuffer(cx, NumberValue(script->lineno), buf))
return NULL;
if (hasAtom && !buf.append(")"))
return NULL;
size_t len = buf.length();
char *cstr = (char*) js_malloc(len + 1);
if (cstr == NULL)
return NULL;
const jschar *ptr = buf.begin();
for (size_t i = 0; i < len; i++)
cstr[i] = ptr[i];
cstr[len] = 0;
JS_ASSERT(gcBefore == cx->runtime->gcNumber);
return cstr;
}
SPSEntryMarker::SPSEntryMarker(JSRuntime *rt) : profiler(&rt->spsProfiler), pushed(false)
{
if (!profiler->enabled())
return;
uint32_t *size = profiler->size_;
size_before = *size;
if (*size < profiler->max_) {
profiler->stack_[*size].string = "js::RunScript";
profiler->stack_[*size].sp = this;
}
(*size)++;
pushed = true;
}
SPSEntryMarker::~SPSEntryMarker()
{
if (!pushed || !profiler->enabled())
return;
(*profiler->size_)--;
JS_ASSERT(*profiler->size_ == size_before);
}

141
js/src/vm/SPSProfiler.h Normal file
View File

@ -0,0 +1,141 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99 ft=cpp:
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef SPSProfiler_h__
#define SPSProfiler_h__
#include "mozilla/HashFunctions.h"
#include <stddef.h>
#include "jsfriendapi.h"
#include "jsfun.h"
#include "jsscript.h"
/*
* SPS Profiler integration with the JS Engine
* https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler
*
* The SPS profiler (found in tools/profiler) is an implementation of a profiler
* which has the ability to walk the C++ stack as well as use instrumentation to
* gather information. When dealing with JS, however, SPS needs integration
* with the engine because otherwise it is very difficult to figure out what
* javascript is executing.
*
* The current method of integration with SPS is a form of instrumentation:
* every time a JS function is entered, a bit of information is pushed onto a
* stack that SPS owns and maintains. This information is then popped at the end
* of the JS function. SPS informs the JS engine of this stack at runtime, and
* it can by turned on/off dynamically.
*
* The SPS stack has three parameters: a base pointer, a size, and a maximum
* size. The stack is the ProfileEntry stack which will have information written
* to it. The size location is a pointer to an integer which represents the
* current size of the stack (number of valid frames). This size will be
* modified when JS functions are called. The maximum specified is the maximum
* capacity of the ProfileEntry stack.
*
* Throughout execution, the size of the stack recorded in memory may exceed the
* maximum. The JS engine will not write any information past the maximum limit,
* 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.
*
* = Profile Strings
*
* The profile strings' allocations and deallocation must be carefully
* maintained, and ideally at a very low overhead cost. For this reason, the JS
* engine maintains a mapping of all known profile strings. These strings are
* keyed in lookup by a JSScript*, but are serialized with a JSFunction*,
* JSScript* pair. A JSScript will destroy its corresponding profile string when
* the script is finalized.
*
* For this reason, a char* pointer pushed on the SPS stack is valid only while
* it is on the SPS stack. SPS uses sampling to read off information from this
* instrumented stack, and it therefore copies the string byte for byte when a
* JS function is encountered during sampling.
*
* = Native Stack Pointer
*
* The actual value pushed as the native pointer is NULL for most JS functions.
* The reason for this is that there's actually very little correlation between
* the JS stack and the C++ stack because many JS functions all run in the same
* C++ frame, or can even go backwards in C++ when going from the JIT back to
* the interpreter.
*
* To alleviate this problem, all JS functions push NULL as their "native stack
* pointer" to indicate that it's a JS function call. The function RunScript(),
* however, pushes an actual C++ stack pointer onto the SPS stack. This way when
* 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.
*/
namespace js {
typedef HashMap<JSScript*, const char*, DefaultHasher<JSScript*>, SystemAllocPolicy>
ProfileStringMap;
class SPSEntryMarker;
class SPSProfiler
{
friend class SPSEntryMarker;
ProfileStringMap strings;
ProfileEntry *stack_;
uint32_t *size_;
uint32_t max_;
static const char *allocProfileString(JSContext *cx, JSScript *script,
JSFunction *function);
public:
SPSProfiler() : stack_(NULL), size_(NULL), max_(0) {}
~SPSProfiler();
uint32_t *size() { return size_; }
uint32_t maxSize() { return max_; }
ProfileEntry *stack() { return stack_; }
bool enabled() { return stack_ != NULL; }
bool enter(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void exit(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max);
const char *profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void onScriptFinalized(JSScript *script);
/* meant to be used for testing, not recommended to call in normal code */
size_t stringsCount() { return strings.count(); }
void stringsReset() { strings.clear(); }
};
/*
* This class is used in RunScript() to push the marker onto the sampling stack
* that we're about to enter JS function calls. This is the only time in which a
* valid stack pointer is pushed to the sampling stack.
*/
class SPSEntryMarker
{
SPSProfiler *profiler;
bool pushed;
DebugOnly<uint32_t> size_before;
public:
SPSEntryMarker(JSRuntime *rt);
~SPSEntryMarker();
};
} /* namespace js */
#endif /* SPSProfiler_h__ */

View File

@ -114,6 +114,9 @@ StackFrame::initInlineFrame(JSFunction *fun, StackFrame *prevfp, jsbytecode *pre
flags_ = StackFrame::FUNCTION;
exec.fun = fun;
resetInlinePrev(prevfp, prevpc);
if (prevfp->hasPushedSPSFrame())
setPushedSPSFrame();
}
inline void

View File

@ -237,11 +237,14 @@ StackFrame::prologue(JSContext *cx, bool newType)
pushOnScopeChain(*callobj);
flags_ |= HAS_CALL_OBJ;
}
Probes::enterScript(cx, script(), NULL, this);
return true;
}
if (isGlobalFrame())
if (isGlobalFrame()) {
Probes::enterScript(cx, script(), NULL, this);
return true;
}
JS_ASSERT(isNonEvalFunctionFrame());
@ -266,7 +269,7 @@ StackFrame::prologue(JSContext *cx, bool newType)
functionThis() = ObjectValue(*obj);
}
Probes::enterJSFun(cx, fun(), script());
Probes::enterScript(cx, script(), script()->function(), this);
return true;
}
@ -277,6 +280,8 @@ StackFrame::epilogue(JSContext *cx)
JS_ASSERT(!isYielding());
JS_ASSERT(!hasBlockChain());
Probes::exitScript(cx, script(), script()->function(), this);
if (isEvalFrame()) {
if (isStrictEvalFrame()) {
JS_ASSERT_IF(hasCallObj(), scopeChain()->asCall().isForEval());
@ -309,8 +314,6 @@ StackFrame::epilogue(JSContext *cx)
if (cx->compartment->debugMode())
cx->runtime->debugScopes->onPopCall(this, cx);
Probes::exitJSFun(cx, fun(), script());
if (script()->nesting() && (flags_ & HAS_NESTING))
types::NestingEpilogue(this);

View File

@ -266,7 +266,10 @@ class StackFrame
LOWERED_CALL_APPLY = 0x200000, /* Pushed by a lowered call/apply */
/* Debugger state */
PREV_UP_TO_DATE = 0x400000 /* see DebugScopes::updateLiveScopes */
PREV_UP_TO_DATE = 0x400000, /* see DebugScopes::updateLiveScopes */
/* Used in tracking calls and profiling (see vm/SPSProfiler.cpp) */
HAS_PUSHED_SPS_FRAME = 0x800000 /* SPS was notified of enty */
};
private:
@ -801,6 +804,14 @@ class StackFrame
flags_ |= HAS_HOOK_DATA;
}
bool hasPushedSPSFrame() {
return !!(flags_ & HAS_PUSHED_SPS_FRAME);
}
void setPushedSPSFrame() {
flags_ |= HAS_PUSHED_SPS_FRAME;
}
/* Return value */
bool hasReturnValue() const {

View File

@ -30,6 +30,7 @@
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/Attributes.h"
#include "sampler.h"
#include "nsJSPrincipals.h"
#ifdef MOZ_CRASHREPORTER
@ -2000,6 +2001,10 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
js::SetPreserveWrapperCallback(mJSRuntime, PreserveWrapper);
#ifdef MOZ_CRASHREPORTER
JS_EnumerateDiagnosticMemoryRegions(DiagnosticMemoryCallback);
#endif
#ifdef MOZ_ENABLE_PROFILER_SPS
if (ProfileStack *stack = mozilla_profile_stack())
stack->sampleRuntime(mJSRuntime);
#endif
JS_SetAccumulateTelemetryCallback(mJSRuntime, AccumulateTelemetryCallback);
js::SetActivityCallback(mJSRuntime, ActivityCallback, this);

View File

@ -376,6 +376,7 @@ class TableTicker: public Sampler {
//XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
mPrimaryThreadProfile.addTag(ProfileEntry('m', "Start"));
}
@ -402,6 +403,8 @@ class TableTicker: public Sampler {
JSObject *ToJSObject(JSContext *aCx);
JSObject *GetMetaJSObject(JSObjectBuilder& b);
const bool ProfileJS() { return mProfileJS; }
private:
// Not implemented on platforms which do not support backtracing
void doBacktrace(ThreadProfile &aProfile, TickSample* aSample);
@ -413,6 +416,7 @@ private:
bool mSaveRequested;
bool mUseStackWalk;
bool mJankOnly;
bool mProfileJS;
};
std::string GetSharedLibraryInfoString();
@ -683,7 +687,9 @@ void doSampleStackTrace(ProfileStack *aStack, ThreadProfile &aProfile, TickSampl
// 's' tag denotes the start of a sample block
// followed by 0 or more 'c' tags.
aProfile.addTag(ProfileEntry('s', "(root)"));
for (mozilla::sig_safe_t i = 0; i < aStack->mStackPointer; i++) {
for (mozilla::sig_safe_t i = 0;
i < aStack->mStackPointer && i < mozilla::ArrayLength(aStack->mStack);
i++) {
// First entry has tagName 's' (start)
// Check for magic pointer bit 1 to indicate copy
const char* sampleLabel = aStack->mStack[i].mLabel;
@ -906,6 +912,7 @@ const char** mozilla_sampler_get_features()
"stackwalk",
#endif
"jank",
"js",
NULL
};
@ -931,6 +938,8 @@ void mozilla_sampler_start(int aProfileEntries, int aInterval,
aFeatures, aFeatureCount);
tlsTicker.set(t);
t->Start();
if (t->ProfileJS())
stack->installJSSampling();
}
void mozilla_sampler_stop()
@ -943,9 +952,16 @@ void mozilla_sampler_stop()
return;
}
bool uninstallJS = t->ProfileJS();
t->Stop();
delete t;
tlsTicker.set(NULL);
ProfileStack *stack = tlsStack.get();
ASSERT(stack != NULL);
if (uninstallJS)
stack->uninstallJSSampling();
}
bool mozilla_sampler_is_active()

View File

@ -12,6 +12,14 @@
#include "mozilla/TimeStamp.h"
#include "mozilla/Util.h"
/* QT has a #define for the word "slots" and jsfriendapi.h has a struct with
* this variable name, causing compilation problems. Alleviate this for now by
* removing this #define */
#ifdef MOZ_WIDGET_QT
#undef slots
#endif
#include "jsfriendapi.h"
using mozilla::TimeStamp;
using mozilla::TimeDuration;
@ -225,7 +233,6 @@ public:
ProfileStack()
: mStackPointer(0)
, mMarkerPointer(0)
, mDroppedStackEntries(0)
, mQueueClearMarker(false)
{ }
@ -273,7 +280,7 @@ public:
void push(const char *aName, void *aStackAddress, bool aCopy)
{
if (size_t(mStackPointer) >= mozilla::ArrayLength(mStack)) {
mDroppedStackEntries++;
mStackPointer++;
return;
}
@ -288,29 +295,47 @@ public:
}
void pop()
{
if (mDroppedStackEntries > 0) {
mDroppedStackEntries--;
} else {
mStackPointer--;
}
mStackPointer--;
}
bool isEmpty()
{
return mStackPointer == 0;
}
void sampleRuntime(JSRuntime *runtime) {
mRuntime = runtime;
}
void installJSSampling() {
JS_STATIC_ASSERT(sizeof(mStack[0]) == sizeof(js::ProfileEntry));
js::SetRuntimeProfilingStack(mRuntime,
(js::ProfileEntry*) mStack,
(uint32_t*) &mStackPointer,
mozilla::ArrayLength(mStack));
}
void uninstallJSSampling() {
js::SetRuntimeProfilingStack(mRuntime, NULL, NULL, 0);
}
// Keep a list of active checkpoints
StackEntry volatile mStack[1024];
// Keep a list of active markers to be applied to the next sample taken
char const * volatile mMarkers[1024];
volatile mozilla::sig_safe_t mStackPointer;
volatile mozilla::sig_safe_t mMarkerPointer;
volatile mozilla::sig_safe_t mDroppedStackEntries;
// We don't want to modify _markers from within the signal so we allow
// it to queue a clear operation.
volatile mozilla::sig_safe_t mQueueClearMarker;
// The runtime which is being sampled
JSRuntime *mRuntime;
};
inline ProfileStack* mozilla_profile_stack(void)
{
if (!stack_key_initialized)
return NULL;
return tlsStack.get();
}
inline void* mozilla_sampler_call_enter(const char *aInfo, void *aFrameAddress, bool aCopy)
{
// check if we've been initialized to avoid calling pthread_getspecific