Skip to content

Commit 025995f

Browse files
authored
gh-106529: Split FOR_ITER_{LIST,TUPLE} into uops (#106696)
Also rename `_ITER_EXHAUSTED_XXX` to `_IS_ITER_EXHAUSTED_XXX` to make it clear this is a test.
1 parent 128a6c1 commit 025995f

File tree

6 files changed

+378
-111
lines changed

6 files changed

+378
-111
lines changed

Include/internal/pycore_opcode_metadata.h

+19-7
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

+45-2
Original file line numberDiff line numberDiff line change
@@ -2590,7 +2590,6 @@ def testfunc(n):
25902590
for i in range(n):
25912591
total += i
25922592
return total
2593-
# import dis; dis.dis(testfunc)
25942593

25952594
opt = _testinternalcapi.get_uop_optimizer()
25962595
with temporary_optimizer(opt):
@@ -2602,7 +2601,51 @@ def testfunc(n):
26022601
# for i, (opname, oparg) in enumerate(ex):
26032602
# print(f"{i:4d}: {opname:<20s} {oparg:3d}")
26042603
uops = {opname for opname, _ in ex}
2605-
self.assertIn("_ITER_EXHAUSTED_RANGE", uops)
2604+
self.assertIn("_IS_ITER_EXHAUSTED_RANGE", uops)
2605+
# Verification that the jump goes past END_FOR
2606+
# is done by manual inspection of the output
2607+
2608+
def test_for_iter_list(self):
2609+
def testfunc(a):
2610+
total = 0
2611+
for i in a:
2612+
total += i
2613+
return total
2614+
2615+
opt = _testinternalcapi.get_uop_optimizer()
2616+
with temporary_optimizer(opt):
2617+
a = list(range(10))
2618+
total = testfunc(a)
2619+
self.assertEqual(total, 45)
2620+
2621+
ex = get_first_executor(testfunc)
2622+
self.assertIsNotNone(ex)
2623+
# for i, (opname, oparg) in enumerate(ex):
2624+
# print(f"{i:4d}: {opname:<20s} {oparg:3d}")
2625+
uops = {opname for opname, _ in ex}
2626+
self.assertIn("_IS_ITER_EXHAUSTED_LIST", uops)
2627+
# Verification that the jump goes past END_FOR
2628+
# is done by manual inspection of the output
2629+
2630+
def test_for_iter_tuple(self):
2631+
def testfunc(a):
2632+
total = 0
2633+
for i in a:
2634+
total += i
2635+
return total
2636+
2637+
opt = _testinternalcapi.get_uop_optimizer()
2638+
with temporary_optimizer(opt):
2639+
a = tuple(range(10))
2640+
total = testfunc(a)
2641+
self.assertEqual(total, 45)
2642+
2643+
ex = get_first_executor(testfunc)
2644+
self.assertIsNotNone(ex)
2645+
# for i, (opname, oparg) in enumerate(ex):
2646+
# print(f"{i:4d}: {opname:<20s} {oparg:3d}")
2647+
uops = {opname for opname, _ in ex}
2648+
self.assertIn("_IS_ITER_EXHAUSTED_TUPLE", uops)
26062649
# Verification that the jump goes past END_FOR
26072650
# is done by manual inspection of the output
26082651

Python/bytecodes.c

+95-34
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "pycore_object.h" // _PyObject_GC_TRACK()
1818
#include "pycore_moduleobject.h" // PyModuleObject
1919
#include "pycore_opcode.h" // EXTRA_CASES
20+
#include "pycore_opcode_metadata.h" // uop names
2021
#include "pycore_opcode_utils.h" // MAKE_FUNCTION_*
2122
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
2223
#include "pycore_pystate.h" // _PyInterpreterState_GET()
@@ -55,13 +56,14 @@
5556
static PyObject *value, *value1, *value2, *left, *right, *res, *sum, *prod, *sub;
5657
static PyObject *container, *start, *stop, *v, *lhs, *rhs, *res2;
5758
static PyObject *list, *tuple, *dict, *owner, *set, *str, *tup, *map, *keys;
58-
static PyObject *exit_func, *lasti, *val, *retval, *obj, *iter;
59+
static PyObject *exit_func, *lasti, *val, *retval, *obj, *iter, *exhausted;
5960
static PyObject *aiter, *awaitable, *iterable, *w, *exc_value, *bc, *locals;
6061
static PyObject *orig, *excs, *update, *b, *fromlist, *level, *from;
6162
static PyObject **pieces, **values;
6263
static size_t jump;
6364
// Dummy variables for cache effects
6465
static uint16_t invert, counter, index, hint;
66+
#define unused 0 // Used in a macro def, can't be static
6567
static uint32_t type_version;
6668

6769
static PyObject *
@@ -2418,52 +2420,108 @@ dummy_func(
24182420
INSTRUMENTED_JUMP(here, target, PY_MONITORING_EVENT_BRANCH);
24192421
}
24202422

2421-
inst(FOR_ITER_LIST, (unused/1, iter -- iter, next)) {
2423+
op(_ITER_CHECK_LIST, (iter -- iter)) {
24222424
DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER);
2425+
}
2426+
2427+
op(_ITER_JUMP_LIST, (iter -- iter)) {
24232428
_PyListIterObject *it = (_PyListIterObject *)iter;
2429+
assert(Py_TYPE(iter) == &PyListIter_Type);
24242430
STAT_INC(FOR_ITER, hit);
24252431
PyListObject *seq = it->it_seq;
2426-
if (seq) {
2427-
if (it->it_index < PyList_GET_SIZE(seq)) {
2428-
next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++));
2429-
goto end_for_iter_list; // End of this instruction
2432+
if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) {
2433+
if (seq != NULL) {
2434+
it->it_seq = NULL;
2435+
Py_DECREF(seq);
24302436
}
2431-
it->it_seq = NULL;
2432-
Py_DECREF(seq);
2437+
Py_DECREF(iter);
2438+
STACK_SHRINK(1);
2439+
SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER);
2440+
/* Jump forward oparg, then skip following END_FOR instruction */
2441+
JUMPBY(oparg + 1);
2442+
DISPATCH();
24332443
}
2434-
Py_DECREF(iter);
2435-
STACK_SHRINK(1);
2436-
SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER);
2437-
/* Jump forward oparg, then skip following END_FOR instruction */
2438-
JUMPBY(oparg + 1);
2439-
DISPATCH();
2440-
end_for_iter_list:
2441-
// Common case: no jump, leave it to the code generator
24422444
}
24432445

2444-
inst(FOR_ITER_TUPLE, (unused/1, iter -- iter, next)) {
2446+
// Only used by Tier 2
2447+
op(_IS_ITER_EXHAUSTED_LIST, (iter -- iter, exhausted)) {
2448+
_PyListIterObject *it = (_PyListIterObject *)iter;
2449+
assert(Py_TYPE(iter) == &PyListIter_Type);
2450+
PyListObject *seq = it->it_seq;
2451+
if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) {
2452+
exhausted = Py_True;
2453+
}
2454+
else {
2455+
exhausted = Py_False;
2456+
}
2457+
}
2458+
2459+
op(_ITER_NEXT_LIST, (iter -- iter, next)) {
2460+
_PyListIterObject *it = (_PyListIterObject *)iter;
2461+
assert(Py_TYPE(iter) == &PyListIter_Type);
2462+
PyListObject *seq = it->it_seq;
2463+
assert(seq);
2464+
assert(it->it_index < PyList_GET_SIZE(seq));
2465+
next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++));
2466+
}
2467+
2468+
macro(FOR_ITER_LIST) =
2469+
unused/1 + // Skip over the counter
2470+
_ITER_CHECK_LIST +
2471+
_ITER_JUMP_LIST +
2472+
_ITER_NEXT_LIST;
2473+
2474+
op(_ITER_CHECK_TUPLE, (iter -- iter)) {
2475+
DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER);
2476+
}
2477+
2478+
op(_ITER_JUMP_TUPLE, (iter -- iter)) {
24452479
_PyTupleIterObject *it = (_PyTupleIterObject *)iter;
2446-
DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER);
2480+
assert(Py_TYPE(iter) == &PyTupleIter_Type);
24472481
STAT_INC(FOR_ITER, hit);
24482482
PyTupleObject *seq = it->it_seq;
2449-
if (seq) {
2450-
if (it->it_index < PyTuple_GET_SIZE(seq)) {
2451-
next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++));
2452-
goto end_for_iter_tuple; // End of this instruction
2483+
if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) {
2484+
if (seq != NULL) {
2485+
it->it_seq = NULL;
2486+
Py_DECREF(seq);
24532487
}
2454-
it->it_seq = NULL;
2455-
Py_DECREF(seq);
2488+
Py_DECREF(iter);
2489+
STACK_SHRINK(1);
2490+
SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER);
2491+
/* Jump forward oparg, then skip following END_FOR instruction */
2492+
JUMPBY(oparg + 1);
2493+
DISPATCH();
24562494
}
2457-
Py_DECREF(iter);
2458-
STACK_SHRINK(1);
2459-
SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER);
2460-
/* Jump forward oparg, then skip following END_FOR instruction */
2461-
JUMPBY(oparg + 1);
2462-
DISPATCH();
2463-
end_for_iter_tuple:
2464-
// Common case: no jump, leave it to the code generator
24652495
}
24662496

2497+
// Only used by Tier 2
2498+
op(_IS_ITER_EXHAUSTED_TUPLE, (iter -- iter, exhausted)) {
2499+
_PyTupleIterObject *it = (_PyTupleIterObject *)iter;
2500+
assert(Py_TYPE(iter) == &PyTupleIter_Type);
2501+
PyTupleObject *seq = it->it_seq;
2502+
if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) {
2503+
exhausted = Py_True;
2504+
}
2505+
else {
2506+
exhausted = Py_False;
2507+
}
2508+
}
2509+
2510+
op(_ITER_NEXT_TUPLE, (iter -- iter, next)) {
2511+
_PyTupleIterObject *it = (_PyTupleIterObject *)iter;
2512+
assert(Py_TYPE(iter) == &PyTupleIter_Type);
2513+
PyTupleObject *seq = it->it_seq;
2514+
assert(seq);
2515+
assert(it->it_index < PyTuple_GET_SIZE(seq));
2516+
next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++));
2517+
}
2518+
2519+
macro(FOR_ITER_TUPLE) =
2520+
unused/1 + // Skip over the counter
2521+
_ITER_CHECK_TUPLE +
2522+
_ITER_JUMP_TUPLE +
2523+
_ITER_NEXT_TUPLE;
2524+
24672525
op(_ITER_CHECK_RANGE, (iter -- iter)) {
24682526
_PyRangeIterObject *r = (_PyRangeIterObject *)iter;
24692527
DEOPT_IF(Py_TYPE(r) != &PyRangeIter_Type, FOR_ITER);
@@ -2484,7 +2542,7 @@ dummy_func(
24842542
}
24852543

24862544
// Only used by Tier 2
2487-
op(_ITER_EXHAUSTED_RANGE, (iter -- iter, exhausted)) {
2545+
op(_IS_ITER_EXHAUSTED_RANGE, (iter -- iter, exhausted)) {
24882546
_PyRangeIterObject *r = (_PyRangeIterObject *)iter;
24892547
assert(Py_TYPE(r) == &PyRangeIter_Type);
24902548
exhausted = r->len <= 0 ? Py_True : Py_False;
@@ -2502,7 +2560,10 @@ dummy_func(
25022560
}
25032561

25042562
macro(FOR_ITER_RANGE) =
2505-
unused/1 + _ITER_CHECK_RANGE + _ITER_JUMP_RANGE + _ITER_NEXT_RANGE;
2563+
unused/1 + // Skip over the counter
2564+
_ITER_CHECK_RANGE +
2565+
_ITER_JUMP_RANGE +
2566+
_ITER_NEXT_RANGE;
25062567

25072568
inst(FOR_ITER_GEN, (unused/1, iter -- iter, unused)) {
25082569
DEOPT_IF(tstate->interp->eval_frame, FOR_ITER);

Python/executor_cases.c.h

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

0 commit comments

Comments
 (0)