Skip to content

Commit 9c9a0f7

Browse files
GH-132732: Use pure op machinery to optimize various instructions with _POP_TOP and _POP_TWO (#137577)
1 parent 5c4bb9b commit 9c9a0f7

File tree

5 files changed

+259
-23
lines changed

5 files changed

+259
-23
lines changed

Lib/test/test_capi/test_opt.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,74 @@ def f(n):
16141614
# But all of the appends we care about are still there:
16151615
self.assertEqual(uops.count("_CALL_LIST_APPEND"), len("ABCDEFG"))
16161616

1617+
def test_unary_negative_pop_top_load_const_inline_borrow(self):
1618+
def testfunc(n):
1619+
x = 0
1620+
for i in range(n):
1621+
a = 1
1622+
result = -a
1623+
if result < 0:
1624+
x += 1
1625+
return x
1626+
1627+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1628+
self.assertEqual(res, TIER2_THRESHOLD)
1629+
self.assertIsNotNone(ex)
1630+
uops = get_opnames(ex)
1631+
self.assertNotIn("_UNARY_NEGATIVE", uops)
1632+
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
1633+
1634+
def test_unary_not_pop_top_load_const_inline_borrow(self):
1635+
def testfunc(n):
1636+
x = 0
1637+
for i in range(n):
1638+
a = 42
1639+
result = not a
1640+
if result:
1641+
x += 1
1642+
return x
1643+
1644+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1645+
self.assertEqual(res, 0)
1646+
self.assertIsNotNone(ex)
1647+
uops = get_opnames(ex)
1648+
self.assertNotIn("_UNARY_NOT", uops)
1649+
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
1650+
1651+
def test_unary_invert_pop_top_load_const_inline_borrow(self):
1652+
def testfunc(n):
1653+
x = 0
1654+
for i in range(n):
1655+
a = 0
1656+
result = ~a
1657+
if result < 0:
1658+
x += 1
1659+
return x
1660+
1661+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1662+
self.assertEqual(res, TIER2_THRESHOLD)
1663+
self.assertIsNotNone(ex)
1664+
uops = get_opnames(ex)
1665+
self.assertNotIn("_UNARY_INVERT", uops)
1666+
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
1667+
1668+
def test_compare_op_pop_two_load_const_inline_borrow(self):
1669+
def testfunc(n):
1670+
x = 0
1671+
for _ in range(n):
1672+
a = 10
1673+
b = 10.0
1674+
if a == b:
1675+
x += 1
1676+
return x
1677+
1678+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1679+
self.assertEqual(res, TIER2_THRESHOLD)
1680+
self.assertIsNotNone(ex)
1681+
uops = get_opnames(ex)
1682+
self.assertNotIn("_COMPARE_OP", uops)
1683+
self.assertNotIn("_POP_TWO_LOAD_CONST_INLINE_BORROW", uops)
1684+
16171685
def test_compare_op_int_pop_two_load_const_inline_borrow(self):
16181686
def testfunc(n):
16191687
x = 0
@@ -1665,6 +1733,23 @@ def testfunc(n):
16651733
self.assertNotIn("_COMPARE_OP_FLOAT", uops)
16661734
self.assertNotIn("_POP_TWO_LOAD_CONST_INLINE_BORROW", uops)
16671735

1736+
def test_contains_op_pop_two_load_const_inline_borrow(self):
1737+
def testfunc(n):
1738+
x = 0
1739+
for _ in range(n):
1740+
a = "foo"
1741+
s = "foo bar baz"
1742+
if a in s:
1743+
x += 1
1744+
return x
1745+
1746+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1747+
self.assertEqual(res, TIER2_THRESHOLD)
1748+
self.assertIsNotNone(ex)
1749+
uops = get_opnames(ex)
1750+
self.assertNotIn("_CONTAINS_OP", uops)
1751+
self.assertNotIn("_POP_TWO_LOAD_CONST_INLINE_BORROW", uops)
1752+
16681753
def test_to_bool_bool_contains_op_set(self):
16691754
"""
16701755
Test that _TO_BOOL_BOOL is removed from code like:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Optimize ``_COMPARE_OP``, ``_CONTAINS_OP``, ``_UNARY_NEGATIVE``, ``_UNARY_NOT``, and ``_UNARY_INVERT`` in JIT builds with constant-loading uops (``_POP_TWO_LOAD_CONST_INLINE_BORROW`` and ``_POP_TOP_LOAD_CONST_INLINE_BORROW``), and then remove both to reduce instruction count.

Python/optimizer_bytecodes.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ dummy_func(void) {
397397
}
398398

399399
op(_UNARY_NEGATIVE, (value -- res)) {
400+
REPLACE_OPCODE_IF_EVALUATES_PURE(value);
400401
if (sym_is_compact_int(value)) {
401402
res = sym_new_compact_int(ctx);
402403
}
@@ -412,6 +413,10 @@ dummy_func(void) {
412413
}
413414

414415
op(_UNARY_INVERT, (value -- res)) {
416+
// Required to avoid a warning due to the deprecation of bitwise inversion of bools
417+
if (!sym_matches_type(value, &PyBool_Type)) {
418+
REPLACE_OPCODE_IF_EVALUATES_PURE(value);
419+
}
415420
if (sym_matches_type(value, &PyLong_Type)) {
416421
res = sym_new_type(ctx, &PyLong_Type);
417422
}
@@ -421,6 +426,9 @@ dummy_func(void) {
421426
}
422427

423428
op(_COMPARE_OP, (left, right -- res)) {
429+
// Comparison between bytes and str or int is not impacted by this optimization as bytes
430+
// is not a safe type (due to its ability to raise a warning during comparisons).
431+
REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
424432
if (oparg & 16) {
425433
res = sym_new_type(ctx, &PyBool_Type);
426434
}
@@ -449,6 +457,7 @@ dummy_func(void) {
449457
}
450458

451459
op(_CONTAINS_OP, (left, right -- b)) {
460+
REPLACE_OPCODE_IF_EVALUATES_PURE(left, right);
452461
b = sym_new_type(ctx, &PyBool_Type);
453462
}
454463

0 commit comments

Comments
 (0)