Skip to content

Commit d54b8d8

Browse files
gh-98831: Modernize the FOR_ITER family of instructions (#101626)
Co-authored-by: Irit Katriel <[email protected]>
1 parent a687ae9 commit d54b8d8

File tree

3 files changed

+104
-75
lines changed

3 files changed

+104
-75
lines changed

Python/bytecodes.c

+47-36
Original file line numberDiff line numberDiff line change
@@ -2066,27 +2066,35 @@ dummy_func(
20662066
PREDICT(LOAD_CONST);
20672067
}
20682068

2069-
// stack effect: ( -- __0)
2070-
inst(FOR_ITER) {
2069+
// Most members of this family are "secretly" super-instructions.
2070+
// When the loop is exhausted, they jump, and the jump target is
2071+
// always END_FOR, which pops two values off the stack.
2072+
// This is optimized by skipping that instruction and combining
2073+
// its effect (popping 'iter' instead of pushing 'next'.)
2074+
2075+
family(for_iter, INLINE_CACHE_ENTRIES_FOR_ITER) = {
2076+
FOR_ITER,
2077+
FOR_ITER_LIST,
2078+
FOR_ITER_TUPLE,
2079+
FOR_ITER_RANGE,
2080+
FOR_ITER_GEN,
2081+
};
2082+
2083+
inst(FOR_ITER, (unused/1, iter -- iter, next)) {
20712084
#if ENABLE_SPECIALIZATION
20722085
_PyForIterCache *cache = (_PyForIterCache *)next_instr;
20732086
if (ADAPTIVE_COUNTER_IS_ZERO(cache->counter)) {
20742087
assert(cframe.use_tracing == 0);
20752088
next_instr--;
2076-
_Py_Specialize_ForIter(TOP(), next_instr, oparg);
2089+
_Py_Specialize_ForIter(iter, next_instr, oparg);
20772090
DISPATCH_SAME_OPARG();
20782091
}
20792092
STAT_INC(FOR_ITER, deferred);
20802093
DECREMENT_ADAPTIVE_COUNTER(cache->counter);
20812094
#endif /* ENABLE_SPECIALIZATION */
2082-
/* before: [iter]; after: [iter, iter()] *or* [] */
2083-
PyObject *iter = TOP();
2084-
PyObject *next = (*Py_TYPE(iter)->tp_iternext)(iter);
2085-
if (next != NULL) {
2086-
PUSH(next);
2087-
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER);
2088-
}
2089-
else {
2095+
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
2096+
next = (*Py_TYPE(iter)->tp_iternext)(iter);
2097+
if (next == NULL) {
20902098
if (_PyErr_Occurred(tstate)) {
20912099
if (!_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) {
20922100
goto error;
@@ -2098,70 +2106,74 @@ dummy_func(
20982106
}
20992107
/* iterator ended normally */
21002108
assert(_Py_OPCODE(next_instr[INLINE_CACHE_ENTRIES_FOR_ITER + oparg]) == END_FOR);
2101-
STACK_SHRINK(1);
21022109
Py_DECREF(iter);
2103-
/* Skip END_FOR */
2110+
STACK_SHRINK(1);
2111+
/* Jump forward oparg, then skip following END_FOR instruction */
21042112
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1);
2113+
DISPATCH();
21052114
}
2115+
// Common case: no jump, leave it to the code generator
21062116
}
21072117

2108-
// stack effect: ( -- __0)
2109-
inst(FOR_ITER_LIST) {
2118+
inst(FOR_ITER_LIST, (unused/1, iter -- iter, next)) {
21102119
assert(cframe.use_tracing == 0);
2111-
_PyListIterObject *it = (_PyListIterObject *)TOP();
2112-
DEOPT_IF(Py_TYPE(it) != &PyListIter_Type, FOR_ITER);
2120+
DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER);
2121+
_PyListIterObject *it = (_PyListIterObject *)iter;
21132122
STAT_INC(FOR_ITER, hit);
21142123
PyListObject *seq = it->it_seq;
21152124
if (seq) {
21162125
if (it->it_index < PyList_GET_SIZE(seq)) {
2117-
PyObject *next = PyList_GET_ITEM(seq, it->it_index++);
2118-
PUSH(Py_NewRef(next));
2119-
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER);
2126+
next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++));
21202127
goto end_for_iter_list; // End of this instruction
21212128
}
21222129
it->it_seq = NULL;
21232130
Py_DECREF(seq);
21242131
}
2132+
Py_DECREF(iter);
21252133
STACK_SHRINK(1);
2126-
Py_DECREF(it);
2134+
/* Jump forward oparg, then skip following END_FOR instruction */
21272135
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1);
2136+
DISPATCH();
21282137
end_for_iter_list:
2138+
// Common case: no jump, leave it to the code generator
21292139
}
21302140

2131-
// stack effect: ( -- __0)
2132-
inst(FOR_ITER_TUPLE) {
2141+
inst(FOR_ITER_TUPLE, (unused/1, iter -- iter, next)) {
21332142
assert(cframe.use_tracing == 0);
2134-
_PyTupleIterObject *it = (_PyTupleIterObject *)TOP();
2143+
_PyTupleIterObject *it = (_PyTupleIterObject *)iter;
21352144
DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER);
21362145
STAT_INC(FOR_ITER, hit);
21372146
PyTupleObject *seq = it->it_seq;
21382147
if (seq) {
21392148
if (it->it_index < PyTuple_GET_SIZE(seq)) {
2140-
PyObject *next = PyTuple_GET_ITEM(seq, it->it_index++);
2141-
PUSH(Py_NewRef(next));
2142-
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER);
2149+
next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++));
21432150
goto end_for_iter_tuple; // End of this instruction
21442151
}
21452152
it->it_seq = NULL;
21462153
Py_DECREF(seq);
21472154
}
2155+
Py_DECREF(iter);
21482156
STACK_SHRINK(1);
2149-
Py_DECREF(it);
2157+
/* Jump forward oparg, then skip following END_FOR instruction */
21502158
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1);
2159+
DISPATCH();
21512160
end_for_iter_tuple:
2161+
// Common case: no jump, leave it to the code generator
21522162
}
21532163

2154-
// stack effect: ( -- __0)
2155-
inst(FOR_ITER_RANGE) {
2164+
// This is slightly different, when the loop isn't terminated we
2165+
// jump over the immediately following STORE_FAST instruction.
2166+
inst(FOR_ITER_RANGE, (unused/1, iter -- iter, unused)) {
21562167
assert(cframe.use_tracing == 0);
2157-
_PyRangeIterObject *r = (_PyRangeIterObject *)TOP();
2168+
_PyRangeIterObject *r = (_PyRangeIterObject *)iter;
21582169
DEOPT_IF(Py_TYPE(r) != &PyRangeIter_Type, FOR_ITER);
21592170
STAT_INC(FOR_ITER, hit);
21602171
_Py_CODEUNIT next = next_instr[INLINE_CACHE_ENTRIES_FOR_ITER];
21612172
assert(_PyOpcode_Deopt[_Py_OPCODE(next)] == STORE_FAST);
21622173
if (r->len <= 0) {
21632174
STACK_SHRINK(1);
21642175
Py_DECREF(r);
2176+
// Jump over END_FOR instruction.
21652177
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1);
21662178
}
21672179
else {
@@ -2174,11 +2186,13 @@ dummy_func(
21742186
// The STORE_FAST is already done.
21752187
JUMPBY(INLINE_CACHE_ENTRIES_FOR_ITER + 1);
21762188
}
2189+
DISPATCH();
21772190
}
21782191

2179-
inst(FOR_ITER_GEN) {
2192+
// This is *not* a super-instruction, unique in the family.
2193+
inst(FOR_ITER_GEN, (unused/1, iter -- iter, unused)) {
21802194
assert(cframe.use_tracing == 0);
2181-
PyGenObject *gen = (PyGenObject *)TOP();
2195+
PyGenObject *gen = (PyGenObject *)iter;
21822196
DEOPT_IF(Py_TYPE(gen) != &PyGen_Type, FOR_ITER);
21832197
DEOPT_IF(gen->gi_frame_state >= FRAME_EXECUTING, FOR_ITER);
21842198
STAT_INC(FOR_ITER, hit);
@@ -3168,9 +3182,6 @@ family(call, INLINE_CACHE_ENTRIES_CALL) = {
31683182
CALL_NO_KW_LIST_APPEND, CALL_NO_KW_METHOD_DESCRIPTOR_FAST, CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS,
31693183
CALL_NO_KW_METHOD_DESCRIPTOR_O, CALL_NO_KW_STR_1, CALL_NO_KW_TUPLE_1,
31703184
CALL_NO_KW_TYPE_1 };
3171-
family(for_iter, INLINE_CACHE_ENTRIES_FOR_ITER) = {
3172-
FOR_ITER, FOR_ITER_LIST,
3173-
FOR_ITER_RANGE };
31743185
family(store_fast) = { STORE_FAST, STORE_FAST__LOAD_FAST, STORE_FAST__STORE_FAST };
31753186
family(unpack_sequence, INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE) = {
31763187
UNPACK_SEQUENCE, UNPACK_SEQUENCE_LIST,

Python/generated_cases.c.h

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

Python/opcode_metadata.h

+15-15
Original file line numberDiff line numberDiff line change
@@ -261,15 +261,15 @@ _PyOpcode_num_popped(int opcode, int oparg, bool jump) {
261261
case GET_YIELD_FROM_ITER:
262262
return 1;
263263
case FOR_ITER:
264-
return -1;
264+
return 1;
265265
case FOR_ITER_LIST:
266-
return -1;
266+
return 1;
267267
case FOR_ITER_TUPLE:
268-
return -1;
268+
return 1;
269269
case FOR_ITER_RANGE:
270-
return -1;
270+
return 1;
271271
case FOR_ITER_GEN:
272-
return -1;
272+
return 1;
273273
case BEFORE_ASYNC_WITH:
274274
return 1;
275275
case BEFORE_WITH:
@@ -607,15 +607,15 @@ _PyOpcode_num_pushed(int opcode, int oparg, bool jump) {
607607
case GET_YIELD_FROM_ITER:
608608
return 1;
609609
case FOR_ITER:
610-
return -1;
610+
return 2;
611611
case FOR_ITER_LIST:
612-
return -1;
612+
return 2;
613613
case FOR_ITER_TUPLE:
614-
return -1;
614+
return 2;
615615
case FOR_ITER_RANGE:
616-
return -1;
616+
return 2;
617617
case FOR_ITER_GEN:
618-
return -1;
618+
return 2;
619619
case BEFORE_ASYNC_WITH:
620620
return 2;
621621
case BEFORE_WITH:
@@ -829,11 +829,11 @@ struct opcode_metadata {
829829
[MATCH_KEYS] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IX },
830830
[GET_ITER] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IX },
831831
[GET_YIELD_FROM_ITER] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IX },
832-
[FOR_ITER] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
833-
[FOR_ITER_LIST] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
834-
[FOR_ITER_TUPLE] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
835-
[FOR_ITER_RANGE] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
836-
[FOR_ITER_GEN] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IB },
832+
[FOR_ITER] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IBC },
833+
[FOR_ITER_LIST] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IBC },
834+
[FOR_ITER_TUPLE] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IBC },
835+
[FOR_ITER_RANGE] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IBC },
836+
[FOR_ITER_GEN] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IBC },
837837
[BEFORE_ASYNC_WITH] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IX },
838838
[BEFORE_WITH] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IX },
839839
[WITH_EXCEPT_START] = { DIR_NONE, DIR_NONE, DIR_NONE, true, INSTR_FMT_IX },

0 commit comments

Comments
 (0)