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

@ -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;
}