Skip to content

Commit 9284ae4

Browse files
Merge branch 'main' into dis-theme-support
2 parents 103124d + a51bf70 commit 9284ae4

31 files changed

Lines changed: 1177 additions & 276 deletions

.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

Doc/library/symtable.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ Examining Symbol Tables
180180
Return a tuple containing names of :term:`free (closure) variables <closure variable>`
181181
in this function.
182182

183+
.. method:: get_cells()
184+
185+
Return a tuple containing names of :term:`cell variables <closure variable>` in this table.
186+
187+
.. versionadded:: next
188+
183189

184190
.. class:: Class
185191

@@ -291,6 +297,12 @@ Examining Symbol Tables
291297
Return ``True`` if the symbol is referenced in its block, but not assigned
292298
to.
293299

300+
.. method:: is_cell()
301+
302+
Return ``True`` if the symbol is referenced but not assigned in a nested block.
303+
304+
.. versionadded:: next
305+
294306
.. method:: is_free_class()
295307

296308
Return *True* if a class-scoped symbol is free from

Doc/whatsnew/3.15.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,13 @@ ssl
748748
(Contributed by Ron Frederick in :gh:`138252`.)
749749

750750

751+
symtable
752+
--------
753+
754+
* Add :meth:`symtable.Function.get_cells` and :meth:`symtable.Symbol.is_cell` methods.
755+
(Contributed by Yashp002 in :gh:`143504`.)
756+
757+
751758
sys
752759
---
753760

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/symtable.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class Function(SymbolTable):
184184
__frees = None
185185
__globals = None
186186
__nonlocals = None
187+
__cells = None
187188

188189
def __idents_matching(self, test_func):
189190
return tuple(ident for ident in self.get_identifiers()
@@ -229,6 +230,14 @@ def get_frees(self):
229230
self.__frees = self.__idents_matching(is_free)
230231
return self.__frees
231232

233+
def get_cells(self):
234+
"""Return a tuple of cell variables in the function.
235+
"""
236+
if self.__cells is None:
237+
is_cell = lambda x: _get_scope(x) == CELL
238+
self.__cells = self.__idents_matching(is_cell)
239+
return self.__cells
240+
232241

233242
class Class(SymbolTable):
234243

@@ -342,6 +351,10 @@ def is_free(self):
342351
"""
343352
return bool(self.__scope == FREE)
344353

354+
def is_cell(self):
355+
"""Return *True* if the symbol is a cell variable."""
356+
return bool(self.__scope == CELL)
357+
345358
def is_free_class(self):
346359
"""Return *True* if a class-scoped symbol is free from
347360
the perspective of a method."""

Lib/test/test_bytes.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -792,16 +792,16 @@ def __int__(self):
792792
pi = PseudoFloat(3.1415)
793793

794794
exceptions_params = [
795-
('%x format: an integer is required, not float', b'%x', 3.14),
796-
('%X format: an integer is required, not float', b'%X', 2.11),
797-
('%o format: an integer is required, not float', b'%o', 1.79),
798-
('%x format: an integer is required, not PseudoFloat', b'%x', pi),
799-
('%x format: an integer is required, not complex', b'%x', 3j),
800-
('%X format: an integer is required, not complex', b'%X', 2j),
801-
('%o format: an integer is required, not complex', b'%o', 1j),
802-
('%u format: a real number is required, not complex', b'%u', 3j),
803-
('%i format: a real number is required, not complex', b'%i', 2j),
804-
('%d format: a real number is required, not complex', b'%d', 2j),
795+
('%x requires an integer, not float', b'%x', 3.14),
796+
('%X requires an integer, not float', b'%X', 2.11),
797+
('%o requires an integer, not float', b'%o', 1.79),
798+
(r'%x requires an integer, not .*\.PseudoFloat', b'%x', pi),
799+
('%x requires an integer, not complex', b'%x', 3j),
800+
('%X requires an integer, not complex', b'%X', 2j),
801+
('%o requires an integer, not complex', b'%o', 1j),
802+
('%u requires a real number, not complex', b'%u', 3j),
803+
('%i requires a real number, not complex', b'%i', 2j),
804+
('%d requires a real number, not complex', b'%d', 2j),
805805
(
806806
r'%c requires an integer in range\(256\)'
807807
r' or a single byte, not .*\.PseudoFloat',
@@ -810,7 +810,7 @@ def __int__(self):
810810
]
811811

812812
for msg, format_bytes, value in exceptions_params:
813-
with self.assertRaisesRegex(TypeError, msg):
813+
with self.assertRaisesRegex(TypeError, 'format argument: ' + msg):
814814
operator.mod(format_bytes, value)
815815

816816
def test_memory_leak_gh_140939(self):

Lib/test/test_capi/test_opt.py

Lines changed: 193 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):
@@ -2287,6 +2419,21 @@ def testfunc(n):
22872419
self.assertNotIn("_GUARD_TOS_INT", uops)
22882420
self.assertIn("_POP_TOP_NOP", uops)
22892421

2422+
def test_call_len_string(self):
2423+
def testfunc(n):
2424+
for _ in range(n):
2425+
_ = len("abc")
2426+
d = ''
2427+
_ = len(d)
2428+
_ = len(b"def")
2429+
_ = len(b"")
2430+
2431+
_, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2432+
self.assertIsNotNone(ex)
2433+
uops = get_opnames(ex)
2434+
self.assertNotIn("_CALL_LEN", uops)
2435+
self.assertEqual(count_ops(ex, "_SHUFFLE_3_LOAD_CONST_INLINE_BORROW"), 4)
2436+
22902437
def test_call_len_known_length_small_int(self):
22912438
# Make sure that len(t) is optimized for a tuple of length 5.
22922439
# See https://github.com/python/cpython/issues/139393.
@@ -2897,6 +3044,29 @@ def testfunc(n):
28973044
self.assertIn("_POP_TOP_NOP", uops)
28983045
self.assertLessEqual(count_ops(ex, "_POP_TOP"), 2)
28993046

3047+
def test_binary_op_refcount_elimination(self):
3048+
class CustomAdder:
3049+
def __init__(self, val):
3050+
self.val = val
3051+
def __add__(self, other):
3052+
return CustomAdder(self.val + other.val)
3053+
3054+
def testfunc(n):
3055+
a = CustomAdder(1)
3056+
b = CustomAdder(2)
3057+
res = None
3058+
for _ in range(n):
3059+
res = a + b
3060+
return res.val if res else 0
3061+
3062+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
3063+
self.assertEqual(res, 3)
3064+
self.assertIsNotNone(ex)
3065+
uops = get_opnames(ex)
3066+
self.assertIn("_BINARY_OP", uops)
3067+
self.assertIn("_POP_TOP_NOP", uops)
3068+
self.assertLessEqual(count_ops(ex, "_POP_TOP"), 2)
3069+
29003070
def test_binary_op_extend_float_long_add_refcount_elimination(self):
29013071
def testfunc(n):
29023072
a = 1.5
@@ -3866,6 +4036,29 @@ def __next__(self):
38664036
"""), PYTHON_JIT="1", PYTHON_JIT_STRESS="1")
38674037
self.assertEqual(result[0].rc, 0, result)
38684038

4039+
def test_144068_daemon_thread_jit_cleanup(self):
4040+
result = script_helper.run_python_until_end('-c', textwrap.dedent("""
4041+
import threading
4042+
import time
4043+
4044+
def hot_loop():
4045+
end = time.time() + 5.0
4046+
while time.time() < end:
4047+
pass
4048+
4049+
# Create a daemon thread that will be abandoned at shutdown
4050+
t = threading.Thread(target=hot_loop, daemon=True)
4051+
t.start()
4052+
4053+
time.sleep(0.1)
4054+
"""), PYTHON_JIT="1", ASAN_OPTIONS="detect_leaks=1")
4055+
self.assertEqual(result[0].rc, 0, result)
4056+
stderr = result[0].err.decode('utf-8', errors='replace')
4057+
self.assertNotIn('LeakSanitizer', stderr,
4058+
f"Memory leak detected by ASan:\n{stderr}")
4059+
self.assertNotIn('_PyJit_TryInitializeTracing', stderr,
4060+
f"JIT tracer memory leak detected:\n{stderr}")
4061+
38694062
def global_identity(x):
38704063
return x
38714064

0 commit comments

Comments
 (0)