Skip to content

Commit f1bccb2

Browse files
authored
Merge branch 'main' into match-class-opcodes
2 parents b3ccb13 + 5f736a0 commit f1bccb2

18 files changed

+496
-58
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ Lib/test/test_stable_abi_ctypes.py generated
9494
Lib/test/test_zoneinfo/data/*.json generated
9595
Lib/token.py generated
9696
Misc/sbom.spdx.json generated
97+
Modules/_testinternalcapi/test_cases.c.h generated
98+
Modules/_testinternalcapi/test_targets.h generated
9799
Objects/typeslots.inc generated
98100
PC/python3dll.c generated
99101
Parser/parser.c generated

Include/internal/pycore_opcode_metadata.h

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

Include/internal/pycore_optimizer_types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ typedef struct {
7676
typedef enum {
7777
JIT_PRED_IS,
7878
JIT_PRED_IS_NOT,
79+
JIT_PRED_EQ,
80+
JIT_PRED_NE,
7981
} JitOptPredicateKind;
8082

8183
typedef struct {

Include/internal/pycore_uop_ids.h

Lines changed: 1 addition & 1 deletion
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: 3 additions & 3 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: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,138 @@ def testfunc(n):
890890
self.assertLessEqual(len(guard_nos_unicode_count), 1)
891891
self.assertIn("_COMPARE_OP_STR", uops)
892892

893+
def test_compare_int_eq_narrows_to_constant(self):
894+
def f(n):
895+
def return_1():
896+
return 1
897+
898+
hits = 0
899+
v = return_1()
900+
for _ in range(n):
901+
if v == 1:
902+
if v == 1:
903+
hits += 1
904+
return hits
905+
906+
res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
907+
self.assertEqual(res, TIER2_THRESHOLD)
908+
self.assertIsNotNone(ex)
909+
uops = get_opnames(ex)
910+
911+
# Constant narrowing allows constant folding for second comparison
912+
self.assertLessEqual(count_ops(ex, "_COMPARE_OP_INT"), 1)
913+
914+
def test_compare_int_ne_narrows_to_constant(self):
915+
def f(n):
916+
def return_1():
917+
return 1
918+
919+
hits = 0
920+
v = return_1()
921+
for _ in range(n):
922+
if v != 1:
923+
hits += 1000
924+
else:
925+
if v == 1:
926+
hits += v + 1
927+
return hits
928+
929+
res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
930+
self.assertEqual(res, TIER2_THRESHOLD * 2)
931+
self.assertIsNotNone(ex)
932+
uops = get_opnames(ex)
933+
934+
# Constant narrowing allows constant folding for second comparison
935+
self.assertLessEqual(count_ops(ex, "_COMPARE_OP_INT"), 1)
936+
937+
def test_compare_float_eq_narrows_to_constant(self):
938+
def f(n):
939+
def return_tenth():
940+
return 0.1
941+
942+
hits = 0
943+
v = return_tenth()
944+
for _ in range(n):
945+
if v == 0.1:
946+
if v == 0.1:
947+
hits += 1
948+
return hits
949+
950+
res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
951+
self.assertEqual(res, TIER2_THRESHOLD)
952+
self.assertIsNotNone(ex)
953+
uops = get_opnames(ex)
954+
955+
# Constant narrowing allows constant folding for second comparison
956+
self.assertLessEqual(count_ops(ex, "_COMPARE_OP_FLOAT"), 1)
957+
958+
def test_compare_float_ne_narrows_to_constant(self):
959+
def f(n):
960+
def return_tenth():
961+
return 0.1
962+
963+
hits = 0
964+
v = return_tenth()
965+
for _ in range(n):
966+
if v != 0.1:
967+
hits += 1000
968+
else:
969+
if v == 0.1:
970+
hits += 1
971+
return hits
972+
973+
res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
974+
self.assertEqual(res, TIER2_THRESHOLD)
975+
self.assertIsNotNone(ex)
976+
uops = get_opnames(ex)
977+
978+
# Constant narrowing allows constant folding for second comparison
979+
self.assertLessEqual(count_ops(ex, "_COMPARE_OP_FLOAT"), 1)
980+
981+
def test_compare_str_eq_narrows_to_constant(self):
982+
def f(n):
983+
def return_hello():
984+
return "hello"
985+
986+
hits = 0
987+
v = return_hello()
988+
for _ in range(n):
989+
if v == "hello":
990+
if v == "hello":
991+
hits += 1
992+
return hits
993+
994+
res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
995+
self.assertEqual(res, TIER2_THRESHOLD)
996+
self.assertIsNotNone(ex)
997+
uops = get_opnames(ex)
998+
999+
# Constant narrowing allows constant folding for second comparison
1000+
self.assertLessEqual(count_ops(ex, "_COMPARE_OP_STR"), 1)
1001+
1002+
def test_compare_str_ne_narrows_to_constant(self):
1003+
def f(n):
1004+
def return_hello():
1005+
return "hello"
1006+
1007+
hits = 0
1008+
v = return_hello()
1009+
for _ in range(n):
1010+
if v != "hello":
1011+
hits += 1000
1012+
else:
1013+
if v == "hello":
1014+
hits += 1
1015+
return hits
1016+
1017+
res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
1018+
self.assertEqual(res, TIER2_THRESHOLD)
1019+
self.assertIsNotNone(ex)
1020+
uops = get_opnames(ex)
1021+
1022+
# Constant narrowing allows constant folding for second comparison
1023+
self.assertLessEqual(count_ops(ex, "_COMPARE_OP_STR"), 1)
1024+
8931025
@unittest.skip("gh-139109 WIP")
8941026
def test_combine_stack_space_checks_sequential(self):
8951027
def dummy12(x):
@@ -2897,6 +3029,29 @@ def testfunc(n):
28973029
self.assertIn("_POP_TOP_NOP", uops)
28983030
self.assertLessEqual(count_ops(ex, "_POP_TOP"), 2)
28993031

3032+
def test_binary_op_refcount_elimination(self):
3033+
class CustomAdder:
3034+
def __init__(self, val):
3035+
self.val = val
3036+
def __add__(self, other):
3037+
return CustomAdder(self.val + other.val)
3038+
3039+
def testfunc(n):
3040+
a = CustomAdder(1)
3041+
b = CustomAdder(2)
3042+
res = None
3043+
for _ in range(n):
3044+
res = a + b
3045+
return res.val if res else 0
3046+
3047+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
3048+
self.assertEqual(res, 3)
3049+
self.assertIsNotNone(ex)
3050+
uops = get_opnames(ex)
3051+
self.assertIn("_BINARY_OP", uops)
3052+
self.assertIn("_POP_TOP_NOP", uops)
3053+
self.assertLessEqual(count_ops(ex, "_POP_TOP"), 2)
3054+
29003055
def test_binary_op_extend_float_long_add_refcount_elimination(self):
29013056
def testfunc(n):
29023057
a = 1.5
@@ -3866,6 +4021,29 @@ def __next__(self):
38664021
"""), PYTHON_JIT="1", PYTHON_JIT_STRESS="1")
38674022
self.assertEqual(result[0].rc, 0, result)
38684023

4024+
def test_144068_daemon_thread_jit_cleanup(self):
4025+
result = script_helper.run_python_until_end('-c', textwrap.dedent("""
4026+
import threading
4027+
import time
4028+
4029+
def hot_loop():
4030+
end = time.time() + 5.0
4031+
while time.time() < end:
4032+
pass
4033+
4034+
# Create a daemon thread that will be abandoned at shutdown
4035+
t = threading.Thread(target=hot_loop, daemon=True)
4036+
t.start()
4037+
4038+
time.sleep(0.1)
4039+
"""), PYTHON_JIT="1", ASAN_OPTIONS="detect_leaks=1")
4040+
self.assertEqual(result[0].rc, 0, result)
4041+
stderr = result[0].err.decode('utf-8', errors='replace')
4042+
self.assertNotIn('LeakSanitizer', stderr,
4043+
f"Memory leak detected by ASan:\n{stderr}")
4044+
self.assertNotIn('_PyJit_TryInitializeTracing', stderr,
4045+
f"JIT tracer memory leak detected:\n{stderr}")
4046+
38694047
def global_identity(x):
38704048
return x
38714049

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Eliminate redundant refcounting in the JIT for ``BINARY_OP``.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix JIT tracer memory leak, ensure the JIT tracer state is freed when daemon threads are cleaned up during interpreter shutdown.

Modules/_testinternalcapi/test_cases.c.h

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

Python/bytecodes.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5152,7 +5152,7 @@ dummy_func(
51525152
assert(oparg <= NB_OPARG_LAST);
51535153
}
51545154

5155-
op(_BINARY_OP, (lhs, rhs -- res)) {
5155+
op(_BINARY_OP, (lhs, rhs -- res, l, r)) {
51565156
PyObject *lhs_o = PyStackRef_AsPyObjectBorrow(lhs);
51575157
PyObject *rhs_o = PyStackRef_AsPyObjectBorrow(rhs);
51585158

@@ -5162,10 +5162,13 @@ dummy_func(
51625162
ERROR_NO_POP();
51635163
}
51645164
res = PyStackRef_FromPyObjectSteal(res_o);
5165-
DECREF_INPUTS();
5165+
l = lhs;
5166+
r = rhs;
5167+
DEAD(lhs);
5168+
DEAD(rhs);
51665169
}
51675170

5168-
macro(BINARY_OP) = _SPECIALIZE_BINARY_OP + unused/4 + _BINARY_OP;
5171+
macro(BINARY_OP) = _SPECIALIZE_BINARY_OP + unused/4 + _BINARY_OP + POP_TOP + POP_TOP;
51695172

51705173
pure replicate(2:4) inst(SWAP, (bottom, unused[oparg-2], top --
51715174
bottom, unused[oparg-2], top)) {

0 commit comments

Comments
 (0)