diff --git a/mypyc/codegen/emit.py b/mypyc/codegen/emit.py index 9ca761bd8ac5..4ef53296ef0d 100644 --- a/mypyc/codegen/emit.py +++ b/mypyc/codegen/emit.py @@ -1036,17 +1036,21 @@ def emit_box( self.emit_line(f"{declaration}{dest} = PyFloat_FromDouble({src});") elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) - self.emit_line(f"{declaration}{dest} = PyTuple_New({len(typ.types)});") - self.emit_line(f"if (unlikely({dest} == NULL))") - self.emit_line(" CPyError_OutOfMemory();") - # TODO: Fail if dest is None - for i in range(len(typ.types)): - if not typ.is_unboxed: - self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {src}.f{i}") - else: - inner_name = self.temp_name() - self.emit_box(f"{src}.f{i}", inner_name, typ.types[i], declare_dest=True) - self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {inner_name});") + if not typ.types: + self.emit_line(f"{declaration}{dest} = CPyTuple_LoadEmptyTupleConstant();") + else: + self.emit_line(f"{declaration}{dest} = PyTuple_New({len(typ.types)});") + self.emit_line(f"if (unlikely({dest} == NULL))") + self.emit_line(" CPyError_OutOfMemory();") + + # TODO: Fail if dest is None + for i in range(len(typ.types)): + if not typ.is_unboxed: + self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {src}.f{i}") + else: + inner_name = self.temp_name() + self.emit_box(f"{src}.f{i}", inner_name, typ.types[i], declare_dest=True) + self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {inner_name});") else: assert not typ.is_unboxed # Type is boxed -- trivially just assign. 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..b4d3a0013ae7 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -64,6 +64,15 @@ typedef struct tuple_T4CIOO { } tuple_T4CIOO; #endif +// System-wide empty tuple constant +extern PyObject * __mypyc_empty_tuple__; + +static inline PyObject *CPyTuple_LoadEmptyTupleConstant() { +#if !CPY_3_12_FEATURES + Py_INCREF(__mypyc_empty_tuple__); +#endif + return __mypyc_empty_tuple__; +} // Native object operations diff --git a/mypyc/lib-rt/init.c b/mypyc/lib-rt/init.c index 01b133233489..9215c2d59019 100644 --- a/mypyc/lib-rt/init.c +++ b/mypyc/lib-rt/init.c @@ -4,10 +4,21 @@ 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 // 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__) { + CPyError_OutOfMemory(); + } + } } diff --git a/mypyc/primitives/tuple_ops.py b/mypyc/primitives/tuple_ops.py index f262dec8b05a..ab23f8c441f5 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( diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 63e4ef55d3fc..ea48b3cc2e01 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -1755,7 +1755,7 @@ L0: r7 = __main__.globals :: static r8 = 'f' r9 = CPyDict_GetItem(r7, r8) - r10 = PyTuple_Pack(0) + r10 = CPyTuple_LoadEmptyTupleConstant() r11 = PyDict_Copy(r6) r12 = PyObject_Call(r9, r10, r11) r13 = unbox(tuple[int, int, int], r12) diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test index 68bc18c7bdeb..cd39a8b1c0c4 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