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:
Mark Shannon 2023-04-12 12:04:55 +01:00 • committed by GitHub
parent dce2d38cb0
commit 411b169281
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 6029 additions and 1625 deletions

View file

@ -431,13 +431,13 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
if (_Py_next_func_version != 0) {
_Py_next_func_version++;
}
co->_co_monitoring = NULL;
co->_co_instrumentation_version = 0;
/* not set */
co->co_weakreflist = NULL;
co->co_extra = NULL;
co->_co_cached = NULL;
co->_co_linearray_entry_size = 0;
co->_co_linearray = NULL;
memcpy(_PyCode_CODE(co), PyBytes_AS_STRING(con->code),
PyBytes_GET_SIZE(con->code));
int entry_point = 0;
@ -816,54 +816,6 @@ failed:
* source location tracking (co_lines/co_positions)
******************/
/* Use co_linetable to compute the line number from a bytecode index, addrq. See
lnotab_notes.txt for the details of the lnotab representation.
*/
int
_PyCode_CreateLineArray(PyCodeObject *co)
{
assert(co->_co_linearray == NULL);
PyCodeAddressRange bounds;
int size;
int max_line = 0;
_PyCode_InitAddressRange(co, &bounds);
while(_PyLineTable_NextAddressRange(&bounds)) {
if (bounds.ar_line > max_line) {
max_line = bounds.ar_line;
}
}
if (max_line < (1 << 15)) {
size = 2;
}
else {
size = 4;
}
co->_co_linearray = PyMem_Malloc(Py_SIZE(co)*size);
if (co->_co_linearray == NULL) {
PyErr_NoMemory();
return -1;
}
co->_co_linearray_entry_size = size;
_PyCode_InitAddressRange(co, &bounds);
while(_PyLineTable_NextAddressRange(&bounds)) {
int start = bounds.ar_start / sizeof(_Py_CODEUNIT);
int end = bounds.ar_end / sizeof(_Py_CODEUNIT);
for (int index = start; index < end; index++) {
assert(index < (int)Py_SIZE(co));
if (size == 2) {
assert(((int16_t)bounds.ar_line) == bounds.ar_line);
((int16_t *)co->_co_linearray)[index] = bounds.ar_line;
}
else {
assert(size == 4);
((int32_t *)co->_co_linearray)[index] = bounds.ar_line;
}
}
}
return 0;
}
int
PyCode_Addr2Line(PyCodeObject *co, int addrq)
{
@ -871,9 +823,6 @@ PyCode_Addr2Line(PyCodeObject *co, int addrq)
return co->co_firstlineno;
}
assert(addrq >= 0 && addrq < _PyCode_NBYTES(co));
if (co->_co_linearray) {
return _PyCode_LineNumberFromArray(co, addrq / sizeof(_Py_CODEUNIT));
}
PyCodeAddressRange bounds;
_PyCode_InitAddressRange(co, &bounds);
return _PyCode_CheckLineNumber(addrq, &bounds);
@ -1531,17 +1480,17 @@ PyCode_GetFreevars(PyCodeObject *code)
}
static void
deopt_code(_Py_CODEUNIT *instructions, Py_ssize_t len)
deopt_code(PyCodeObject *code, _Py_CODEUNIT *instructions)
{
Py_ssize_t len = Py_SIZE(code);
for (int i = 0; i < len; i++) {
_Py_CODEUNIT instruction = instructions[i];
int opcode = _PyOpcode_Deopt[instruction.op.code];
int opcode = _Py_GetBaseOpcode(code, i);
int caches = _PyOpcode_Caches[opcode];
instructions[i].op.code = opcode;
while (caches--) {
instructions[++i].op.code = CACHE;
instructions[i].op.arg = 0;
for (int j = 1; j <= caches; j++) {
instructions[i+j].cache = 0;
}
i += caches;
}
}
@ -1559,7 +1508,7 @@ _PyCode_GetCode(PyCodeObject *co)
if (code == NULL) {
return NULL;
}
deopt_code((_Py_CODEUNIT *)PyBytes_AS_STRING(code), Py_SIZE(co));
deopt_code(co, (_Py_CODEUNIT *)PyBytes_AS_STRING(code));
assert(co->_co_cached->_co_code == NULL);
co->_co_cached->_co_code = Py_NewRef(code);
return code;
@ -1693,6 +1642,30 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
return co;
}
static void
free_monitoring_data(_PyCoMonitoringData *data)
{
if (data == NULL) {
return;
}
if (data->tools) {
PyMem_Free(data->tools);
}
if (data->lines) {
PyMem_Free(data->lines);
}
if (data->line_tools) {
PyMem_Free(data->line_tools);
}
if (data->per_instruction_opcodes) {
PyMem_Free(data->per_instruction_opcodes);
}
if (data->per_instruction_tools) {
PyMem_Free(data->per_instruction_tools);
}
PyMem_Free(data);
}
static void
code_dealloc(PyCodeObject *co)
{
@ -1739,9 +1712,7 @@ code_dealloc(PyCodeObject *co)
if (co->co_weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject*)co);
}
if (co->_co_linearray) {
PyMem_Free(co->_co_linearray);
}
free_monitoring_data(co->_co_monitoring);
PyObject_Free(co);
}
@ -1885,7 +1856,7 @@ code_hash(PyCodeObject *co)
SCRAMBLE_IN(co->co_firstlineno);
SCRAMBLE_IN(Py_SIZE(co));
for (int i = 0; i < Py_SIZE(co); i++) {
int deop = _PyOpcode_Deopt[_PyCode_CODE(co)[i].op.code];
int deop = _Py_GetBaseOpcode(co, i);
SCRAMBLE_IN(deop);
SCRAMBLE_IN(_PyCode_CODE(co)[i].op.arg);
i += _PyOpcode_Caches[deop];
@ -2314,7 +2285,7 @@ _PyCode_ConstantKey(PyObject *op)
void
_PyStaticCode_Fini(PyCodeObject *co)
{
deopt_code(_PyCode_CODE(co), Py_SIZE(co));
deopt_code(co, _PyCode_CODE(co));
PyMem_Free(co->co_extra);
if (co->_co_cached != NULL) {
Py_CLEAR(co->_co_cached->_co_code);
@ -2329,10 +2300,8 @@ _PyStaticCode_Fini(PyCodeObject *co)
PyObject_ClearWeakRefs((PyObject *)co);
co->co_weakreflist = NULL;
}
if (co->_co_linearray) {
PyMem_Free(co->_co_linearray);
co->_co_linearray = NULL;
}
free_monitoring_data(co->_co_monitoring);
co->_co_monitoring = NULL;
}
int

View file

@ -17,7 +17,6 @@
static PyMemberDef frame_memberlist[] = {
{"f_trace_lines", T_BOOL, OFF(f_trace_lines), 0},
{"f_trace_opcodes", T_BOOL, OFF(f_trace_opcodes), 0},
{NULL} /* Sentinel */
};
@ -104,24 +103,29 @@ frame_getback(PyFrameObject *f, void *closure)
return res;
}
// Given the index of the effective opcode, scan back to construct the oparg
// with EXTENDED_ARG. This only works correctly with *unquickened* code,
// obtained via a call to _PyCode_GetCode!
static unsigned int
get_arg(const _Py_CODEUNIT *codestr, Py_ssize_t i)
static PyObject *
frame_gettrace_opcodes(PyFrameObject *f, void *closure)
{
_Py_CODEUNIT word;
unsigned int oparg = codestr[i].op.arg;
if (i >= 1 && (word = codestr[i-1]).op.code == EXTENDED_ARG) {
oparg |= word.op.arg << 8;
if (i >= 2 && (word = codestr[i-2]).op.code == EXTENDED_ARG) {
oparg |= word.op.arg << 16;
if (i >= 3 && (word = codestr[i-3]).op.code == EXTENDED_ARG) {
oparg |= word.op.arg << 24;
}
}
PyObject *result = f->f_trace_opcodes ? Py_True : Py_False;
return Py_NewRef(result);
}
static int
frame_settrace_opcodes(PyFrameObject *f, PyObject* value, void *Py_UNUSED(ignored))
{
if (!PyBool_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"attribute value type must be bool");
return -1;
}
return oparg;
if (value == Py_True) {
f->f_trace_opcodes = 1;
_PyInterpreterState_GET()->f_opcode_trace_set = true;
}
else {
f->f_trace_opcodes = 0;
}
return 0;
}
/* Model the evaluation stack, to determine which jumps
@ -299,46 +303,52 @@ mark_stacks(PyCodeObject *code_obj, int len)
while (todo) {
todo = 0;
/* Scan instructions */
for (i = 0; i < len; i++) {
for (i = 0; i < len;) {
int64_t next_stack = stacks[i];
opcode = _Py_GetBaseOpcode(code_obj, i);
int oparg = 0;
while (opcode == EXTENDED_ARG) {
oparg = (oparg << 8) | code[i].op.arg;
i++;
opcode = _Py_GetBaseOpcode(code_obj, i);
stacks[i] = next_stack;
}
int next_i = i + _PyOpcode_Caches[opcode] + 1;
if (next_stack == UNINITIALIZED) {
i = next_i;
continue;
}
opcode = code[i].op.code;
oparg = (oparg << 8) | code[i].op.arg;
switch (opcode) {
case POP_JUMP_IF_FALSE:
case POP_JUMP_IF_TRUE:
{
int64_t target_stack;
int j = get_arg(code, i);
j += i + 1;
int j = next_i + oparg;
assert(j < len);
if (stacks[j] == UNINITIALIZED && j < i) {
todo = 1;
}
next_stack = pop_value(next_stack);
target_stack = next_stack;
assert(stacks[j] == UNINITIALIZED || stacks[j] == target_stack);
stacks[j] = target_stack;
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
}
case SEND:
j = get_arg(code, i) + i + INLINE_CACHE_ENTRIES_SEND + 1;
j = oparg + i + INLINE_CACHE_ENTRIES_SEND + 1;
assert(j < len);
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
stacks[j] = next_stack;
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
case JUMP_FORWARD:
j = get_arg(code, i) + i + 1;
j = oparg + i + 1;
assert(j < len);
assert(stacks[j] == UNINITIALIZED || stacks[j] == next_stack);
stacks[j] = next_stack;
break;
case JUMP_BACKWARD:
case JUMP_BACKWARD_NO_INTERRUPT:
j = i + 1 - get_arg(code, i);
j = i + 1 - oparg;
assert(j >= 0);
assert(j < len);
if (stacks[j] == UNINITIALIZED && j < i) {
@ -350,13 +360,13 @@ mark_stacks(PyCodeObject *code_obj, int len)
case GET_ITER:
case GET_AITER:
next_stack = push_value(pop_value(next_stack), Iterator);
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
case FOR_ITER:
{
int64_t target_stack = push_value(next_stack, Object);
stacks[i+1] = target_stack;
j = get_arg(code, i) + 1 + INLINE_CACHE_ENTRIES_FOR_ITER + i;
stacks[next_i] = target_stack;
j = oparg + 1 + INLINE_CACHE_ENTRIES_FOR_ITER + i;
assert(j < len);
assert(stacks[j] == UNINITIALIZED || stacks[j] == target_stack);
stacks[j] = target_stack;
@ -364,16 +374,16 @@ mark_stacks(PyCodeObject *code_obj, int len)
}
case END_ASYNC_FOR:
next_stack = pop_value(pop_value(next_stack));
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
case PUSH_EXC_INFO:
next_stack = push_value(next_stack, Except);
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
case POP_EXCEPT:
assert(top_of_stack(next_stack) == Except);
next_stack = pop_value(next_stack);
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
case RETURN_VALUE:
assert(pop_value(next_stack) == EMPTY_STACK);
@ -389,57 +399,62 @@ mark_stacks(PyCodeObject *code_obj, int len)
break;
case PUSH_NULL:
next_stack = push_value(next_stack, Null);
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
case LOAD_GLOBAL:
{
int j = get_arg(code, i);
int j = oparg;
if (j & 1) {
next_stack = push_value(next_stack, Null);
}
next_stack = push_value(next_stack, Object);
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
}
case LOAD_ATTR:
{
assert(top_of_stack(next_stack) == Object);
int j = get_arg(code, i);
int j = oparg;
if (j & 1) {
next_stack = pop_value(next_stack);
next_stack = push_value(next_stack, Null);
next_stack = push_value(next_stack, Object);
}
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
}
case CALL:
{
int args = get_arg(code, i);
int args = oparg;
for (int j = 0; j < args+2; j++) {
next_stack = pop_value(next_stack);
}
next_stack = push_value(next_stack, Object);
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
}
case SWAP:
{
int n = get_arg(code, i);
int n = oparg;
next_stack = stack_swap(next_stack, n);
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
}
case COPY:
{
int n = get_arg(code, i);
int n = oparg;
next_stack = push_value(next_stack, peek(next_stack, n));
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
break;
}
case CACHE:
case RESERVED:
{
assert(0);
}
default:
{
int delta = PyCompile_OpcodeStackEffect(opcode, get_arg(code, i));
int delta = PyCompile_OpcodeStackEffect(opcode, oparg);
assert(delta != PY_INVALID_STACK_EFFECT);
while (delta < 0) {
next_stack = pop_value(next_stack);
@ -449,9 +464,10 @@ mark_stacks(PyCodeObject *code_obj, int len)
next_stack = push_value(next_stack, Object);
delta--;
}
stacks[i+1] = next_stack;
stacks[next_i] = next_stack;
}
}
i = next_i;
}
/* Scan exception table */
unsigned char *start = (unsigned char *)PyBytes_AS_STRING(code_obj->co_exceptiontable);
@ -646,31 +662,43 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
* In addition, jumps are forbidden when not tracing,
* as this is a debugging feature.
*/
switch(PyThreadState_GET()->tracing_what) {
case PyTrace_EXCEPTION:
PyErr_SetString(PyExc_ValueError,
"can only jump from a 'line' trace event");
return -1;
case PyTrace_CALL:
int what_event = PyThreadState_GET()->what_event;
if (what_event < 0) {
PyErr_Format(PyExc_ValueError,
"f_lineno can only be set in a trace function");
return -1;
}
switch (what_event) {
case PY_MONITORING_EVENT_PY_RESUME:
case PY_MONITORING_EVENT_JUMP:
case PY_MONITORING_EVENT_BRANCH:
case PY_MONITORING_EVENT_LINE:
case PY_MONITORING_EVENT_PY_YIELD:
/* Setting f_lineno is allowed for the above events */
break;
case PY_MONITORING_EVENT_PY_START:
PyErr_Format(PyExc_ValueError,
"can't jump from the 'call' trace event of a new frame");
return -1;
case PyTrace_LINE:
break;
case PyTrace_RETURN:
if (state == FRAME_SUSPENDED) {
break;
}
/* fall through */
default:
case PY_MONITORING_EVENT_CALL:
case PY_MONITORING_EVENT_C_RETURN:
PyErr_SetString(PyExc_ValueError,
"can't jump during a call");
return -1;
case PY_MONITORING_EVENT_PY_RETURN:
case PY_MONITORING_EVENT_PY_UNWIND:
case PY_MONITORING_EVENT_PY_THROW:
case PY_MONITORING_EVENT_RAISE:
case PY_MONITORING_EVENT_C_RAISE:
case PY_MONITORING_EVENT_INSTRUCTION:
case PY_MONITORING_EVENT_EXCEPTION_HANDLED:
PyErr_Format(PyExc_ValueError,
"can only jump from a 'line' trace event");
return -1;
}
if (!f->f_trace) {
PyErr_Format(PyExc_ValueError,
"f_lineno can only be set by a trace function");
return -1;
default:
PyErr_SetString(PyExc_SystemError,
"unexpected event type");
return -1;
}
int new_lineno;
@ -803,6 +831,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
start_stack = pop_value(start_stack);
}
/* Finally set the new lasti and return OK. */
f->f_last_traced_line = new_lineno;
f->f_lineno = 0;
f->f_frame->prev_instr = _PyCode_CODE(f->f_frame->f_code) + best_addr;
return 0;
@ -823,7 +852,10 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
if (v == Py_None) {
v = NULL;
}
Py_XSETREF(f->f_trace, Py_XNewRef(v));
if (v != f->f_trace) {
Py_XSETREF(f->f_trace, Py_XNewRef(v));
f->f_last_traced_line = -1;
}
return 0;
}
@ -838,6 +870,7 @@ static PyGetSetDef frame_getsetlist[] = {
{"f_globals", (getter)frame_getglobals, NULL, NULL},
{"f_builtins", (getter)frame_getbuiltins, NULL, NULL},
{"f_code", (getter)frame_getcode, NULL, NULL},
{"f_trace_opcodes", (getter)frame_gettrace_opcodes, (setter)frame_settrace_opcodes, NULL},
{0}
};
@ -1023,6 +1056,7 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
f->f_trace_opcodes = 0;
f->f_fast_as_locals = 0;
f->f_lineno = 0;
f->f_last_traced_line = -1;
return f;
}

View file

@ -1972,6 +1972,7 @@ extern PyTypeObject _Py_GenericAliasIterType;
extern PyTypeObject _PyMemoryIter_Type;
extern PyTypeObject _PyLineIterator;
extern PyTypeObject _PyPositionsIterator;
extern PyTypeObject _PyLegacyEventHandler_Type;
static PyTypeObject* static_types[] = {
// The two most important base types: must be initialized first and
@ -2069,6 +2070,7 @@ static PyTypeObject* static_types[] = {
&_PyHamt_BitmapNode_Type,
&_PyHamt_CollisionNode_Type,
&_PyHamt_Type,
&_PyLegacyEventHandler_Type,
&_PyInterpreterID_Type,
&_PyLineIterator,
&_PyManagedBuffer_Type,