From 5755ce693dfc497389ab89e8ae0e62c3cc89d4ff Mon Sep 17 00:00:00 2001 From: Fred Drake Date: Wed, 27 Jun 2001 19:19:46 +0000 Subject: [PATCH] Revise the interface to the profiling and tracing support for the Python interpreter. This change adds two new C-level APIs: PyEval_SetProfile() and PyEval_SetTrace(). These can be used to install profile and trace functions implemented in C, which can operate at much higher speeds than Python-based functions. The overhead for calling a C-based profile function is a very small fraction of a percent of the overhead involved in calling a Python-based function. The machinery required to call a Python-based profile or trace function been moved to sysmodule.c, where sys.setprofile() and sys.setprofile() simply become users of the new interface. As a side effect, SF bug #436058 is fixed; there is no longer a _PyTrace_Init() function to declare. --- Python/ceval.c | 197 +++++++++++++++------------------------------ Python/pystate.c | 12 ++- Python/sysmodule.c | 123 +++++++++++++++++++++++++--- 3 files changed, 182 insertions(+), 150 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index a73e4d06a72..702380d7533 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2,7 +2,7 @@ /* Execute compiled code */ /* XXX TO DO: - XXX how to pass arguments to call_trace? + XXX how to pass arguments to profile and trace functions? XXX speed up searching for keywords by using a dictionary XXX document it! */ @@ -61,9 +61,9 @@ static PyObject *load_args(PyObject ***, int); #ifdef LLTRACE static int prtrace(PyObject *, char *); #endif -static void call_exc_trace(PyObject **, PyObject**, PyFrameObject *); -static int call_trace(PyObject **, PyObject **, - PyFrameObject *, PyObject *, PyObject *); +static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *, + int, PyObject *); +static void call_exc_trace(Py_tracefunc, PyObject *, PyFrameObject *); static PyObject *loop_subscript(PyObject *, PyObject *); static PyObject *apply_slice(PyObject *, PyObject *, PyObject *); static int assign_slice(PyObject *, PyObject *, @@ -98,14 +98,6 @@ static long dxp[256]; #endif #endif -/* Cached interned string objects used for calling the profile and - * trace functions. - */ -static PyObject *str_call = NULL; -static PyObject *str_exception = NULL; -static PyObject *str_line = NULL; -static PyObject *str_return = NULL; - staticforward PyTypeObject gentype; @@ -1892,12 +1884,15 @@ eval_frame(PyFrameObject *f) printf("--- %s:%d \n", filename, oparg); #endif f->f_lineno = oparg; - if (f->f_trace == NULL) + if (tstate->c_tracefunc == NULL || tstate->tracing) continue; /* Trace each line of code reached */ f->f_lasti = INSTR_OFFSET(); - err = call_trace(&f->f_trace, &f->f_trace, - f, str_line, Py_None); + /* Inline call_trace() for performance: */ + tstate->tracing++; + err = (tstate->c_tracefunc)(tstate->c_traceobj, f, + PyTrace_LINE, Py_None); + tstate->tracing--; break; case CALL_FUNCTION: @@ -2147,11 +2142,12 @@ eval_frame(PyFrameObject *f) f->f_lasti -= 2; PyTraceBack_Here(f); - if (f->f_trace) - call_exc_trace(&f->f_trace, &f->f_trace, f); - if (tstate->sys_profilefunc) - call_exc_trace(&tstate->sys_profilefunc, - (PyObject**)0, f); + if (tstate->c_tracefunc) + call_exc_trace(tstate->c_tracefunc, + tstate->c_traceobj, f); + if (tstate->c_profilefunc) + call_exc_trace(tstate->c_profilefunc, + tstate->c_profileobj, f); } /* For the rest, treat WHY_RERAISE as WHY_EXCEPTION */ @@ -2232,10 +2228,10 @@ eval_frame(PyFrameObject *f) if (why != WHY_RETURN && why != WHY_YIELD) retval = NULL; - if (f->f_trace) { + if (tstate->c_tracefunc && !tstate->tracing) { if (why == WHY_RETURN || why == WHY_YIELD) { - if (call_trace(&f->f_trace, &f->f_trace, f, - str_return, retval)) { + if (call_trace(tstate->c_tracefunc, tstate->c_traceobj, + f, PyTrace_RETURN, retval)) { Py_XDECREF(retval); retval = NULL; why = WHY_EXCEPTION; @@ -2243,10 +2239,10 @@ eval_frame(PyFrameObject *f) } } - if (tstate->sys_profilefunc && - (why == WHY_RETURN || why == WHY_YIELD)) { - if (call_trace(&tstate->sys_profilefunc, (PyObject**)0, - f, str_return, retval)) { + if (tstate->c_profilefunc && !tstate->tracing + && (why == WHY_RETURN || why == WHY_YIELD)) { + if (call_trace(tstate->c_profilefunc, tstate->c_profileobj, + f, PyTrace_RETURN, retval)) { Py_XDECREF(retval); retval = NULL; why = WHY_EXCEPTION; @@ -2475,7 +2471,7 @@ eval_code2(PyCodeObject *co, PyObject *globals, PyObject *locals, } } - if (tstate->sys_tracefunc != NULL) { + if (tstate->c_tracefunc != NULL && !tstate->tracing) { /* tstate->sys_tracefunc, if defined, is a function that will be called on *every* entry to a code block. Its return value, if not None, is a function that @@ -2488,20 +2484,21 @@ eval_code2(PyCodeObject *co, PyObject *globals, PyObject *locals, depends on the situation. The global trace function (sys.trace) is also called whenever an exception is detected. */ - if (call_trace(&tstate->sys_tracefunc, - &f->f_trace, f, str_call, - Py_None/*XXX how to compute arguments now?*/)) { + if (call_trace(tstate->c_tracefunc, tstate->c_traceobj, + f, PyTrace_CALL, Py_None)) { + /* XXX Need way to compute arguments?? */ /* Trace function raised an error */ goto fail; } } - if (tstate->sys_profilefunc != NULL) { + if (tstate->c_profilefunc != NULL) { /* Similar for sys_profilefunc, except it needn't return itself and isn't called for "line" events */ - if (call_trace(&tstate->sys_profilefunc, - (PyObject**)0, f, str_call, - Py_None/*XXX*/)) { + if (call_trace(tstate->c_profilefunc, tstate->c_profileobj, + f, PyTrace_CALL, Py_None)) { + /* XXX Need way to compute arguments?? */ + /* Profile function raised an error */ goto fail; } } @@ -2772,7 +2769,7 @@ prtrace(PyObject *v, char *str) #endif static void -call_exc_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f) +call_exc_trace(Py_tracefunc func, PyObject *self, PyFrameObject *f) { PyObject *type, *value, *traceback, *arg; int err; @@ -2786,7 +2783,7 @@ call_exc_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f) PyErr_Restore(type, value, traceback); return; } - err = call_trace(p_trace, p_newtrace, f, str_exception, arg); + err = call_trace(func, self, f, PyTrace_EXCEPTION, arg); Py_DECREF(arg); if (err == 0) PyErr_Restore(type, value, traceback); @@ -2797,110 +2794,44 @@ call_exc_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f) } } -/* PyObject **p_trace: in/out; may not be NULL; - may not point to NULL variable initially - PyObject **p_newtrace: in/out; may be NULL; - may point to NULL variable; - may be same variable as p_newtrace - PyObject *msg: in; must not be NULL -*/ - static int -call_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f, - PyObject *msg, PyObject *arg) +call_trace(Py_tracefunc func, PyObject *obj, PyFrameObject *frame, + int what, PyObject *arg) { - PyThreadState *tstate = f->f_tstate; - PyObject *args; - PyObject *res = NULL; - - if (tstate->tracing) { - /* Don't do recursive traces */ - if (p_newtrace) { - Py_XDECREF(*p_newtrace); - *p_newtrace = NULL; - } + register PyThreadState *tstate = frame->f_tstate; + int result; + if (tstate->tracing) return 0; - } - - args = PyTuple_New(3); - if (args == NULL) - goto cleanup; - Py_INCREF(msg); - Py_INCREF(f); - PyTuple_SET_ITEM(args, 0, (PyObject *)f); - PyTuple_SET_ITEM(args, 1, msg); - if (arg == NULL) - arg = Py_None; - Py_INCREF(arg); - PyTuple_SET_ITEM(args, 2, arg); tstate->tracing++; - PyFrame_FastToLocals(f); - res = PyEval_CallObject(*p_trace, args); /* May clear *p_trace! */ - PyFrame_LocalsToFast(f, 1); + result = func(obj, frame, what, arg); tstate->tracing--; - cleanup: - Py_XDECREF(args); - if (res == NULL) { - /* The trace proc raised an exception */ - PyTraceBack_Here(f); - Py_XDECREF(*p_trace); - *p_trace = NULL; - if (p_newtrace) { - Py_XDECREF(*p_newtrace); - *p_newtrace = NULL; - } - /* to be extra double plus sure we don't get recursive - * calls inf either tracefunc or profilefunc gets an - * exception, zap the global variables. - */ - Py_XDECREF(tstate->sys_tracefunc); - tstate->sys_tracefunc = NULL; - Py_XDECREF(tstate->sys_profilefunc); - tstate->sys_profilefunc = NULL; - return -1; - } - else { - if (p_newtrace) { - Py_XDECREF(*p_newtrace); - if (res == Py_None) - *p_newtrace = NULL; - else { - Py_INCREF(res); - *p_newtrace = res; - } - } - Py_DECREF(res); - return 0; - } + return result; } -/* Initialize the strings that get passed to the profile and trace functions; - * this avoids doing this while we're actually profiling/tracing. - */ -int -_PyTrace_Init(void) +void +PyEval_SetProfile(Py_tracefunc func, PyObject *arg) { - if (str_call == NULL) { - str_call = PyString_InternFromString("call"); - if (str_call == NULL) - return -1; - } - if (str_exception == NULL) { - str_exception = PyString_InternFromString("exception"); - if (str_exception == NULL) - return -1; - } - if (str_line == NULL) { - str_line = PyString_InternFromString("line"); - if (str_line == NULL) - return -1; - } - if (str_return == NULL) { - str_return = PyString_InternFromString("return"); - if (str_return == NULL) - return -1; - } - return 0; + PyThreadState *tstate = PyThreadState_Get(); + PyObject *temp = tstate->c_profileobj; + Py_XINCREF(arg); + tstate->c_profilefunc = NULL; + tstate->c_profileobj = NULL; + Py_XDECREF(temp); + tstate->c_profilefunc = func; + tstate->c_profileobj = arg; +} + +void +PyEval_SetTrace(Py_tracefunc func, PyObject *arg) +{ + PyThreadState *tstate = PyThreadState_Get(); + PyObject *temp = tstate->c_traceobj; + Py_XINCREF(arg); + tstate->c_tracefunc = NULL; + tstate->c_traceobj = NULL; + Py_XDECREF(temp); + tstate->c_tracefunc = func; + tstate->c_traceobj = arg; } PyObject * diff --git a/Python/pystate.c b/Python/pystate.c index 8e5896a44fd..192c4c14f62 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -120,8 +120,10 @@ PyThreadState_New(PyInterpreterState *interp) tstate->exc_value = NULL; tstate->exc_traceback = NULL; - tstate->sys_profilefunc = NULL; - tstate->sys_tracefunc = NULL; + tstate->c_profilefunc = NULL; + tstate->c_tracefunc = NULL; + tstate->c_profileobj = NULL; + tstate->c_traceobj = NULL; HEAD_LOCK(); tstate->next = interp->tstate_head; @@ -152,8 +154,10 @@ PyThreadState_Clear(PyThreadState *tstate) ZAP(tstate->exc_value); ZAP(tstate->exc_traceback); - ZAP(tstate->sys_profilefunc); - ZAP(tstate->sys_tracefunc); + tstate->c_profilefunc = NULL; + tstate->c_tracefunc = NULL; + ZAP(tstate->c_profileobj); + ZAP(tstate->c_traceobj); } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index fe880d5443c..76d40bfd0ba 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -196,20 +196,120 @@ static char setdefaultencoding_doc[] = \n\ Set the current default string encoding used by the Unicode implementation."; -extern int _PyTrace_Init(void); +/* + * Cached interned string objects used for calling the profile and + * trace functions. Initialized by trace_init(). + */ +static PyObject *whatstrings[4] = {NULL, NULL, NULL, NULL}; + +static int +trace_init(void) +{ + static char *whatnames[4] = {"call", "exception", "line", "return"}; + PyObject *name; + int i; + for (i = 0; i < 4; ++i) { + if (whatstrings[i] == NULL) { + name = PyString_InternFromString(whatnames[i]); + if (name == NULL) + return -1; + whatstrings[i] = name; + } + } + return 0; +} + + +static PyObject * +call_trampoline(PyThreadState *tstate, PyObject* callback, + PyFrameObject *frame, int what, PyObject *arg) +{ + PyObject *args = PyTuple_New(3); + PyObject *whatstr; + PyObject *result; + + if (args == NULL) + return NULL; + Py_INCREF(frame); + whatstr = whatstrings[what]; + Py_INCREF(whatstr); + if (arg == NULL) + arg = Py_None; + Py_INCREF(arg); + PyTuple_SET_ITEM(args, 0, (PyObject *)frame); + PyTuple_SET_ITEM(args, 1, whatstr); + PyTuple_SET_ITEM(args, 2, arg); + + /* call the Python-level function */ + PyFrame_FastToLocals(frame); + result = PyEval_CallObject(callback, args); + PyFrame_LocalsToFast(frame, 1); + if (result == NULL) + PyTraceBack_Here(frame); + + /* cleanup */ + Py_DECREF(args); + return result; +} + +static int +profile_trampoline(PyObject *self, PyFrameObject *frame, + int what, PyObject *arg) +{ + PyThreadState *tstate = frame->f_tstate; + PyObject *result; + + result = call_trampoline(tstate, self, frame, what, arg); + if (result == NULL) { + PyEval_SetProfile(NULL, NULL); + return -1; + } + Py_DECREF(result); + return 0; +} + +static int +trace_trampoline(PyObject *self, PyFrameObject *frame, + int what, PyObject *arg) +{ + PyThreadState *tstate = frame->f_tstate; + PyObject *callback; + PyObject *result; + + if (what == PyTrace_CALL) + callback = self; + else + callback = frame->f_trace; + if (callback == NULL) + return 0; + result = call_trampoline(tstate, callback, frame, what, arg); + if (result == NULL) { + PyEval_SetTrace(NULL, NULL); + Py_XDECREF(frame->f_trace); + frame->f_trace = NULL; + return -1; + } + if (result != Py_None) { + PyObject *temp = frame->f_trace; + frame->f_trace = NULL; + Py_XDECREF(temp); + frame->f_trace = result; + } + else { + Py_DECREF(result); + } + return 0; +} static PyObject * sys_settrace(PyObject *self, PyObject *args) { - PyThreadState *tstate = PyThreadState_Get(); - if (_PyTrace_Init() == -1) + if (trace_init() == -1) return NULL; if (args == Py_None) - args = NULL; + PyEval_SetTrace(NULL, NULL); else - Py_XINCREF(args); - Py_XDECREF(tstate->sys_tracefunc); - tstate->sys_tracefunc = args; + PyEval_SetTrace(trace_trampoline, args); Py_INCREF(Py_None); return Py_None; } @@ -223,15 +323,12 @@ function call. See the debugger chapter in the library manual."; static PyObject * sys_setprofile(PyObject *self, PyObject *args) { - PyThreadState *tstate = PyThreadState_Get(); - if (_PyTrace_Init() == -1) + if (trace_init() == -1) return NULL; if (args == Py_None) - args = NULL; + PyEval_SetProfile(NULL, NULL); else - Py_XINCREF(args); - Py_XDECREF(tstate->sys_profilefunc); - tstate->sys_profilefunc = args; + PyEval_SetProfile(profile_trampoline, args); Py_INCREF(Py_None); return Py_None; }