Skip to content

Commit 1995955

Browse files
authored
gh-106529: Make FOR_ITER a viable uop (#112134)
This uses the new mechanism whereby certain uops are replaced by others during translation, using the `_PyUop_Replacements` table. We further special-case the `_FOR_ITER_TIER_TWO` uop to update the deoptimization target to point just past the corresponding `END_FOR` opcode. Two tiny code cleanups are also part of this PR.
1 parent d59feb5 commit 1995955

File tree

8 files changed

+138
-43
lines changed

8 files changed

+138
-43
lines changed

Include/internal/pycore_opcode_metadata.h

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

Lib/test/test_capi/test_misc.py

+30
Original file line numberDiff line numberDiff line change
@@ -2808,6 +2808,36 @@ def testfunc(n):
28082808
uops = {opname for opname, _, _ in ex}
28092809
self.assertIn("_GUARD_IS_FALSE_POP", uops)
28102810

2811+
def test_for_iter_tier_two(self):
2812+
class MyIter:
2813+
def __init__(self, n):
2814+
self.n = n
2815+
def __iter__(self):
2816+
return self
2817+
def __next__(self):
2818+
self.n -= 1
2819+
if self.n < 0:
2820+
raise StopIteration
2821+
return self.n
2822+
2823+
def testfunc(n, m):
2824+
x = 0
2825+
for i in range(m):
2826+
for j in MyIter(n):
2827+
x += 1000*i + j
2828+
return x
2829+
2830+
opt = _testinternalcapi.get_uop_optimizer()
2831+
with temporary_optimizer(opt):
2832+
x = testfunc(10, 10)
2833+
2834+
self.assertEqual(x, sum(range(10)) * 10010)
2835+
2836+
ex = get_first_executor(testfunc)
2837+
self.assertIsNotNone(ex)
2838+
uops = {opname for opname, _, _ in ex}
2839+
self.assertIn("_FOR_ITER_TIER_TWO", uops)
2840+
28112841

28122842
if __name__ == "__main__":
28132843
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Enable translating unspecialized ``FOR_ITER`` to Tier 2.

Python/abstract_interp_cases.c.h

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

Python/bytecodes.c

+21-2
Original file line numberDiff line numberDiff line change
@@ -2369,7 +2369,7 @@ dummy_func(
23692369
goto enter_tier_one;
23702370
}
23712371

2372-
replaced op(_POP_JUMP_IF_FALSE, (unused/1, cond -- )) {
2372+
replaced op(_POP_JUMP_IF_FALSE, (unused/1, cond -- )) {
23732373
assert(PyBool_Check(cond));
23742374
int flag = Py_IsFalse(cond);
23752375
#if ENABLE_SPECIALIZATION
@@ -2513,7 +2513,7 @@ dummy_func(
25132513
#endif /* ENABLE_SPECIALIZATION */
25142514
}
25152515

2516-
op(_FOR_ITER, (iter -- iter, next)) {
2516+
replaced op(_FOR_ITER, (iter -- iter, next)) {
25172517
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
25182518
next = (*Py_TYPE(iter)->tp_iternext)(iter);
25192519
if (next == NULL) {
@@ -2536,6 +2536,25 @@ dummy_func(
25362536
// Common case: no jump, leave it to the code generator
25372537
}
25382538

2539+
op(_FOR_ITER_TIER_TWO, (iter -- iter, next)) {
2540+
/* before: [iter]; after: [iter, iter()] *or* [] (and jump over END_FOR.) */
2541+
next = (*Py_TYPE(iter)->tp_iternext)(iter);
2542+
if (next == NULL) {
2543+
if (_PyErr_Occurred(tstate)) {
2544+
if (!_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) {
2545+
GOTO_ERROR(error);
2546+
}
2547+
_PyErr_Clear(tstate);
2548+
}
2549+
/* iterator ended normally */
2550+
Py_DECREF(iter);
2551+
STACK_SHRINK(1);
2552+
/* The translator sets the deopt target just past END_FOR */
2553+
DEOPT_IF(true);
2554+
}
2555+
// Common case: no jump, leave it to the code generator
2556+
}
2557+
25392558
macro(FOR_ITER) = _SPECIALIZE_FOR_ITER + _FOR_ITER;
25402559

25412560
inst(INSTRUMENTED_FOR_ITER, (unused/1 -- )) {

Python/ceval.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
10741074
UOP_STAT_INC(opcode, miss);
10751075
frame->return_offset = 0; // Dispatch to frame->instr_ptr
10761076
_PyFrame_SetStackPointer(frame, stack_pointer);
1077-
frame->instr_ptr = next_uop[-1].target + _PyCode_CODE((PyCodeObject *)frame->f_executable);
1077+
frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame));
10781078
Py_DECREF(current_executor);
10791079
// Fall through
10801080
// Jump here from ENTER_EXECUTOR
@@ -1085,7 +1085,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
10851085
// Jump here from _EXIT_TRACE
10861086
exit_trace:
10871087
_PyFrame_SetStackPointer(frame, stack_pointer);
1088-
frame->instr_ptr = next_uop[-1].target + _PyCode_CODE((PyCodeObject *)frame->f_executable);
1088+
frame->instr_ptr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame));
10891089
Py_DECREF(current_executor);
10901090
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist);
10911091
goto enter_tier_one;

Python/executor_cases.c.h

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

Python/optimizer.c

+6
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ _PyUop_Replacements[OPCODE_METADATA_SIZE] = {
392392
[_ITER_JUMP_RANGE] = _GUARD_NOT_EXHAUSTED_RANGE,
393393
[_ITER_JUMP_LIST] = _GUARD_NOT_EXHAUSTED_LIST,
394394
[_ITER_JUMP_TUPLE] = _GUARD_NOT_EXHAUSTED_TUPLE,
395+
[_FOR_ITER] = _FOR_ITER_TIER_TWO,
395396
};
396397

397398
static const uint16_t
@@ -620,6 +621,11 @@ translate_bytecode_to_trace(
620621
}
621622
if (_PyUop_Replacements[uop]) {
622623
uop = _PyUop_Replacements[uop];
624+
if (uop == _FOR_ITER_TIER_TWO) {
625+
target += 1 + INLINE_CACHE_ENTRIES_FOR_ITER + oparg + 1;
626+
assert(_PyCode_CODE(code)[target-1].op.code == END_FOR ||
627+
_PyCode_CODE(code)[target-1].op.code == INSTRUMENTED_END_FOR);
628+
}
623629
}
624630
break;
625631
case OPARG_CACHE_1:

0 commit comments

Comments
 (0)