From 78c9c5481298819fb36b956d0949b455732bf074 Mon Sep 17 00:00:00 2001 From: BobTheBuidler Date: Thu, 14 Aug 2025 03:17:40 +0000 Subject: [PATCH 1/6] [mypyc] feat: `__mypyc_empty_tuple__` constant --- mypyc/irbuild/ll_builder.py | 8 ++++++-- 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(+), 2 deletions(-) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 116a1bb4bae0..8f0d11b20cb6 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -188,6 +188,7 @@ ) from mypyc.primitives.tuple_ops import ( list_tuple_op, + load_empty_tuple_constant_op, new_tuple_op, new_tuple_with_length_op, sequence_tuple_op, @@ -2359,8 +2360,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 8cd141545bbb..c51cd3117c32 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 f262dec8b05a..e8da5b503624 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 ed00a0a4a807703b33ea93c66abad922197e790b 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 2/6] Update irbuild-basic.test --- mypyc/test-data/irbuild-basic.test | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 63e4ef55d3fc..67f5758acd71 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -1755,11 +1755,13 @@ L0: r7 = __main__.globals :: static r8 = 'f' r9 = CPyDict_GetItem(r7, r8) - r10 = PyTuple_Pack(0) - r11 = PyDict_Copy(r6) - r12 = PyObject_Call(r9, r10, r11) - r13 = unbox(tuple[int, int, int], r12) - return r13 + r10 = PyDict_New() + r11 = CPyDict_UpdateInDisplay(r10, r6) + r12 = r11 >= 0 :: signed + r13 = _CPyTuple_LoadEmptyTupleConstant() + r14 = PyObject_Call(r9, r13, r10) + r15 = unbox(tuple[int, int, int], r14) + return r15 def h(): r0, r1 :: str r2, r3 :: object From 30eaf791826446c195f9a68a6a729cf9bc961494 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 3/6] 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 68bc18c7bdeb..5a983bc134d3 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 e8589908d42bd7057db9dd592a77186d8b2ba7b7 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 4/6] 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 c417c5568819f8aeca70a14558fca8862ef701ad 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 5/6] 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 fecc89c59c4953961805bdd6639c8cf4982e1fb5 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 6/6] 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 c51cd3117c32..dfa4381ee4b0 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__; }