Skip to content

Commit c15f572

Browse files
gvanrossumkgdiem
authored andcommitted
pythongh-106529: Split FOR_ITER_{LIST,TUPLE} into uops (python#106696)
Also rename `_ITER_EXHAUSTED_XXX` to `_IS_ITER_EXHAUSTED_XXX` to make it clear this is a test.
1 parent 0941a94 commit c15f572

File tree

6 files changed

+378
-112
lines changed

6 files changed

+378
-112
lines changed

Include/internal/pycore_opcode_metadata.h

Lines changed: 19 additions & 7 deletions
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

Lines changed: 45 additions & 2 deletions
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

Lines changed: 95 additions & 34 deletions
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 *
@@ -2406,52 +2408,108 @@ dummy_func(
24062408
INSTRUMENTED_JUMP(here, target, PY_MONITORING_EVENT_BRANCH);
24072409
}
24082410

2409-
inst(FOR_ITER_LIST, (unused/1, iter -- iter, next)) {
2411+
op(_ITER_CHECK_LIST, (iter -- iter)) {
24102412
DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER);
2413+
}
2414+
2415+
op(_ITER_JUMP_LIST, (iter -- iter)) {
24112416
_PyListIterObject *it = (_PyListIterObject *)iter;
2417+
assert(Py_TYPE(iter) == &PyListIter_Type);
24122418
STAT_INC(FOR_ITER, hit);
24132419
PyListObject *seq = it->it_seq;
2414-
if (seq) {
2415-
if (it->it_index < PyList_GET_SIZE(seq)) {
2416-
next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++));
2417-
goto end_for_iter_list; // End of this instruction
2420+
if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) {
2421+
if (seq != NULL) {
2422+
it->it_seq = NULL;
2423+
Py_DECREF(seq);
24182424
}
2419-
it->it_seq = NULL;
2420-
Py_DECREF(seq);
2425+
Py_DECREF(iter);
2426+
STACK_SHRINK(1);
2427+
SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER);
2428+
/* Jump forward oparg, then skip following END_FOR instruction */
2429+
JUMPBY(oparg + 1);
2430+
DISPATCH();
24212431
}
2422-
Py_DECREF(iter);
2423-
STACK_SHRINK(1);
2424-
SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER);
2425-
/* Jump forward oparg, then skip following END_FOR instruction */
2426-
JUMPBY(oparg + 1);
2427-
DISPATCH();
2428-
end_for_iter_list:
2429-
// Common case: no jump, leave it to the code generator
24302432
}
24312433

2432-
inst(FOR_ITER_TUPLE, (unused/1, iter -- iter, next)) {
2434+
// Only used by Tier 2
2435+
op(_IS_ITER_EXHAUSTED_LIST, (iter -- iter, exhausted)) {
2436+
_PyListIterObject *it = (_PyListIterObject *)iter;
2437+
assert(Py_TYPE(iter) == &PyListIter_Type);
2438+
PyListObject *seq = it->it_seq;
2439+
if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) {
2440+
exhausted = Py_True;
2441+
}
2442+
else {
2443+
exhausted = Py_False;
2444+
}
2445+
}
2446+
2447+
op(_ITER_NEXT_LIST, (iter -- iter, next)) {
2448+
_PyListIterObject *it = (_PyListIterObject *)iter;
2449+
assert(Py_TYPE(iter) == &PyListIter_Type);
2450+
PyListObject *seq = it->it_seq;
2451+
assert(seq);
2452+
assert(it->it_index < PyList_GET_SIZE(seq));
2453+
next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++));
2454+
}
2455+
2456+
macro(FOR_ITER_LIST) =
2457+
unused/1 + // Skip over the counter
2458+
_ITER_CHECK_LIST +
2459+
_ITER_JUMP_LIST +
2460+
_ITER_NEXT_LIST;
2461+
2462+
op(_ITER_CHECK_TUPLE, (iter -- iter)) {
2463+
DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER);
2464+
}
2465+
2466+
op(_ITER_JUMP_TUPLE, (iter -- iter)) {
24332467
_PyTupleIterObject *it = (_PyTupleIterObject *)iter;
2434-
DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER);
2468+
assert(Py_TYPE(iter) == &PyTupleIter_Type);
24352469
STAT_INC(FOR_ITER, hit);
24362470
PyTupleObject *seq = it->it_seq;
2437-
if (seq) {
2438-
if (it->it_index < PyTuple_GET_SIZE(seq)) {
2439-
next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++));
2440-
goto end_for_iter_tuple; // End of this instruction
2471+
if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) {
2472+
if (seq != NULL) {
2473+
it->it_seq = NULL;
2474+
Py_DECREF(seq);
24412475
}
2442-
it->it_seq = NULL;
2443-
Py_DECREF(seq);
2476+
Py_DECREF(iter);
2477+
STACK_SHRINK(1);
2478+
SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER);
2479+
/* Jump forward oparg, then skip following END_FOR instruction */
2480+
JUMPBY(oparg + 1);
2481+
DISPATCH();
24442482
}
2445-
Py_DECREF(iter);
2446-
STACK_SHRINK(1);
2447-
SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER);
2448-
/* Jump forward oparg, then skip following END_FOR instruction */
2449-
JUMPBY(oparg + 1);
2450-
DISPATCH();
2451-
end_for_iter_tuple:
2452-
// Common case: no jump, leave it to the code generator
24532483
}
24542484

2485+
// Only used by Tier 2
2486+
op(_IS_ITER_EXHAUSTED_TUPLE, (iter -- iter, exhausted)) {
2487+
_PyTupleIterObject *it = (_PyTupleIterObject *)iter;
2488+
assert(Py_TYPE(iter) == &PyTupleIter_Type);
2489+
PyTupleObject *seq = it->it_seq;
2490+
if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) {
2491+
exhausted = Py_True;
2492+
}
2493+
else {
2494+
exhausted = Py_False;
2495+
}
2496+
}
2497+
2498+
op(_ITER_NEXT_TUPLE, (iter -- iter, next)) {
2499+
_PyTupleIterObject *it = (_PyTupleIterObject *)iter;
2500+
assert(Py_TYPE(iter) == &PyTupleIter_Type);
2501+
PyTupleObject *seq = it->it_seq;
2502+
assert(seq);
2503+
assert(it->it_index < PyTuple_GET_SIZE(seq));
2504+
next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++));
2505+
}
2506+
2507+
macro(FOR_ITER_TUPLE) =
2508+
unused/1 + // Skip over the counter
2509+
_ITER_CHECK_TUPLE +
2510+
_ITER_JUMP_TUPLE +
2511+
_ITER_NEXT_TUPLE;
2512+
24552513
op(_ITER_CHECK_RANGE, (iter -- iter)) {
24562514
_PyRangeIterObject *r = (_PyRangeIterObject *)iter;
24572515
DEOPT_IF(Py_TYPE(r) != &PyRangeIter_Type, FOR_ITER);
@@ -2472,7 +2530,7 @@ dummy_func(
24722530
}
24732531

24742532
// Only used by Tier 2
2475-
op(_ITER_EXHAUSTED_RANGE, (iter -- iter, exhausted)) {
2533+
op(_IS_ITER_EXHAUSTED_RANGE, (iter -- iter, exhausted)) {
24762534
_PyRangeIterObject *r = (_PyRangeIterObject *)iter;
24772535
assert(Py_TYPE(r) == &PyRangeIter_Type);
24782536
exhausted = r->len <= 0 ? Py_True : Py_False;
@@ -2490,7 +2548,10 @@ dummy_func(
24902548
}
24912549

24922550
macro(FOR_ITER_RANGE) =
2493-
unused/1 + _ITER_CHECK_RANGE + _ITER_JUMP_RANGE + _ITER_NEXT_RANGE;
2551+
unused/1 + // Skip over the counter
2552+
_ITER_CHECK_RANGE +
2553+
_ITER_JUMP_RANGE +
2554+
_ITER_NEXT_RANGE;
24942555

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

Python/executor_cases.c.h

Lines changed: 75 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)