mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
GH-103082: Implementation of PEP 669: Low Impact Monitoring for CPython (GH-103083)
* The majority of the monitoring code is in instrumentation.c * The new instrumentation bytecodes are in bytecodes.c * legacy_tracing.c adapts the new API to the old sys.setrace and sys.setprofile APIs
This commit is contained in:
parent
dce2d38cb0
commit
411b169281
44 changed files with 6029 additions and 1625 deletions
639
Python/ceval.c
639
Python/ceval.c
|
@ -10,6 +10,7 @@
|
|||
#include "pycore_function.h"
|
||||
#include "pycore_intrinsics.h"
|
||||
#include "pycore_long.h" // _PyLong_GetZero()
|
||||
#include "pycore_instruments.h"
|
||||
#include "pycore_object.h" // _PyObject_GC_TRACK()
|
||||
#include "pycore_moduleobject.h" // PyModuleObject
|
||||
#include "pycore_opcode.h" // EXTRA_CASES
|
||||
|
@ -92,13 +93,6 @@
|
|||
#define _Py_atomic_load_relaxed_int32(ATOMIC_VAL) _Py_atomic_load_relaxed(ATOMIC_VAL)
|
||||
#endif
|
||||
|
||||
/* Forward declarations */
|
||||
static PyObject *trace_call_function(
|
||||
PyThreadState *tstate, PyObject *callable, PyObject **stack,
|
||||
Py_ssize_t oparg, PyObject *kwnames);
|
||||
static PyObject * do_call_core(
|
||||
PyThreadState *tstate, PyObject *func,
|
||||
PyObject *callargs, PyObject *kwdict, int use_tracing);
|
||||
|
||||
#ifdef LLTRACE
|
||||
static void
|
||||
|
@ -179,19 +173,22 @@ lltrace_resume_frame(_PyInterpreterFrame *frame)
|
|||
PyErr_SetRaisedException(exc);
|
||||
}
|
||||
#endif
|
||||
static int call_trace(Py_tracefunc, PyObject *,
|
||||
PyThreadState *, _PyInterpreterFrame *,
|
||||
int, PyObject *);
|
||||
static int call_trace_protected(Py_tracefunc, PyObject *,
|
||||
PyThreadState *, _PyInterpreterFrame *,
|
||||
int, PyObject *);
|
||||
static void call_exc_trace(Py_tracefunc, PyObject *,
|
||||
PyThreadState *, _PyInterpreterFrame *);
|
||||
static int maybe_call_line_trace(Py_tracefunc, PyObject *,
|
||||
PyThreadState *, _PyInterpreterFrame *, int);
|
||||
static void maybe_dtrace_line(_PyInterpreterFrame *, PyTraceInfo *, int);
|
||||
static void dtrace_function_entry(_PyInterpreterFrame *);
|
||||
static void dtrace_function_return(_PyInterpreterFrame *);
|
||||
|
||||
static void monitor_raise(PyThreadState *tstate,
|
||||
_PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr);
|
||||
static int monitor_stop_iteration(PyThreadState *tstate,
|
||||
_PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr);
|
||||
static void monitor_unwind(PyThreadState *tstate,
|
||||
_PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr);
|
||||
static void monitor_handled(PyThreadState *tstate,
|
||||
_PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr, PyObject *exc);
|
||||
static void monitor_throw(PyThreadState *tstate,
|
||||
_PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr);
|
||||
|
||||
static PyObject * import_name(PyThreadState *, _PyInterpreterFrame *,
|
||||
PyObject *, PyObject *, PyObject *);
|
||||
|
@ -217,21 +214,6 @@ _PyEvalFrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame);
|
|||
"cannot access free variable '%s' where it is not associated with a" \
|
||||
" value in enclosing scope"
|
||||
|
||||
#ifndef NDEBUG
|
||||
/* Ensure that tstate is valid: sanity check for PyEval_AcquireThread() and
|
||||
PyEval_RestoreThread(). Detect if tstate memory was freed. It can happen
|
||||
when a thread continues to run after Python finalization, especially
|
||||
daemon threads. */
|
||||
static int
|
||||
is_tstate_valid(PyThreadState *tstate)
|
||||
{
|
||||
assert(!_PyMem_IsPtrFreed(tstate));
|
||||
assert(!_PyMem_IsPtrFreed(tstate->interp));
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef HAVE_ERRNO_H
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
@ -596,63 +578,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
|
|||
|
||||
#include "ceval_macros.h"
|
||||
|
||||
static int
|
||||
trace_function_entry(PyThreadState *tstate, _PyInterpreterFrame *frame)
|
||||
{
|
||||
if (tstate->c_tracefunc != NULL) {
|
||||
/* tstate->c_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 will be called at
|
||||
the start of each executed line of code.
|
||||
(Actually, the function must return itself
|
||||
in order to continue tracing.) The trace
|
||||
functions are called with three arguments:
|
||||
a pointer to the current frame, a string
|
||||
indicating why the function is called, and
|
||||
an argument which depends on the situation.
|
||||
The global trace function is also called
|
||||
whenever an exception is detected. */
|
||||
if (call_trace_protected(tstate->c_tracefunc,
|
||||
tstate->c_traceobj,
|
||||
tstate, frame,
|
||||
PyTrace_CALL, Py_None)) {
|
||||
/* Trace function raised an error */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (tstate->c_profilefunc != NULL) {
|
||||
/* Similar for c_profilefunc, except it needn't
|
||||
return itself and isn't called for "line" events */
|
||||
if (call_trace_protected(tstate->c_profilefunc,
|
||||
tstate->c_profileobj,
|
||||
tstate, frame,
|
||||
PyTrace_CALL, Py_None)) {
|
||||
/* Profile function raised an error */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_function_exit(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject *retval)
|
||||
{
|
||||
if (tstate->c_tracefunc) {
|
||||
if (call_trace_protected(tstate->c_tracefunc, tstate->c_traceobj,
|
||||
tstate, frame, PyTrace_RETURN, retval)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (tstate->c_profilefunc) {
|
||||
if (call_trace_protected(tstate->c_profilefunc, tstate->c_profileobj,
|
||||
tstate, frame, PyTrace_RETURN, retval)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int _Py_CheckRecursiveCallPy(
|
||||
PyThreadState *tstate)
|
||||
|
@ -730,7 +655,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
* strict stack discipline must be maintained.
|
||||
*/
|
||||
_PyCFrame *prev_cframe = tstate->cframe;
|
||||
cframe.use_tracing = prev_cframe->use_tracing;
|
||||
cframe.previous = prev_cframe;
|
||||
tstate->cframe = &cframe;
|
||||
|
||||
|
@ -765,8 +689,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
if (_Py_EnterRecursivePy(tstate)) {
|
||||
goto exit_unwind;
|
||||
}
|
||||
TRACE_FUNCTION_THROW_ENTRY();
|
||||
DTRACE_FUNCTION_ENTRY();
|
||||
/* Because this avoids the RESUME,
|
||||
* we need to update instrumentation */
|
||||
_Py_Instrument(frame->f_code, tstate->interp);
|
||||
monitor_throw(tstate, frame, frame->prev_instr);
|
||||
/* TO DO -- Monitor throw entry. */
|
||||
goto resume_with_error;
|
||||
}
|
||||
|
||||
|
@ -781,15 +708,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
assert(_PyInterpreterFrame_LASTI(frame) >= -1); \
|
||||
/* Jump back to the last instruction executed... */ \
|
||||
next_instr = frame->prev_instr + 1; \
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame); \
|
||||
/* Set stackdepth to -1. \
|
||||
Update when returning or calling trace function. \
|
||||
Having stackdepth <= 0 ensures that invalid \
|
||||
values are not visible to the cycle GC. \
|
||||
We choose -1 rather than 0 to assist debugging. \
|
||||
*/ \
|
||||
frame->stacktop = -1;
|
||||
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame);
|
||||
|
||||
start_frame:
|
||||
if (_Py_EnterRecursivePy(tstate)) {
|
||||
|
@ -845,91 +764,6 @@ handle_eval_breaker:
|
|||
|
||||
#include "generated_cases.c.h"
|
||||
|
||||
#if USE_COMPUTED_GOTOS
|
||||
TARGET_DO_TRACING:
|
||||
#else
|
||||
case DO_TRACING:
|
||||
#endif
|
||||
{
|
||||
assert(cframe.use_tracing);
|
||||
assert(tstate->tracing == 0);
|
||||
if (INSTR_OFFSET() >= frame->f_code->_co_firsttraceable) {
|
||||
int instr_prev = _PyInterpreterFrame_LASTI(frame);
|
||||
frame->prev_instr = next_instr;
|
||||
NEXTOPARG();
|
||||
// No _PyOpcode_Deopt here, since RESUME has no optimized forms:
|
||||
if (opcode == RESUME) {
|
||||
if (oparg < 2) {
|
||||
CHECK_EVAL_BREAKER();
|
||||
}
|
||||
/* Call tracing */
|
||||
TRACE_FUNCTION_ENTRY();
|
||||
DTRACE_FUNCTION_ENTRY();
|
||||
}
|
||||
else {
|
||||
/* line-by-line tracing support */
|
||||
if (PyDTrace_LINE_ENABLED()) {
|
||||
maybe_dtrace_line(frame, &tstate->trace_info, instr_prev);
|
||||
}
|
||||
|
||||
if (cframe.use_tracing &&
|
||||
tstate->c_tracefunc != NULL && !tstate->tracing) {
|
||||
int err;
|
||||
/* see maybe_call_line_trace()
|
||||
for expository comments */
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
|
||||
err = maybe_call_line_trace(tstate->c_tracefunc,
|
||||
tstate->c_traceobj,
|
||||
tstate, frame, instr_prev);
|
||||
// Reload possibly changed frame fields:
|
||||
stack_pointer = _PyFrame_GetStackPointer(frame);
|
||||
frame->stacktop = -1;
|
||||
// next_instr is only reloaded if tracing *does not* raise.
|
||||
// This is consistent with the behavior of older Python
|
||||
// versions. If a trace function sets a new f_lineno and
|
||||
// *then* raises, we use the *old* location when searching
|
||||
// for an exception handler, displaying the traceback, and
|
||||
// so on:
|
||||
if (err) {
|
||||
// next_instr wasn't incremented at the start of this
|
||||
// instruction. Increment it before handling the error,
|
||||
// so that it looks the same as a "normal" instruction:
|
||||
next_instr++;
|
||||
goto error;
|
||||
}
|
||||
// Reload next_instr. Don't increment it, though, since
|
||||
// we're going to re-dispatch to the "true" instruction now:
|
||||
next_instr = frame->prev_instr;
|
||||
}
|
||||
}
|
||||
}
|
||||
NEXTOPARG();
|
||||
PRE_DISPATCH_GOTO();
|
||||
// No _PyOpcode_Deopt here, since EXTENDED_ARG has no optimized forms:
|
||||
while (opcode == EXTENDED_ARG) {
|
||||
// CPython hasn't ever traced the instruction after an EXTENDED_ARG.
|
||||
// Inline the EXTENDED_ARG here, so we can avoid branching there:
|
||||
INSTRUCTION_START(EXTENDED_ARG);
|
||||
opcode = next_instr->op.code;
|
||||
oparg = oparg << 8 | next_instr->op.arg;
|
||||
// Make sure the next instruction isn't a RESUME, since that needs
|
||||
// to trace properly (and shouldn't have an EXTENDED_ARG, anyways):
|
||||
assert(opcode != RESUME);
|
||||
PRE_DISPATCH_GOTO();
|
||||
}
|
||||
opcode = _PyOpcode_Deopt[opcode];
|
||||
if (_PyOpcode_Caches[opcode]) {
|
||||
uint16_t *counter = &next_instr[1].cache;
|
||||
// The instruction is going to decrement the counter, so we need to
|
||||
// increment it here to make sure it doesn't try to specialize:
|
||||
if (!ADAPTIVE_COUNTER_IS_MAX(*counter)) {
|
||||
INCREMENT_ADAPTIVE_COUNTER(*counter);
|
||||
}
|
||||
}
|
||||
DISPATCH_GOTO();
|
||||
}
|
||||
|
||||
#if USE_COMPUTED_GOTOS
|
||||
_unknown_opcode:
|
||||
#else
|
||||
|
@ -988,12 +822,7 @@ error:
|
|||
PyTraceBack_Here(f);
|
||||
}
|
||||
}
|
||||
|
||||
if (tstate->c_tracefunc != NULL) {
|
||||
/* Make sure state is set to FRAME_UNWINDING for tracing */
|
||||
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj,
|
||||
tstate, frame);
|
||||
}
|
||||
monitor_raise(tstate, frame, next_instr-1);
|
||||
|
||||
exception_unwind:
|
||||
{
|
||||
|
@ -1012,8 +841,7 @@ exception_unwind:
|
|||
}
|
||||
assert(STACK_LEVEL() == 0);
|
||||
_PyFrame_SetStackPointer(frame, stack_pointer);
|
||||
TRACE_FUNCTION_UNWIND();
|
||||
DTRACE_FUNCTION_EXIT();
|
||||
monitor_unwind(tstate, frame, next_instr-1);
|
||||
goto exit_unwind;
|
||||
}
|
||||
|
||||
|
@ -1036,8 +864,10 @@ exception_unwind:
|
|||
available to the handler,
|
||||
so a program can emulate the
|
||||
Python main loop. */
|
||||
PUSH(_PyErr_GetRaisedException(tstate));
|
||||
PyObject *exc = _PyErr_GetRaisedException(tstate);
|
||||
PUSH(exc);
|
||||
JUMPTO(handler);
|
||||
monitor_handled(tstate, frame, next_instr, exc);
|
||||
/* Resume normal execution */
|
||||
DISPATCH();
|
||||
}
|
||||
|
@ -1054,7 +884,6 @@ exit_unwind:
|
|||
if (frame == &entry_frame) {
|
||||
/* Restore previous cframe and exit */
|
||||
tstate->cframe = cframe.previous;
|
||||
tstate->cframe->use_tracing = cframe.use_tracing;
|
||||
assert(tstate->cframe->current_frame == frame->previous);
|
||||
_Py_LeaveRecursiveCallTstate(tstate);
|
||||
return NULL;
|
||||
|
@ -2020,105 +1849,108 @@ Error:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
call_exc_trace(Py_tracefunc func, PyObject *self,
|
||||
PyThreadState *tstate,
|
||||
_PyInterpreterFrame *f)
|
||||
static int
|
||||
do_monitor_exc(PyThreadState *tstate, _PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr, int event)
|
||||
{
|
||||
PyObject *exc = _PyErr_GetRaisedException(tstate);
|
||||
assert(exc && PyExceptionInstance_Check(exc));
|
||||
PyObject *type = PyExceptionInstance_Class(exc);
|
||||
PyObject *traceback = PyException_GetTraceback(exc);
|
||||
if (traceback == NULL) {
|
||||
traceback = Py_NewRef(Py_None);
|
||||
}
|
||||
PyObject *arg = PyTuple_Pack(3, type, exc, traceback);
|
||||
Py_XDECREF(traceback);
|
||||
|
||||
if (arg == NULL) {
|
||||
_PyErr_SetRaisedException(tstate, exc);
|
||||
return;
|
||||
}
|
||||
int err = call_trace(func, self, tstate, f, PyTrace_EXCEPTION, arg);
|
||||
Py_DECREF(arg);
|
||||
assert(event < PY_MONITORING_UNGROUPED_EVENTS);
|
||||
PyObject *exc = PyErr_GetRaisedException();
|
||||
assert(exc != NULL);
|
||||
int err = _Py_call_instrumentation_arg(tstate, event, frame, instr, exc);
|
||||
if (err == 0) {
|
||||
_PyErr_SetRaisedException(tstate, exc);
|
||||
PyErr_SetRaisedException(exc);
|
||||
}
|
||||
else {
|
||||
Py_XDECREF(exc);
|
||||
Py_DECREF(exc);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int
|
||||
no_tools_for_event(PyThreadState *tstate, _PyInterpreterFrame *frame, int event)
|
||||
{
|
||||
_PyCoMonitoringData *data = frame->f_code->_co_monitoring;
|
||||
if (data) {
|
||||
if (data->active_monitors.tools[event] == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tstate->interp->monitors.tools[event] == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
monitor_raise(PyThreadState *tstate, _PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr)
|
||||
{
|
||||
if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_RAISE)) {
|
||||
return;
|
||||
}
|
||||
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_RAISE);
|
||||
}
|
||||
|
||||
static int
|
||||
call_trace_protected(Py_tracefunc func, PyObject *obj,
|
||||
PyThreadState *tstate, _PyInterpreterFrame *frame,
|
||||
int what, PyObject *arg)
|
||||
monitor_stop_iteration(PyThreadState *tstate, _PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr)
|
||||
{
|
||||
PyObject *exc = _PyErr_GetRaisedException(tstate);
|
||||
int err = call_trace(func, obj, tstate, frame, what, arg);
|
||||
if (err == 0)
|
||||
{
|
||||
_PyErr_SetRaisedException(tstate, exc);
|
||||
if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_STOP_ITERATION)) {
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
Py_XDECREF(exc);
|
||||
return -1;
|
||||
}
|
||||
return do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_STOP_ITERATION);
|
||||
}
|
||||
|
||||
static void
|
||||
initialize_trace_info(PyTraceInfo *trace_info, _PyInterpreterFrame *frame)
|
||||
monitor_unwind(PyThreadState *tstate,
|
||||
_PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr)
|
||||
{
|
||||
PyCodeObject *code = frame->f_code;
|
||||
if (trace_info->code != code) {
|
||||
trace_info->code = code;
|
||||
_PyCode_InitAddressRange(code, &trace_info->bounds);
|
||||
if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_PY_UNWIND)) {
|
||||
return;
|
||||
}
|
||||
_Py_call_instrumentation_exc0(tstate, PY_MONITORING_EVENT_PY_UNWIND, frame, instr);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
monitor_handled(PyThreadState *tstate,
|
||||
_PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr, PyObject *exc)
|
||||
{
|
||||
if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
|
||||
return;
|
||||
}
|
||||
_Py_call_instrumentation_arg(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED, frame, instr, exc);
|
||||
}
|
||||
|
||||
static void
|
||||
monitor_throw(PyThreadState *tstate,
|
||||
_PyInterpreterFrame *frame,
|
||||
_Py_CODEUNIT *instr)
|
||||
{
|
||||
if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_PY_THROW)) {
|
||||
return;
|
||||
}
|
||||
_Py_call_instrumentation_exc0(tstate, PY_MONITORING_EVENT_PY_THROW, frame, instr);
|
||||
}
|
||||
|
||||
void
|
||||
PyThreadState_EnterTracing(PyThreadState *tstate)
|
||||
{
|
||||
assert(tstate->tracing >= 0);
|
||||
tstate->tracing++;
|
||||
tstate->cframe->use_tracing = 0;
|
||||
}
|
||||
|
||||
void
|
||||
PyThreadState_LeaveTracing(PyThreadState *tstate)
|
||||
{
|
||||
assert(tstate->tracing > 0 && tstate->cframe->use_tracing == 0);
|
||||
assert(tstate->tracing > 0);
|
||||
tstate->tracing--;
|
||||
_PyThreadState_UpdateTracingState(tstate);
|
||||
}
|
||||
|
||||
static int
|
||||
call_trace(Py_tracefunc func, PyObject *obj,
|
||||
PyThreadState *tstate, _PyInterpreterFrame *frame,
|
||||
int what, PyObject *arg)
|
||||
{
|
||||
int result;
|
||||
if (tstate->tracing) {
|
||||
return 0;
|
||||
}
|
||||
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
|
||||
if (f == NULL) {
|
||||
return -1;
|
||||
}
|
||||
int old_what = tstate->tracing_what;
|
||||
tstate->tracing_what = what;
|
||||
PyThreadState_EnterTracing(tstate);
|
||||
assert(_PyInterpreterFrame_LASTI(frame) >= 0);
|
||||
if (_PyCode_InitLineArray(frame->f_code)) {
|
||||
return -1;
|
||||
}
|
||||
f->f_lineno = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
|
||||
result = func(obj, f, what, arg);
|
||||
f->f_lineno = 0;
|
||||
PyThreadState_LeaveTracing(tstate);
|
||||
tstate->tracing_what = old_what;
|
||||
return result;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
_PyEval_CallTracing(PyObject *func, PyObject *args)
|
||||
|
@ -2126,7 +1958,6 @@ _PyEval_CallTracing(PyObject *func, PyObject *args)
|
|||
// Save and disable tracing
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
int save_tracing = tstate->tracing;
|
||||
int save_use_tracing = tstate->cframe->use_tracing;
|
||||
tstate->tracing = 0;
|
||||
|
||||
// Call the tracing function
|
||||
|
@ -2134,81 +1965,9 @@ _PyEval_CallTracing(PyObject *func, PyObject *args)
|
|||
|
||||
// Restore tracing
|
||||
tstate->tracing = save_tracing;
|
||||
tstate->cframe->use_tracing = save_use_tracing;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* See Objects/lnotab_notes.txt for a description of how tracing works. */
|
||||
static int
|
||||
maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
|
||||
PyThreadState *tstate, _PyInterpreterFrame *frame, int instr_prev)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
/* If the last instruction falls at the start of a line or if it
|
||||
represents a jump backwards, update the frame's line number and
|
||||
then call the trace function if we're tracing source lines.
|
||||
*/
|
||||
if (_PyCode_InitLineArray(frame->f_code)) {
|
||||
return -1;
|
||||
}
|
||||
int lastline;
|
||||
if (instr_prev <= frame->f_code->_co_firsttraceable) {
|
||||
lastline = -1;
|
||||
}
|
||||
else {
|
||||
lastline = _PyCode_LineNumberFromArray(frame->f_code, instr_prev);
|
||||
}
|
||||
int line = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
|
||||
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
|
||||
if (f == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (line != -1 && f->f_trace_lines) {
|
||||
/* Trace backward edges (except in 'yield from') or if line number has changed */
|
||||
int trace = line != lastline ||
|
||||
(_PyInterpreterFrame_LASTI(frame) < instr_prev &&
|
||||
// SEND has no quickened forms, so no need to use _PyOpcode_Deopt
|
||||
// here:
|
||||
frame->prev_instr->op.code != SEND);
|
||||
if (trace) {
|
||||
result = call_trace(func, obj, tstate, frame, PyTrace_LINE, Py_None);
|
||||
}
|
||||
}
|
||||
/* Always emit an opcode event if we're tracing all opcodes. */
|
||||
if (f->f_trace_opcodes && result == 0) {
|
||||
result = call_trace(func, obj, tstate, frame, PyTrace_OPCODE, Py_None);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
_PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
|
||||
{
|
||||
assert(is_tstate_valid(tstate));
|
||||
/* The caller must hold the GIL */
|
||||
assert(PyGILState_Check());
|
||||
|
||||
/* Call _PySys_Audit() in the context of the current thread state,
|
||||
even if tstate is not the current thread state. */
|
||||
PyThreadState *current_tstate = _PyThreadState_GET();
|
||||
if (_PySys_Audit(current_tstate, "sys.setprofile", NULL) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
tstate->c_profilefunc = func;
|
||||
PyObject *old_profileobj = tstate->c_profileobj;
|
||||
tstate->c_profileobj = Py_XNewRef(arg);
|
||||
/* Flag that tracing or profiling is turned on */
|
||||
_PyThreadState_UpdateTracingState(tstate);
|
||||
|
||||
// gh-98257: Only call Py_XDECREF() once the new profile function is fully
|
||||
// set, so it's safe to call sys.setprofile() again (reentrant call).
|
||||
Py_XDECREF(old_profileobj);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
PyEval_SetProfile(Py_tracefunc func, PyObject *arg)
|
||||
{
|
||||
|
@ -2240,33 +1999,6 @@ PyEval_SetProfileAllThreads(Py_tracefunc func, PyObject *arg)
|
|||
}
|
||||
}
|
||||
|
||||
int
|
||||
_PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
|
||||
{
|
||||
assert(is_tstate_valid(tstate));
|
||||
/* The caller must hold the GIL */
|
||||
assert(PyGILState_Check());
|
||||
|
||||
/* Call _PySys_Audit() in the context of the current thread state,
|
||||
even if tstate is not the current thread state. */
|
||||
PyThreadState *current_tstate = _PyThreadState_GET();
|
||||
if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
tstate->c_tracefunc = func;
|
||||
PyObject *old_traceobj = tstate->c_traceobj;
|
||||
tstate->c_traceobj = Py_XNewRef(arg);
|
||||
/* Flag that tracing or profiling is turned on */
|
||||
_PyThreadState_UpdateTracingState(tstate);
|
||||
|
||||
// gh-98257: Only call Py_XDECREF() once the new trace function is fully
|
||||
// set, so it's safe to call sys.settrace() again (reentrant call).
|
||||
Py_XDECREF(old_traceobj);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
PyEval_SetTrace(Py_tracefunc func, PyObject *arg)
|
||||
{
|
||||
|
@ -2492,114 +2224,6 @@ PyEval_GetFuncDesc(PyObject *func)
|
|||
return " object";
|
||||
}
|
||||
|
||||
#define C_TRACE(x, call) \
|
||||
if (use_tracing && tstate->c_profilefunc) { \
|
||||
if (call_trace(tstate->c_profilefunc, tstate->c_profileobj, \
|
||||
tstate, tstate->cframe->current_frame, \
|
||||
PyTrace_C_CALL, func)) { \
|
||||
x = NULL; \
|
||||
} \
|
||||
else { \
|
||||
x = call; \
|
||||
if (tstate->c_profilefunc != NULL) { \
|
||||
if (x == NULL) { \
|
||||
call_trace_protected(tstate->c_profilefunc, \
|
||||
tstate->c_profileobj, \
|
||||
tstate, tstate->cframe->current_frame, \
|
||||
PyTrace_C_EXCEPTION, func); \
|
||||
/* XXX should pass (type, value, tb) */ \
|
||||
} else { \
|
||||
if (call_trace(tstate->c_profilefunc, \
|
||||
tstate->c_profileobj, \
|
||||
tstate, tstate->cframe->current_frame, \
|
||||
PyTrace_C_RETURN, func)) { \
|
||||
Py_DECREF(x); \
|
||||
x = NULL; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} else { \
|
||||
x = call; \
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
trace_call_function(PyThreadState *tstate,
|
||||
PyObject *func,
|
||||
PyObject **args, Py_ssize_t nargs,
|
||||
PyObject *kwnames)
|
||||
{
|
||||
int use_tracing = 1;
|
||||
PyObject *x;
|
||||
if (PyCFunction_CheckExact(func) || PyCMethod_CheckExact(func)) {
|
||||
C_TRACE(x, PyObject_Vectorcall(func, args, nargs, kwnames));
|
||||
return x;
|
||||
}
|
||||
else if (Py_IS_TYPE(func, &PyMethodDescr_Type) && nargs > 0) {
|
||||
/* We need to create a temporary bound method as argument
|
||||
for profiling.
|
||||
|
||||
If nargs == 0, then this cannot work because we have no
|
||||
"self". In any case, the call itself would raise
|
||||
TypeError (foo needs an argument), so we just skip
|
||||
profiling. */
|
||||
PyObject *self = args[0];
|
||||
func = Py_TYPE(func)->tp_descr_get(func, self, (PyObject*)Py_TYPE(self));
|
||||
if (func == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
C_TRACE(x, PyObject_Vectorcall(func,
|
||||
args+1, nargs-1,
|
||||
kwnames));
|
||||
Py_DECREF(func);
|
||||
return x;
|
||||
}
|
||||
return PyObject_Vectorcall(func, args, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
do_call_core(PyThreadState *tstate,
|
||||
PyObject *func,
|
||||
PyObject *callargs,
|
||||
PyObject *kwdict,
|
||||
int use_tracing
|
||||
)
|
||||
{
|
||||
PyObject *result;
|
||||
if (PyCFunction_CheckExact(func) || PyCMethod_CheckExact(func)) {
|
||||
C_TRACE(result, PyObject_Call(func, callargs, kwdict));
|
||||
return result;
|
||||
}
|
||||
else if (Py_IS_TYPE(func, &PyMethodDescr_Type)) {
|
||||
Py_ssize_t nargs = PyTuple_GET_SIZE(callargs);
|
||||
if (nargs > 0 && use_tracing) {
|
||||
/* We need to create a temporary bound method as argument
|
||||
for profiling.
|
||||
|
||||
If nargs == 0, then this cannot work because we have no
|
||||
"self". In any case, the call itself would raise
|
||||
TypeError (foo needs an argument), so we just skip
|
||||
profiling. */
|
||||
PyObject *self = PyTuple_GET_ITEM(callargs, 0);
|
||||
func = Py_TYPE(func)->tp_descr_get(func, self, (PyObject*)Py_TYPE(self));
|
||||
if (func == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
C_TRACE(result, _PyObject_FastCallDictTstate(
|
||||
tstate, func,
|
||||
&_PyTuple_ITEMS(callargs)[1],
|
||||
nargs - 1,
|
||||
kwdict));
|
||||
Py_DECREF(func);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func);
|
||||
return PyObject_Call(func, callargs, kwdict);
|
||||
}
|
||||
|
||||
/* Extract a slice index from a PyLong or an object with the
|
||||
nb_index slot defined, and store in *pi.
|
||||
Silently reduce values larger than PY_SSIZE_T_MAX to PY_SSIZE_T_MAX,
|
||||
|
@ -2973,69 +2597,6 @@ PyUnstable_Eval_RequestCodeExtraIndex(freefunc free)
|
|||
return new_index;
|
||||
}
|
||||
|
||||
static void
|
||||
dtrace_function_entry(_PyInterpreterFrame *frame)
|
||||
{
|
||||
const char *filename;
|
||||
const char *funcname;
|
||||
int lineno;
|
||||
|
||||
PyCodeObject *code = frame->f_code;
|
||||
filename = PyUnicode_AsUTF8(code->co_filename);
|
||||
funcname = PyUnicode_AsUTF8(code->co_name);
|
||||
lineno = _PyInterpreterFrame_GetLine(frame);
|
||||
|
||||
PyDTrace_FUNCTION_ENTRY(filename, funcname, lineno);
|
||||
}
|
||||
|
||||
static void
|
||||
dtrace_function_return(_PyInterpreterFrame *frame)
|
||||
{
|
||||
const char *filename;
|
||||
const char *funcname;
|
||||
int lineno;
|
||||
|
||||
PyCodeObject *code = frame->f_code;
|
||||
filename = PyUnicode_AsUTF8(code->co_filename);
|
||||
funcname = PyUnicode_AsUTF8(code->co_name);
|
||||
lineno = _PyInterpreterFrame_GetLine(frame);
|
||||
|
||||
PyDTrace_FUNCTION_RETURN(filename, funcname, lineno);
|
||||
}
|
||||
|
||||
/* DTrace equivalent of maybe_call_line_trace. */
|
||||
static void
|
||||
maybe_dtrace_line(_PyInterpreterFrame *frame,
|
||||
PyTraceInfo *trace_info,
|
||||
int instr_prev)
|
||||
{
|
||||
const char *co_filename, *co_name;
|
||||
|
||||
/* If the last instruction executed isn't in the current
|
||||
instruction window, reset the window.
|
||||
*/
|
||||
initialize_trace_info(trace_info, frame);
|
||||
int lastline = _PyCode_CheckLineNumber(instr_prev*sizeof(_Py_CODEUNIT), &trace_info->bounds);
|
||||
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
|
||||
int line = _PyCode_CheckLineNumber(addr, &trace_info->bounds);
|
||||
if (line != -1) {
|
||||
/* Trace backward edges or first instruction of a new line */
|
||||
if (_PyInterpreterFrame_LASTI(frame) < instr_prev ||
|
||||
(line != lastline && addr == trace_info->bounds.ar_start))
|
||||
{
|
||||
co_filename = PyUnicode_AsUTF8(frame->f_code->co_filename);
|
||||
if (!co_filename) {
|
||||
co_filename = "?";
|
||||
}
|
||||
co_name = PyUnicode_AsUTF8(frame->f_code->co_name);
|
||||
if (!co_name) {
|
||||
co_name = "?";
|
||||
}
|
||||
PyDTrace_LINE(co_filename, co_name, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Implement Py_EnterRecursiveCall() and Py_LeaveRecursiveCall() as functions
|
||||
for the limited API. */
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue