Skip to content

Commit 5e1e45d

Browse files
committed
Eliminate redundant refcounting from UNPACK_SEQUENCE family
1 parent 8b64dd8 commit 5e1e45d

9 files changed

Lines changed: 263 additions & 98 deletions

File tree

Include/internal/pycore_opcode_metadata.h

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

Include/internal/pycore_uop_ids.h

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 19 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_capi/test_opt.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,75 @@ def testfunc(n, m):
433433
uops = get_opnames(ex)
434434
self.assertIn("_FOR_ITER_TIER_TWO", uops)
435435

436+
def test_unpack_sequence_generic(self):
437+
def testfunc(x):
438+
i = 0
439+
while i < x:
440+
i += 1
441+
# Use an iterator to force generic UNPACK_SEQUENCE
442+
a, b = iter((1, 2))
443+
return a, b
444+
445+
res = testfunc(TIER2_THRESHOLD)
446+
self.assertEqual(res, (1, 2))
447+
448+
ex = get_first_executor(testfunc)
449+
self.assertIsNotNone(ex)
450+
uops = get_opnames(ex)
451+
self.assertIn("_UNPACK_SEQUENCE", uops)
452+
453+
def test_unpack_sequence_two_tuple(self):
454+
def testfunc(x):
455+
i = 0
456+
while i < x:
457+
i += 1
458+
t = (i, i)
459+
a, b = t
460+
return a, b
461+
462+
res = testfunc(TIER2_THRESHOLD)
463+
self.assertEqual(res, (TIER2_THRESHOLD, TIER2_THRESHOLD))
464+
465+
ex = get_first_executor(testfunc)
466+
self.assertIsNotNone(ex)
467+
uops = get_opnames(ex)
468+
self.assertIn("_UNPACK_SEQUENCE_TWO_TUPLE", uops)
469+
self.assertNotIn("_POP_TOP", uops)
470+
471+
def test_unpack_sequence_tuple(self):
472+
def testfunc(x):
473+
i = 0
474+
while i < x:
475+
i += 1
476+
t = (i, i, i)
477+
a, b, c = t
478+
return a, b, c
479+
480+
res = testfunc(TIER2_THRESHOLD)
481+
self.assertEqual(res, (TIER2_THRESHOLD, TIER2_THRESHOLD, TIER2_THRESHOLD))
482+
483+
ex = get_first_executor(testfunc)
484+
self.assertIsNotNone(ex)
485+
uops = get_opnames(ex)
486+
self.assertIn("_UNPACK_SEQUENCE_TUPLE", uops)
487+
self.assertNotIn("_POP_TOP", uops)
488+
489+
def test_unpack_sequence_list(self):
490+
def testfunc(x):
491+
i = 0
492+
while i < x:
493+
i += 1
494+
a, b = [1, 2]
495+
return a, b
496+
497+
res = testfunc(TIER2_THRESHOLD)
498+
self.assertEqual(res, (1, 2))
499+
500+
ex = get_first_executor(testfunc)
501+
self.assertIsNotNone(ex)
502+
uops = get_opnames(ex)
503+
self.assertIn("_UNPACK_SEQUENCE_LIST", uops)
504+
436505

437506
@requires_specialization
438507
@unittest.skipIf(Py_GIL_DISABLED, "optimizer not yet supported in free-threaded builds")

Python/bytecodes.c

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,33 +1574,38 @@ dummy_func(
15741574
(void)counter;
15751575
}
15761576

1577-
op(_UNPACK_SEQUENCE, (seq -- unused[oparg], top[0])) {
1578-
PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);
1577+
op(_UNPACK_SEQUENCE, (seq -- unused[oparg], top[0], s)) {
1578+
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
15791579
int res = _PyEval_UnpackIterableStackRef(tstate, seq_o, oparg, -1, top);
1580-
Py_DECREF(seq_o);
1581-
ERROR_IF(res == 0);
1580+
if (res == 0) {
1581+
PyStackRef_CLOSE(seq);
1582+
ERROR_IF(1);
1583+
}
1584+
s = seq;
1585+
INPUTS_DEAD();
15821586
}
15831587

1584-
macro(UNPACK_SEQUENCE) = _SPECIALIZE_UNPACK_SEQUENCE + _UNPACK_SEQUENCE;
1588+
macro(UNPACK_SEQUENCE) = _SPECIALIZE_UNPACK_SEQUENCE + _UNPACK_SEQUENCE + POP_TOP;
15851589

15861590
macro(UNPACK_SEQUENCE_TWO_TUPLE) =
1587-
_GUARD_TOS_TUPLE + unused/1 + _UNPACK_SEQUENCE_TWO_TUPLE;
1591+
_GUARD_TOS_TUPLE + unused/1 + _UNPACK_SEQUENCE_TWO_TUPLE + POP_TOP;
15881592

1589-
op(_UNPACK_SEQUENCE_TWO_TUPLE, (seq -- val1, val0)) {
1593+
op(_UNPACK_SEQUENCE_TWO_TUPLE, (seq -- val1, val0, s)) {
15901594
assert(oparg == 2);
15911595
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
15921596
assert(PyTuple_CheckExact(seq_o));
15931597
DEOPT_IF(PyTuple_GET_SIZE(seq_o) != 2);
15941598
STAT_INC(UNPACK_SEQUENCE, hit);
15951599
val0 = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq_o, 0));
15961600
val1 = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(seq_o, 1));
1597-
PyStackRef_CLOSE(seq);
1601+
s = seq;
1602+
INPUTS_DEAD();
15981603
}
15991604

16001605
macro(UNPACK_SEQUENCE_TUPLE) =
1601-
_GUARD_TOS_TUPLE + unused/1 + _UNPACK_SEQUENCE_TUPLE;
1606+
_GUARD_TOS_TUPLE + unused/1 + _UNPACK_SEQUENCE_TUPLE + POP_TOP;
16021607

1603-
op(_UNPACK_SEQUENCE_TUPLE, (seq -- values[oparg])) {
1608+
op(_UNPACK_SEQUENCE_TUPLE, (seq -- values[oparg], s)) {
16041609
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
16051610
assert(PyTuple_CheckExact(seq_o));
16061611
DEOPT_IF(PyTuple_GET_SIZE(seq_o) != oparg);
@@ -1609,13 +1614,14 @@ dummy_func(
16091614
for (int i = oparg; --i >= 0; ) {
16101615
*values++ = PyStackRef_FromPyObjectNew(items[i]);
16111616
}
1612-
DECREF_INPUTS();
1617+
s = seq;
1618+
INPUTS_DEAD();
16131619
}
16141620

16151621
macro(UNPACK_SEQUENCE_LIST) =
1616-
_GUARD_TOS_LIST + unused/1 + _UNPACK_SEQUENCE_LIST;
1622+
_GUARD_TOS_LIST + unused/1 + _UNPACK_SEQUENCE_LIST + POP_TOP;
16171623

1618-
op(_UNPACK_SEQUENCE_LIST, (seq -- values[oparg])) {
1624+
op(_UNPACK_SEQUENCE_LIST, (seq -- values[oparg], s)) {
16191625
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
16201626
assert(PyList_CheckExact(seq_o));
16211627
DEOPT_IF(!LOCK_OBJECT(seq_o));
@@ -1629,7 +1635,8 @@ dummy_func(
16291635
*values++ = PyStackRef_FromPyObjectNew(items[i]);
16301636
}
16311637
UNLOCK_OBJECT(seq_o);
1632-
DECREF_INPUTS();
1638+
s = seq;
1639+
INPUTS_DEAD();
16331640
}
16341641

16351642
inst(UNPACK_EX, (seq -- unused[oparg & 0xFF], unused, unused[oparg >> 8], top[0])) {

0 commit comments

Comments
 (0)