Skip to content

Commit 67a91f7

Browse files
authoredOct 26, 2023
pythongh-109094: replace frame->prev_instr by frame->instr_ptr (python#109095)
1 parent 573eff3 commit 67a91f7

23 files changed

+249
-164
lines changed
 

‎Include/internal/pycore_frame.h

+6-16
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,16 @@ typedef struct _PyInterpreterFrame {
5858
PyObject *f_builtins; /* Borrowed reference. Only valid if not on C stack */
5959
PyObject *f_locals; /* Strong reference, may be NULL. Only valid if not on C stack */
6060
PyFrameObject *frame_obj; /* Strong reference, may be NULL. Only valid if not on C stack */
61-
// NOTE: This is not necessarily the last instruction started in the given
62-
// frame. Rather, it is the code unit *prior to* the *next* instruction. For
63-
// example, it may be an inline CACHE entry, an instruction we just jumped
64-
// over, or (in the case of a newly-created frame) a totally invalid value:
65-
_Py_CODEUNIT *prev_instr;
61+
_Py_CODEUNIT *instr_ptr; /* Instruction currently executing (or about to begin) */
6662
int stacktop; /* Offset of TOS from localsplus */
67-
/* The return_offset determines where a `RETURN` should go in the caller,
68-
* relative to `prev_instr`.
69-
* It is only meaningful to the callee,
70-
* so it needs to be set in any CALL (to a Python function)
71-
* or SEND (to a coroutine or generator).
72-
* If there is no callee, then it is meaningless. */
73-
uint16_t return_offset;
63+
uint16_t return_offset; /* Only relevant during a function call */
7464
char owner;
7565
/* Locals and stack */
7666
PyObject *localsplus[1];
7767
} _PyInterpreterFrame;
7868

7969
#define _PyInterpreterFrame_LASTI(IF) \
80-
((int)((IF)->prev_instr - _PyCode_CODE(_PyFrame_GetCode(IF))))
70+
((int)((IF)->instr_ptr - _PyCode_CODE(_PyFrame_GetCode(IF))))
8171

8272
static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
8373
assert(PyCode_Check(f->f_executable));
@@ -134,7 +124,7 @@ _PyFrame_Initialize(
134124
frame->f_locals = locals;
135125
frame->stacktop = code->co_nlocalsplus;
136126
frame->frame_obj = NULL;
137-
frame->prev_instr = _PyCode_CODE(code) - 1;
127+
frame->instr_ptr = _PyCode_CODE(code);
138128
frame->return_offset = 0;
139129
frame->owner = FRAME_OWNED_BY_THREAD;
140130

@@ -185,7 +175,7 @@ _PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
185175
return true;
186176
}
187177
return frame->owner != FRAME_OWNED_BY_GENERATOR &&
188-
frame->prev_instr < _PyCode_CODE(_PyFrame_GetCode(frame)) + _PyFrame_GetCode(frame)->_co_firsttraceable;
178+
frame->instr_ptr < _PyCode_CODE(_PyFrame_GetCode(frame)) + _PyFrame_GetCode(frame)->_co_firsttraceable;
189179
}
190180

191181
static inline _PyInterpreterFrame *
@@ -297,7 +287,7 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
297287
frame->f_locals = NULL;
298288
frame->stacktop = code->co_nlocalsplus + stackdepth;
299289
frame->frame_obj = NULL;
300-
frame->prev_instr = _PyCode_CODE(code);
290+
frame->instr_ptr = _PyCode_CODE(code);
301291
frame->owner = FRAME_OWNED_BY_THREAD;
302292
frame->return_offset = 0;
303293
return frame;

‎Include/internal/pycore_opcode_metadata.h

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_runtime.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ typedef struct _Py_DebugOffsets {
8787
struct _interpreter_frame {
8888
off_t previous;
8989
off_t executable;
90-
off_t prev_instr;
90+
off_t instr_ptr;
9191
off_t localsplus;
9292
off_t owner;
9393
} interpreter_frame;

‎Include/internal/pycore_runtime_init.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extern PyTypeObject _PyExc_MemoryError;
5858
.interpreter_frame = { \
5959
.previous = offsetof(_PyInterpreterFrame, previous), \
6060
.executable = offsetof(_PyInterpreterFrame, f_executable), \
61-
.prev_instr = offsetof(_PyInterpreterFrame, prev_instr), \
61+
.instr_ptr = offsetof(_PyInterpreterFrame, instr_ptr), \
6262
.localsplus = offsetof(_PyInterpreterFrame, localsplus), \
6363
.owner = offsetof(_PyInterpreterFrame, owner), \
6464
}, \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Replace ``prev_instr`` on the interpreter frame by ``instr_ptr`` which
2+
points to the beginning of the instruction that is currently executing (or
3+
will execute once the frame resumes).

‎Objects/frame_layout.md

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

‎Objects/frameobject.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
821821
}
822822
/* Finally set the new lasti and return OK. */
823823
f->f_lineno = 0;
824-
f->f_frame->prev_instr = _PyCode_CODE(code) + best_addr;
824+
f->f_frame->instr_ptr = _PyCode_CODE(code) + best_addr;
825825
return 0;
826826
}
827827

@@ -1079,7 +1079,7 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
10791079
f->f_frame = (_PyInterpreterFrame *)f->_f_frame_data;
10801080
f->f_frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
10811081
// This frame needs to be "complete", so pretend that the first RESUME ran:
1082-
f->f_frame->prev_instr = _PyCode_CODE(code) + code->_co_firsttraceable;
1082+
f->f_frame->instr_ptr = _PyCode_CODE(code) + code->_co_firsttraceable + 1;
10831083
assert(!_PyFrame_IsIncomplete(f->f_frame));
10841084
Py_DECREF(func);
10851085
_PyObject_GC_TRACK(f);
@@ -1093,7 +1093,7 @@ _PyFrame_OpAlreadyRan(_PyInterpreterFrame *frame, int opcode, int oparg)
10931093
assert(_PyOpcode_Deopt[opcode] == opcode);
10941094
int check_oparg = 0;
10951095
for (_Py_CODEUNIT *instruction = _PyCode_CODE(_PyFrame_GetCode(frame));
1096-
instruction < frame->prev_instr; instruction++)
1096+
instruction < frame->instr_ptr; instruction++)
10971097
{
10981098
int check_opcode = _PyOpcode_Deopt[instruction->op.code];
10991099
check_oparg |= instruction->op.arg;
@@ -1135,7 +1135,7 @@ frame_init_get_vars(_PyInterpreterFrame *frame)
11351135
frame->localsplus[offset + i] = Py_NewRef(o);
11361136
}
11371137
// COPY_FREE_VARS doesn't have inline CACHEs, either:
1138-
frame->prev_instr = _PyCode_CODE(_PyFrame_GetCode(frame));
1138+
frame->instr_ptr = _PyCode_CODE(_PyFrame_GetCode(frame));
11391139
}
11401140

11411141

‎Objects/genobject.c

+8-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "pycore_genobject.h" // struct _Py_async_gen_state
1010
#include "pycore_modsupport.h" // _PyArg_CheckPositional()
1111
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
12+
#include "pycore_opcode_metadata.h" // _PyOpcode_Caches
1213
#include "pycore_pyerrors.h" // _PyErr_ClearExcState()
1314
#include "pycore_pystate.h" // _PyThreadState_GET()
1415

@@ -362,8 +363,7 @@ _PyGen_yf(PyGenObject *gen)
362363
assert(_PyCode_CODE(_PyGen_GetCode(gen))[0].op.code != SEND);
363364
return NULL;
364365
}
365-
_Py_CODEUNIT next = frame->prev_instr[1];
366-
if (!is_resume(&next) || next.op.arg < 2)
366+
if (!is_resume(frame->instr_ptr) || frame->instr_ptr->op.arg < 2)
367367
{
368368
/* Not in a yield from */
369369
return NULL;
@@ -398,9 +398,12 @@ gen_close(PyGenObject *gen, PyObject *args)
398398
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)gen->gi_iframe;
399399
/* It is possible for the previous instruction to not be a
400400
* YIELD_VALUE if the debugger has changed the lineno. */
401-
if (err == 0 && is_yield(frame->prev_instr)) {
402-
assert(is_resume(frame->prev_instr + 1));
403-
int exception_handler_depth = frame->prev_instr[0].op.arg;
401+
assert(_PyOpcode_Caches[YIELD_VALUE] == 0);
402+
assert(_PyOpcode_Caches[INSTRUMENTED_YIELD_VALUE] == 0);
403+
if (err == 0 && is_yield(frame->instr_ptr - 1)) {
404+
_Py_CODEUNIT *yield_instr = frame->instr_ptr - 1;
405+
assert(is_resume(frame->instr_ptr));
406+
int exception_handler_depth = yield_instr->op.arg;
404407
assert(exception_handler_depth > 0);
405408
/* We can safely ignore the outermost try block
406409
* as it automatically generated to handle

‎Python/abstract_interp_cases.c.h

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.