Bug 507012: function call/return callback for profiling, r=dmandelin, a=sayrer

This commit is contained in:
Steve Fink 2010-08-09 13:38:13 -07:00
parent cefdea759e
commit 082cfbef39
11 changed files with 271 additions and 5 deletions

View File

@ -110,6 +110,7 @@ MOZ_JPROF = @MOZ_JPROF@
MOZ_SHARK = @MOZ_SHARK@
MOZ_CALLGRIND = @MOZ_CALLGRIND@
MOZ_VTUNE = @MOZ_VTUNE@
MOZ_TRACE_JSCALLS = @MOZ_TRACE_JSCALLS@
MOZ_TRACEVIS = @MOZ_TRACEVIS@
DEHYDRA_PATH = @DEHYDRA_PATH@

View File

@ -7335,6 +7335,17 @@ MOZ_ARG_WITH_STRING(wrap-malloc,
[ --with-wrap-malloc=DIR Location of malloc wrapper library],
WRAP_MALLOC_LIB=$withval)
dnl ========================================================
dnl = Use JS Call tracing
dnl ========================================================
MOZ_ARG_ENABLE_BOOL(trace-jscalls,
[ --enable-trace-jscalls Enable JS call enter/exit callback (default=no)],
MOZ_TRACE_JSCALLS=1,
MOZ_TRACE_JSCALLS= )
if test -n "$MOZ_TRACE_JSCALLS"; then
AC_DEFINE(MOZ_TRACE_JSCALLS)
fi
dnl ========================================================
dnl = Use TraceVis
dnl ========================================================

View File

@ -4351,6 +4351,17 @@ if test "$JS_HAS_CTYPES"; then
AC_DEFINE(JS_HAS_CTYPES)
fi
dnl ========================================================
dnl = Use JS Call tracing
dnl ========================================================
MOZ_ARG_ENABLE_BOOL(trace-jscalls,
[ --enable-trace-jscalls Enable JS call enter/exit callback (default=no)],
MOZ_TRACE_JSCALLS=1,
MOZ_TRACE_JSCALLS= )
if test -n "$MOZ_TRACE_JSCALLS"; then
AC_DEFINE(MOZ_TRACE_JSCALLS)
fi
dnl ========================================================
dnl = Use TraceVis
dnl ========================================================

View File

@ -57,6 +57,7 @@ CPPSRCS = \
testDefineProperty.cpp \
testExtendedEq.cpp \
testGCChunkAlloc.cpp \
testFuncCallback.cpp \
testIntString.cpp \
testIsAboutToBeFinalized.cpp \
testLookup.cpp \

View File

@ -0,0 +1,138 @@
#include "tests.h"
#include "jsfun.h"
#include "jscntxt.h"
// For TRACING_ENABLED
#include "jstracer.h"
#ifdef MOZ_TRACE_JSCALLS
static int depth = 0;
static int enters = 0;
static int leaves = 0;
static int interpreted = 0;
static void
funcTransition(const JSFunction *,
const JSScript *,
const JSContext *cx,
JSBool entering)
{
if (entering) {
++depth;
++enters;
if (! JS_ON_TRACE(cx))
++interpreted;
} else {
--depth;
++leaves;
}
}
static JSBool called2 = false;
static void
funcTransition2(const JSFunction *, const JSScript*, const JSContext*, JSBool)
{
called2 = true;
}
static int overlays = 0;
static JSFunctionCallback innerCallback = NULL;
static void
funcTransitionOverlay(const JSFunction *fun,
const JSScript *script,
const JSContext *cx,
JSBool entering)
{
(*innerCallback)(fun, script, cx, entering);
overlays++;
}
#endif
BEGIN_TEST(testFuncCallback_bug507012)
{
#ifdef MOZ_TRACE_JSCALLS
// Call funcTransition() whenever a Javascript method is invoked
JS_SetFunctionCallback(cx, funcTransition);
EXEC("x = 0; function f (n) { if (n > 1) { f(n - 1); } }");
interpreted = enters = leaves = depth = 0;
// Check whether JS_Execute() tracking works
EXEC("42");
CHECK(enters == 1 && leaves == 1 && depth == 0);
interpreted = enters = leaves = depth = 0;
// Check whether the basic function tracking works
EXEC("f(1)");
CHECK(enters == 2 && leaves == 2 && depth == 0);
// Can we switch to a different callback?
enters = 777;
JS_SetFunctionCallback(cx, funcTransition2);
EXEC("f(1)");
CHECK(called2 && enters == 777);
// Check whether we can turn off function tracing
JS_SetFunctionCallback(cx, NULL);
EXEC("f(1)");
CHECK(enters == 777);
interpreted = enters = leaves = depth = 0;
// Check nested invocations
JS_SetFunctionCallback(cx, funcTransition);
enters = leaves = depth = 0;
EXEC("f(3)");
CHECK(enters == 1+3 && leaves == 1+3 && depth == 0);
interpreted = enters = leaves = depth = 0;
// Check calls invoked while running on trace
EXEC("function g () { ++x; }");
interpreted = enters = leaves = depth = 0;
EXEC("for (i = 0; i < 50; ++i) { g(); }");
CHECK(enters == 50+1 && leaves == 50+1 && depth == 0);
// If this fails, it means that the code was interpreted rather
// than trace-JITted, and so is not testing what it's supposed to
// be testing. Which doesn't necessarily imply that the
// functionality is broken.
#ifdef JS_TRACER
if (TRACING_ENABLED(cx))
CHECK(interpreted < enters);
#endif
// Test nesting callbacks via JS_GetFunctionCallback()
JS_SetFunctionCallback(cx, funcTransition);
innerCallback = JS_GetFunctionCallback(cx);
JS_SetFunctionCallback(cx, funcTransitionOverlay);
EXEC("x = 0; function f (n) { if (n > 1) { f(n - 1); } }");
interpreted = enters = leaves = depth = overlays = 0;
EXEC("42.5");
CHECK(enters == 1);
CHECK(leaves == 1);
CHECK(depth == 0);
CHECK(overlays == 2); // 1 each for enter and exit
interpreted = enters = leaves = depth = overlays = 0;
#endif
return true;
}
// Not strictly necessary, but part of the test attempts to check
// whether these callbacks still trigger when traced, so force
// JSOPTION_JIT just to be sure. Once the method jit and tracing jit
// are integrated, this'll probably have to change (and we'll probably
// want to test in all modes.)
virtual
JSContext *createContext()
{
JSContext *cx = JSAPITest::createContext();
if (cx)
JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_JIT);
return cx;
}
END_TEST(testFuncCallback_bug507012)

View File

@ -5660,6 +5660,20 @@ JS_ClearContextThread(JSContext *cx)
#endif
}
#ifdef MOZ_TRACE_JSCALLS
JS_PUBLIC_API(void)
JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb)
{
cx->functionCallback = fcb;
}
JS_PUBLIC_API(JSFunctionCallback)
JS_GetFunctionCallback(JSContext *cx)
{
return cx->functionCallback;
}
#endif
#ifdef JS_GC_ZEAL
JS_PUBLIC_API(void)
JS_SetGCZeal(JSContext *cx, uint8 zeal)

View File

@ -3006,6 +3006,19 @@ JS_SetContextThread(JSContext *cx);
extern JS_PUBLIC_API(jsword)
JS_ClearContextThread(JSContext *cx);
#ifdef MOZ_TRACE_JSCALLS
typedef void (*JSFunctionCallback)(const JSFunction *fun,
const JSScript *scr,
const JSContext *cx,
JSBool entering);
extern JS_PUBLIC_API(void)
JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb);
extern JS_PUBLIC_API(JSFunctionCallback)
JS_GetFunctionCallback(JSContext *cx);
#endif
/************************************************************************/
#ifdef DEBUG

View File

@ -1944,6 +1944,19 @@ struct JSContext
#endif
}
#ifdef MOZ_TRACE_JSCALLS
/* Function entry/exit debugging callback. */
JSFunctionCallback functionCallback;
void doFunctionCallback(const JSFunction *fun,
const JSScript *scr,
JSBool entering) const
{
if (functionCallback)
functionCallback(fun, scr, this, entering);
}
#endif
DSTOffsetCache dstOffsetCache;
/* List of currently active non-escaping enumerators (for-in). */

View File

@ -69,11 +69,12 @@ class DTrace {
static void finalizeObject(JSObject *obj);
class ExecutionScope {
const JSContext *cx;
const JSScript *script;
void startExecution();
void endExecution();
public:
explicit ExecutionScope(JSScript *script);
explicit ExecutionScope(JSContext *cx, JSScript *script);
~ExecutionScope();
};
@ -106,6 +107,9 @@ DTrace::enterJSFun(JSContext *cx, JSStackFrame *fp, JSFunction *fun, JSStackFram
handleFunctionArgs(cx, fp, fun, argc, argv);
}
#endif
#ifdef MOZ_TRACE_JSCALLS
cx->doFunctionCallback(fun, fun ? FUN_SCRIPT(fun) : NULL, true);
#endif
}
inline void
@ -120,6 +124,9 @@ DTrace::exitJSFun(JSContext *cx, JSStackFrame *fp, JSFunction *fun,
handleFunctionReturn(cx, fp, fun);
}
#endif
#ifdef MOZ_TRACE_JSCALLS
cx->doFunctionCallback(fun, fun ? FUN_SCRIPT(fun) : NULL, false);
#endif
}
inline void
@ -134,13 +141,16 @@ DTrace::finalizeObject(JSObject *obj)
/* Execution scope. */
inline
DTrace::ExecutionScope::ExecutionScope(JSScript *script)
: script(script)
DTrace::ExecutionScope::ExecutionScope(JSContext *cx, JSScript *script)
: cx(cx), script(script)
{
#ifdef INCLUDE_MOZILLA_DTRACE
if (JAVASCRIPT_EXECUTE_START_ENABLED())
startExecution();
#endif
#ifdef MOZ_TRACE_JSCALLS
cx->doFunctionCallback(NULL, script, true);
#endif
}
inline
@ -150,6 +160,9 @@ DTrace::ExecutionScope::~ExecutionScope()
if (JAVASCRIPT_EXECUTE_DONE_ENABLED())
endExecution();
#endif
#ifdef MOZ_TRACE_JSCALLS
cx->doFunctionCallback(NULL, script, false);
#endif
}
/* Object creation scope. */

View File

@ -794,7 +794,7 @@ Execute(JSContext *cx, JSObject *chain, JSScript *script,
LeaveTrace(cx);
DTrace::ExecutionScope executionScope(script);
DTrace::ExecutionScope executionScope(cx, script);
/*
* Get a pointer to new frame/slots. This memory is not "claimed", so the
* code before pushExecuteFrame must not reenter the interpreter.

View File

@ -10545,6 +10545,22 @@ TraceRecorder::record_JSOP_LEAVEWITH()
return ARECORD_STOP;
}
#ifdef MOZ_TRACE_JSCALLS
// Usually, cx->doFunctionCallback() is invoked via DTrace::enterJSFun
// and friends, but the DTrace:: probes use fp and therefore would
// need to break out of tracing. So we define a functionProbe()
// callback to be called by generated code when a Javascript function
// is entered or exited.
static JSBool JS_FASTCALL
functionProbe(JSContext *cx, JSFunction *fun, JSBool enter)
{
cx->doFunctionCallback(fun, FUN_SCRIPT(fun), enter);
return true;
}
JS_DEFINE_CALLINFO_3(static, BOOL, functionProbe, CONTEXT, FUNCTION, BOOL, 0, 0)
#endif
JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_RETURN()
{
@ -10561,6 +10577,14 @@ TraceRecorder::record_JSOP_RETURN()
putActivationObjects();
#ifdef MOZ_TRACE_JSCALLS
if (cx->functionCallback) {
LIns* args[] = { INS_CONST(0), INS_CONSTPTR(cx->fp->fun), cx_ins };
LIns* call_ins = lir->insCall(&functionProbe_ci, args);
guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT);
}
#endif
/* If we inlined this function call, make the return value available to the caller code. */
Value& rval = stackval(-1);
JSStackFrame *fp = cx->fp;
@ -11578,6 +11602,17 @@ TraceRecorder::functionCall(uintN argc, JSOp mode)
*/
JSFunction* fun = GET_FUNCTION_PRIVATE(cx, &fval.toObject());
#ifdef MOZ_TRACE_JSCALLS
if (cx->functionCallback) {
JSScript *script = FUN_SCRIPT(fun);
if (! script || ! script->isEmpty()) {
LIns* args[] = { INS_CONST(1), INS_CONSTPTR(fun), cx_ins };
LIns* call_ins = lir->insCall(&functionProbe_ci, args);
guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT);
}
}
#endif
if (FUN_INTERPRETED(fun)) {
if (mode == JSOP_NEW) {
LIns* args[] = { get(&fval), INS_CONSTPTR(&js_ObjectClass), cx_ins };
@ -11606,7 +11641,15 @@ TraceRecorder::functionCall(uintN argc, JSOp mode)
}
}
return callNative(argc, mode);
RecordingStatus rs = callNative(argc, mode);
#ifdef MOZ_TRACE_JSCALLS
if (cx->functionCallback) {
LIns* args[] = { INS_CONST(0), INS_CONSTPTR(fun), cx_ins };
LIns* call_ins = lir->insCall(&functionProbe_ci, args);
guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT);
}
#endif
return rs;
}
JS_REQUIRES_STACK AbortableRecordingStatus
@ -15721,6 +15764,14 @@ TraceRecorder::record_JSOP_STOP()
putActivationObjects();
#ifdef MOZ_TRACE_JSCALLS
if (cx->functionCallback) {
LIns* args[] = { INS_CONST(0), INS_CONSTPTR(cx->fp->fun), cx_ins };
LIns* call_ins = lir->insCall(&functionProbe_ci, args);
guard(false, lir->insEqI_0(call_ins), MISMATCH_EXIT);
}
#endif
/*
* We know falling off the end of a constructor returns the new object that
* was passed in via fp->argv[-1], while falling off the end of a function