mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 761261 - Add JS profiling to SPS (r=luke,ehsan)
--HG-- extra : rebase_source : 328a82697aa9a9f63d18c7a30a813f436e163922
This commit is contained in:
parent
0a379d77d8
commit
f491495a41
@ -126,6 +126,7 @@ CPPSRCS = \
|
||||
ParseNode.cpp \
|
||||
Parser.cpp \
|
||||
SemanticAnalysis.cpp \
|
||||
SPSProfiler.cpp \
|
||||
TokenStream.cpp \
|
||||
TreeContext.cpp \
|
||||
TestingFunctions.cpp \
|
||||
|
@ -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;
|
||||
|
||||
|
@ -63,6 +63,7 @@ CPPSRCS = \
|
||||
testValueABI.cpp \
|
||||
testVersion.cpp \
|
||||
testXDR.cpp \
|
||||
testProfileStrings.cpp \
|
||||
$(NULL)
|
||||
|
||||
CSRCS = \
|
||||
|
215
js/src/jsapi-tests/testProfileStrings.cpp
Normal file
215
js/src/jsapi-tests/testProfileStrings.cpp
Normal 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)
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
||||
/*
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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
172
js/src/vm/SPSProfiler.cpp
Normal 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
141
js/src/vm/SPSProfiler.h
Normal 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__ */
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user