changeset 18326:e427afb5637f legacy-trunk

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. [#436058]
author Fred Drake <fdrake@acm.org>
date Wed, 27 Jun 2001 19:19:46 +0000
parents fb35b3a22604
children 93bdc5ece1bf
files Python/ceval.c Python/pystate.c Python/sysmodule.c
diffstat 3 files changed, 182 insertions(+), 150 deletions(-) [+]
line wrap: on
line diff
--- 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 ***,
 #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 *g
 		}
 	}
 
-	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 *g
 		   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, PyObj
 		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, PyObj
 	}
 }
 
-/* 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 *
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -120,8 +120,10 @@ PyThreadState_New(PyInterpreterState *in
 		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 *tstat
 	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);
 }
 
 
--- 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
 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;
 }