mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 507012: function call/return callback for profiling, r=dmandelin, a=sayrer
This commit is contained in:
parent
cefdea759e
commit
082cfbef39
@ -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@
|
||||
|
||||
|
11
configure.in
11
configure.in
@ -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 ========================================================
|
||||
|
@ -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 ========================================================
|
||||
|
@ -57,6 +57,7 @@ CPPSRCS = \
|
||||
testDefineProperty.cpp \
|
||||
testExtendedEq.cpp \
|
||||
testGCChunkAlloc.cpp \
|
||||
testFuncCallback.cpp \
|
||||
testIntString.cpp \
|
||||
testIsAboutToBeFinalized.cpp \
|
||||
testLookup.cpp \
|
||||
|
138
js/src/jsapi-tests/testFuncCallback.cpp
Normal file
138
js/src/jsapi-tests/testFuncCallback.cpp
Normal 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)
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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). */
|
||||
|
@ -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. */
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user