From 903f3842ef4237ddfbf3289ad1d1b1cafa2dbb3e Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Thu, 14 Aug 2025 03:17:40 +0000 Subject: [PATCH 1/7] [mypyc] feat: `__mypyc_empty_tuple__` constant --- mypyc/irbuild/ll_builder.py | 9 ++++++--- mypyc/lib-rt/CPy.h | 7 +++++++ mypyc/lib-rt/init.c | 10 ++++++++++ mypyc/lib-rt/tuple_ops.c | 3 +++ mypyc/primitives/tuple_ops.py | 7 +++++++ 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index a5e28268efed..bcb971a2ea45 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -183,7 +183,7 @@ str_ssize_t_size_op, unicode_compare, ) -from mypyc.primitives.tuple_ops import list_tuple_op, new_tuple_op, new_tuple_with_length_op +from mypyc.primitives.tuple_ops import list_tuple_op, load_empty_tuple_constant_op, new_tuple_op, new_tuple_with_length_op from mypyc.rt_subtype import is_runtime_subtype from mypyc.sametype import is_same_type from mypyc.subtype import is_subtype @@ -2305,8 +2305,11 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val return self.call_c(generic_len_op, [val], line) def new_tuple(self, items: list[Value], line: int) -> Value: - size: Value = Integer(len(items), c_pyssize_t_rprimitive) - return self.call_c(new_tuple_op, [size] + items, line) + if items: + size: Value = Integer(len(items), c_pyssize_t_rprimitive) + return self.call_c(new_tuple_op, [size] + items, line) + else: + return self.call_c(load_empty_tuple_constant_op, [], line) def new_tuple_with_length(self, length: Value, line: int) -> Value: """This function returns an uninitialized tuple. diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 1881aa97f308..33edcea51340 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -64,6 +64,13 @@ typedef struct tuple_T4CIOO { } tuple_T4CIOO; #endif +// System-wide empty tuple constant +extern PyObject * __mypyc_empty_tuple__; + +static inline PyObject *_CPyTuple_LoadEmptyTupleConstant() { + Py_INCREF(__mypyc_empty_tuple__); + return __mypyc_empty_tuple__; +} // Native object operations diff --git a/mypyc/lib-rt/init.c b/mypyc/lib-rt/init.c index 01b133233489..653b224ece9d 100644 --- a/mypyc/lib-rt/init.c +++ b/mypyc/lib-rt/init.c @@ -10,4 +10,14 @@ PyObject *_CPy_ExcDummy = (PyObject *)&_CPy_ExcDummyStruct; // things at load time. void CPy_Init(void) { _CPy_ExcDummyStruct.ob_base.ob_type = &PyBaseObject_Type; + + // Initialize system-wide empty tuple constant + if (__mypyc_empty_tuple__ == NULL) { + __mypyc_empty_tuple__ = PyTuple_New(0); + if (!__mypyc_empty_tuple__) { + PyErr_SetString(PyExc_RuntimeError, "Failed to initialize __mypyc_empty_tuple__"); + return; + } + Py_INCREF(__mypyc_empty_tuple__); + } } diff --git a/mypyc/lib-rt/tuple_ops.c b/mypyc/lib-rt/tuple_ops.c index 1df73f1907e2..1c0d694ae4bb 100644 --- a/mypyc/lib-rt/tuple_ops.c +++ b/mypyc/lib-rt/tuple_ops.c @@ -5,6 +5,9 @@ #include #include "CPy.h" +// System-wide empty tuple constant +PyObject * __mypyc_empty_tuple__ = NULL; + PyObject *CPySequenceTuple_GetItem(PyObject *tuple, CPyTagged index) { if (CPyTagged_CheckShort(index)) { Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); diff --git a/mypyc/primitives/tuple_ops.py b/mypyc/primitives/tuple_ops.py index d95161acf853..c58f7f853832 100644 --- a/mypyc/primitives/tuple_ops.py +++ b/mypyc/primitives/tuple_ops.py @@ -55,6 +55,13 @@ error_kind=ERR_MAGIC, ) +load_empty_tuple_constant_op = custom_op( + arg_types=[], + return_type=tuple_rprimitive, + c_function_name="_CPyTuple_LoadEmptyTupleConstant", + error_kind=ERR_NEVER, +) + # PyTuple_SET_ITEM does no error checking, # and should only be used to fill in brand new tuples. new_tuple_set_item_op = custom_op( From 8228b9eacc6a9963c573a08fa8924a189fbe53a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 03:20:13 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypyc/irbuild/ll_builder.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index bcb971a2ea45..777fb9b42bb6 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -183,7 +183,12 @@ str_ssize_t_size_op, unicode_compare, ) -from mypyc.primitives.tuple_ops import list_tuple_op, load_empty_tuple_constant_op, new_tuple_op, new_tuple_with_length_op +from mypyc.primitives.tuple_ops import ( + list_tuple_op, + load_empty_tuple_constant_op, + new_tuple_op, + new_tuple_with_length_op, +) from mypyc.rt_subtype import is_runtime_subtype from mypyc.sametype import is_same_type from mypyc.subtype import is_subtype From e02dbf3ffe0ad3620a4d076bd068ca82f6fd6448 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:44:22 -0400 Subject: [PATCH 3/7] Update irbuild-basic.test --- mypyc/test-data/irbuild-basic.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 4a7d315ec836..184d67380dde 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -1766,7 +1766,7 @@ L0: r10 = PyDict_New() r11 = CPyDict_UpdateInDisplay(r10, r6) r12 = r11 >= 0 :: signed - r13 = PyTuple_Pack(0) + r13 = _CPyTuple_LoadEmptyTupleConstant() r14 = PyObject_Call(r9, r13, r10) r15 = unbox(tuple[int, int, int], r14) return r15 From 42c6d1664e2395abd2e0ec413fc7d47d41927c37 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:44:43 -0400 Subject: [PATCH 4/7] Update irbuild-classes.test --- mypyc/test-data/irbuild-classes.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 1a2c237cc3c9..915502dbbd1b 100644 --- a/mypyc/test-data/irbuild-classes.test +++ b/mypyc/test-data/irbuild-classes.test @@ -297,7 +297,7 @@ L2: r27 = CPyType_FromTemplate(r26, r24, r25) r28 = C_trait_vtable_setup() r29 = '__mypyc_attrs__' - r30 = PyTuple_Pack(0) + r30 = _CPyTuple_LoadEmptyTupleConstant() r31 = PyObject_SetAttr(r27, r29, r30) r32 = r31 >= 0 :: signed __main__.C = r27 :: type @@ -310,7 +310,7 @@ L2: r39 = __main__.S_template :: type r40 = CPyType_FromTemplate(r39, r37, r38) r41 = '__mypyc_attrs__' - r42 = PyTuple_Pack(0) + r42 = _CPyTuple_LoadEmptyTupleConstant() r43 = PyObject_SetAttr(r40, r41, r42) r44 = r43 >= 0 :: signed __main__.S = r40 :: type From 05db3902a46fc937908a25106c825b65bd61bf9c Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:50:23 -0400 Subject: [PATCH 5/7] Update tuple_ops.c --- mypyc/lib-rt/tuple_ops.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/mypyc/lib-rt/tuple_ops.c b/mypyc/lib-rt/tuple_ops.c index 1c0d694ae4bb..1df73f1907e2 100644 --- a/mypyc/lib-rt/tuple_ops.c +++ b/mypyc/lib-rt/tuple_ops.c @@ -5,9 +5,6 @@ #include #include "CPy.h" -// System-wide empty tuple constant -PyObject * __mypyc_empty_tuple__ = NULL; - PyObject *CPySequenceTuple_GetItem(PyObject *tuple, CPyTagged index) { if (CPyTagged_CheckShort(index)) { Py_ssize_t n = CPyTagged_ShortAsSsize_t(index); From 9c861aeda240b8982a74a1b603b1f4d81946d035 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:50:49 -0400 Subject: [PATCH 6/7] Update init.c --- mypyc/lib-rt/init.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypyc/lib-rt/init.c b/mypyc/lib-rt/init.c index 653b224ece9d..74137e13a980 100644 --- a/mypyc/lib-rt/init.c +++ b/mypyc/lib-rt/init.c @@ -4,6 +4,9 @@ struct ExcDummyStruct _CPy_ExcDummyStruct = { PyObject_HEAD_INIT(NULL) }; PyObject *_CPy_ExcDummy = (PyObject *)&_CPy_ExcDummyStruct; +// System-wide empty tuple constant +PyObject * __mypyc_empty_tuple__ = NULL; + // Because its dynamic linker is more restricted than linux/OS X, // Windows doesn't allow initializing globals with values from // other dynamic libraries. This means we need to initialize From 6ce0c9f4613655a8b6ebf6f9de40b4af291c3bb3 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:41:40 -0400 Subject: [PATCH 7/7] no refcounting --- mypyc/lib-rt/CPy.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index 33edcea51340..343f1028fd01 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -68,7 +68,8 @@ typedef struct tuple_T4CIOO { extern PyObject * __mypyc_empty_tuple__; static inline PyObject *_CPyTuple_LoadEmptyTupleConstant() { - Py_INCREF(__mypyc_empty_tuple__); + // do tests still pass if I comment this out? empty tuple singleton is not tracked by gc + // Py_INCREF(__mypyc_empty_tuple__); return __mypyc_empty_tuple__; }