gh-109094: replace frame->prev_instr by frame->instr_ptr (#109095)

This commit is contained in:
Irit Katriel 2023-10-26 14:43:10 +01:00 committed by GitHub
parent 573eff3e2e
commit 67a91f78e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 249 additions and 164 deletions

View file

@ -130,3 +130,28 @@ returns. This extra frame is inserted so that `RETURN_VALUE`, `YIELD_VALUE`, and
`RETURN_GENERATOR` do not need to check whether the current frame is the entry frame.
The shim frame points to a special code object containing the `INTERPRETER_EXIT`
instruction which cleans up the shim frame and returns.
### The Instruction Pointer
`_PyInterpreterFrame` has two fields which are used to maintain the instruction
pointer: `instr_ptr` and `return_offset`.
When a frame is executing, `instr_ptr` points to the instruction currently being
executed. In a suspended frame, it points to the instruction that would execute
if the frame were to resume. After `frame.f_lineno` is set, `instr_ptr` points to
the next instruction to be executed. During a call to a python function,
`instr_ptr` points to the call instruction, because this is what we would expect
to see in an exception traceback.
The `return_offset` field determines where a `RETURN` should go in the caller,
relative to `instr_ptr`. It is only meaningful to the callee, so it needs to
be set in any instruction that implements a call (to a Python function),
including CALL, SEND and BINARY_SUBSCR_GETITEM, among others. If there is no
callee, then return_offset is meaningless. It is necessary to have a separate
field for the return offset because (1) if we apply this offset to `instr_ptr`
while executing the `RETURN`, this is too early and would lose us information
about the previous instruction which we could need for introspecting and
debugging. (2) `SEND` needs to pass two offsets to the generator: one for
`RETURN` and one for `YIELD`. It uses the `oparg` for one, and the
`return_offset` for the other.

View file

@ -821,7 +821,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
}
/* Finally set the new lasti and return OK. */
f->f_lineno = 0;
f->f_frame->prev_instr = _PyCode_CODE(code) + best_addr;
f->f_frame->instr_ptr = _PyCode_CODE(code) + best_addr;
return 0;
}
@ -1079,7 +1079,7 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
f->f_frame = (_PyInterpreterFrame *)f->_f_frame_data;
f->f_frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
// This frame needs to be "complete", so pretend that the first RESUME ran:
f->f_frame->prev_instr = _PyCode_CODE(code) + code->_co_firsttraceable;
f->f_frame->instr_ptr = _PyCode_CODE(code) + code->_co_firsttraceable + 1;
assert(!_PyFrame_IsIncomplete(f->f_frame));
Py_DECREF(func);
_PyObject_GC_TRACK(f);
@ -1093,7 +1093,7 @@ _PyFrame_OpAlreadyRan(_PyInterpreterFrame *frame, int opcode, int oparg)
assert(_PyOpcode_Deopt[opcode] == opcode);
int check_oparg = 0;
for (_Py_CODEUNIT *instruction = _PyCode_CODE(_PyFrame_GetCode(frame));
instruction < frame->prev_instr; instruction++)
instruction < frame->instr_ptr; instruction++)
{
int check_opcode = _PyOpcode_Deopt[instruction->op.code];
check_oparg |= instruction->op.arg;
@ -1135,7 +1135,7 @@ frame_init_get_vars(_PyInterpreterFrame *frame)
frame->localsplus[offset + i] = Py_NewRef(o);
}
// COPY_FREE_VARS doesn't have inline CACHEs, either:
frame->prev_instr = _PyCode_CODE(_PyFrame_GetCode(frame));
frame->instr_ptr = _PyCode_CODE(_PyFrame_GetCode(frame));
}

View file

@ -9,6 +9,7 @@
#include "pycore_genobject.h" // struct _Py_async_gen_state
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_opcode_metadata.h" // _PyOpcode_Caches
#include "pycore_pyerrors.h" // _PyErr_ClearExcState()
#include "pycore_pystate.h" // _PyThreadState_GET()
@ -362,8 +363,7 @@ _PyGen_yf(PyGenObject *gen)
assert(_PyCode_CODE(_PyGen_GetCode(gen))[0].op.code != SEND);
return NULL;
}
_Py_CODEUNIT next = frame->prev_instr[1];
if (!is_resume(&next) || next.op.arg < 2)
if (!is_resume(frame->instr_ptr) || frame->instr_ptr->op.arg < 2)
{
/* Not in a yield from */
return NULL;
@ -398,9 +398,12 @@ gen_close(PyGenObject *gen, PyObject *args)
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)gen->gi_iframe;
/* It is possible for the previous instruction to not be a
* YIELD_VALUE if the debugger has changed the lineno. */
if (err == 0 && is_yield(frame->prev_instr)) {
assert(is_resume(frame->prev_instr + 1));
int exception_handler_depth = frame->prev_instr[0].op.arg;
assert(_PyOpcode_Caches[YIELD_VALUE] == 0);
assert(_PyOpcode_Caches[INSTRUMENTED_YIELD_VALUE] == 0);
if (err == 0 && is_yield(frame->instr_ptr - 1)) {
_Py_CODEUNIT *yield_instr = frame->instr_ptr - 1;
assert(is_resume(frame->instr_ptr));
int exception_handler_depth = yield_instr->op.arg;
assert(exception_handler_depth > 0);
/* We can safely ignore the outermost try block
* as it automatically generated to handle