From 9b31385501501ac14c7c0edf555eaad2df697a1c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 4 Jul 2026 14:44:21 +0100 Subject: [PATCH 1/2] [mypyc] Make for loop over list memory safe on free-threaded builds --- mypyc/irbuild/for_helpers.py | 13 +++++++++++-- mypyc/test-data/irbuild-basic.test | 6 +++--- mypyc/test-data/irbuild-lists.test | 8 ++++---- mypyc/test-data/irbuild-set.test | 4 ++-- mypyc/test-data/irbuild-statements.test | 6 +++--- mypyc/test-data/irbuild-tuple.test | 4 ++-- mypyc/test-data/irbuild-vec-i64.test | 4 ++-- mypyc/test-data/lowering-int.test | 2 +- mypyc/test/test_lowering.py | 8 +++++++- 9 files changed, 35 insertions(+), 20 deletions(-) diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 95894dcedaba8..fc539a1fc090b 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -28,6 +28,7 @@ Var, ) from mypy.types import LiteralType, TupleType, get_proper_type, get_proper_types +from mypyc.common import IS_FREE_THREADED from mypyc.ir.ops import ( ERR_NEVER, BasicBlock, @@ -81,7 +82,12 @@ ) from mypyc.primitives.exc_ops import no_err_occurred_op, propagate_if_error_op from mypyc.primitives.generic_ops import aiter_op, anext_op, iter_op, next_op -from mypyc.primitives.list_ops import list_append_op, list_get_item_unsafe_op, new_list_set_item_op +from mypyc.primitives.list_ops import ( + list_append_op, + list_get_item_int64_op, + list_get_item_unsafe_op, + new_list_set_item_op, +) from mypyc.primitives.misc_ops import stop_async_iteration_op from mypyc.primitives.registry import CFunctionDescription from mypyc.primitives.set_ops import set_add_op @@ -866,7 +872,10 @@ def unsafe_index(builder: IRBuilder, target: Value, index: Value, line: int) -> # since we want to use __getitem__ if we don't have an unsafe version, # so we just check manually. if is_list_rprimitive(target.type): - return builder.primitive_op(list_get_item_unsafe_op, [target, index], line) + if not IS_FREE_THREADED: + return builder.primitive_op(list_get_item_unsafe_op, [target, index], line) + else: + return builder.primitive_op(list_get_item_int64_op, [target, index], line) elif is_tuple_rprimitive(target.type): return builder.call_c(tuple_get_item_unsafe_op, [target, index], line) elif is_str_rprimitive(target.type): diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 4e015c80a71d3..e0c86e4cfb93f 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -1870,7 +1870,7 @@ L0: r5 = a.f(12, 6, r4) return 1 -[case testListComprehension] +[case testListComprehension_withgil] from typing import List def f() -> List[int]: @@ -1931,7 +1931,7 @@ L7: L8: return r0 -[case testDictComprehension] +[case testDictComprehension_withgil] from typing import Dict def f() -> Dict[int, int]: return {x: x*x for x in [1,2,3] if x != 2 if x != 3} @@ -1993,7 +1993,7 @@ L7: L8: return r0 -[case testLoopsMultipleAssign] +[case testLoopsMultipleAssign_withgil] from typing import List, Tuple def f(l: List[Tuple[int, int, int]]) -> List[int]: for x, y, z in l: diff --git a/mypyc/test-data/irbuild-lists.test b/mypyc/test-data/irbuild-lists.test index 6050931497a7b..e70d81c96bf7a 100644 --- a/mypyc/test-data/irbuild-lists.test +++ b/mypyc/test-data/irbuild-lists.test @@ -379,7 +379,7 @@ L0: r2 = r1 >= 0 :: signed return 1 -[case testListBuiltFromGenerator] +[case testListBuiltFromGenerator_withgil] from typing import List def f(source: List[int]) -> None: a = list(x + 1 for x in source) @@ -448,7 +448,7 @@ L8: b = r11 return 1 -[case testGeneratorNext] +[case testGeneratorNext_withgil] from typing import List, Optional def test(x: List[int]) -> None: @@ -489,7 +489,7 @@ L5: res = r6 return 1 -[case testSimplifyListUnion] +[case testSimplifyListUnion_withgil] from typing import List, Union, Optional def narrow(a: Union[List[str], List[bytes], int]) -> int: @@ -923,7 +923,7 @@ L10: a = r3 return 1 -[case testListBuiltFromStars] +[case testListBuiltFromStars_withgil] from typing import Final abc: Final = "abc" diff --git a/mypyc/test-data/irbuild-set.test b/mypyc/test-data/irbuild-set.test index 00bcff68d4ff5..ac01c58401118 100644 --- a/mypyc/test-data/irbuild-set.test +++ b/mypyc/test-data/irbuild-set.test @@ -53,7 +53,7 @@ L0: r0 = PySet_New(l) return r0 -[case testNewSetFromIterable2] +[case testNewSetFromIterable2_withgil] def f(x: int) -> int: return x @@ -273,7 +273,7 @@ L4: e = r0 return 1 -[case testNewSetFromIterable3] +[case testNewSetFromIterable3_withgil] def f1(x: int) -> int: return x diff --git a/mypyc/test-data/irbuild-statements.test b/mypyc/test-data/irbuild-statements.test index 8199cfdaa699e..340953b040586 100644 --- a/mypyc/test-data/irbuild-statements.test +++ b/mypyc/test-data/irbuild-statements.test @@ -243,7 +243,7 @@ L1: r2 = r0 < r1 :: signed if r2 goto L2 else goto L4 :: bool L2: - r3 = list_get_item_unsafe ls, r0 + r3 = CPyList_GetItemInt64(ls, r0) r4 = unbox(int, r3) x = r4 r5 = CPyTagged_Add(y, x) @@ -899,7 +899,7 @@ L0: r6 = r5 >= 0 :: signed return 1 -[case testForEnumerate] +[case testForEnumerate_withgil] from typing import List, Iterable def f(a: List[int]) -> None: @@ -967,7 +967,7 @@ L4: L5: return 1 -[case testForZip] +[case testForZip_withgil] from typing import List, Iterable, Sequence def f(a: List[int], b: Sequence[bool]) -> None: diff --git a/mypyc/test-data/irbuild-tuple.test b/mypyc/test-data/irbuild-tuple.test index 9f6b0f5d07390..808b5b9e0d4ab 100644 --- a/mypyc/test-data/irbuild-tuple.test +++ b/mypyc/test-data/irbuild-tuple.test @@ -281,7 +281,7 @@ L5: L6: return r6 -[case testTupleBuiltFromList] +[case testTupleBuiltFromList_withgil] def f(val: int) -> bool: return val % 2 == 0 @@ -1018,7 +1018,7 @@ L5: a = r42 return 1 -[case testTupleBuiltFromStars] +[case testTupleBuiltFromStars_withgil] from typing import Final abc: Final = "abc" diff --git a/mypyc/test-data/irbuild-vec-i64.test b/mypyc/test-data/irbuild-vec-i64.test index af69f30924d46..176e2616a4856 100644 --- a/mypyc/test-data/irbuild-vec-i64.test +++ b/mypyc/test-data/irbuild-vec-i64.test @@ -334,7 +334,7 @@ L3: L4: return r1 -[case testVecI64FastComprehensionFromList] +[case testVecI64FastComprehensionFromList_nogil] from librt.vecs import vec from mypy_extensions import i64 from typing import List @@ -364,7 +364,7 @@ L1: r4 = r2 < r3 :: signed if r4 goto L2 else goto L4 :: bool L2: - r5 = list_get_item_unsafe l, r2 + r5 = CPyList_GetItemInt64(l, r2) r6 = unbox(i64, r5) x = r6 r7 = x + 1 diff --git a/mypyc/test-data/lowering-int.test b/mypyc/test-data/lowering-int.test index c2bcba54e444d..72c4a82dcaea3 100644 --- a/mypyc/test-data/lowering-int.test +++ b/mypyc/test-data/lowering-int.test @@ -332,7 +332,7 @@ L4: L5: return 4 -[case testLowerIntForLoop_64bit] +[case testLowerIntForLoop_withgil_64bit] from __future__ import annotations def f(l: list[int]) -> None: diff --git a/mypyc/test/test_lowering.py b/mypyc/test/test_lowering.py index 3eb4698c68793..75351cd50e4a7 100644 --- a/mypyc/test/test_lowering.py +++ b/mypyc/test/test_lowering.py @@ -7,7 +7,7 @@ from mypy.errors import CompileError from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase -from mypyc.common import TOP_LEVEL_NAME +from mypyc.common import IS_FREE_THREADED, TOP_LEVEL_NAME from mypyc.ir.pprint import format_func from mypyc.options import CompilerOptions from mypyc.test.testutil import ( @@ -36,6 +36,12 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: if options is None: # Skipped test case return + if "_withgil" in testcase.name and IS_FREE_THREADED: + # Test case should only run on a non-free-threaded build. + return + if "_nogil" in testcase.name and not IS_FREE_THREADED: + # Test case should only run on a free-threaded build. + return with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): expected_output = remove_comment_lines(testcase.output) expected_output = replace_word_size(expected_output) From 76125cf80e2bdcdafe55df71e3fef6ff25deb6d9 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 4 Jul 2026 15:16:33 +0100 Subject: [PATCH 2/2] Fix testForList test case --- mypyc/test-data/irbuild-statements.test | 39 ++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-statements.test b/mypyc/test-data/irbuild-statements.test index 340953b040586..8c5c95c8223ec 100644 --- a/mypyc/test-data/irbuild-statements.test +++ b/mypyc/test-data/irbuild-statements.test @@ -218,7 +218,44 @@ L5: L6: return 1 -[case testForList] +[case testForList_withgil] +from typing import List + +def f(ls: List[int]) -> int: + y = 0 + for x in ls: + y = y + x + return y +[out] +def f(ls): + ls :: list + y :: int + r0, r1 :: native_int + r2 :: bit + r3 :: object + r4, x, r5 :: int + r6 :: native_int +L0: + y = 0 + r0 = 0 +L1: + r1 = var_object_size ls + r2 = r0 < r1 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = list_get_item_unsafe ls, r0 + r4 = unbox(int, r3) + x = r4 + r5 = CPyTagged_Add(y, x) + y = r5 +L3: + r6 = r0 + 1 + r0 = r6 + goto L1 +L4: + return y + +[case testForList_nogil] from typing import List def f(ls: List[int]) -> int: