diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h index 0f1af8ba338865..6a8fa124ba38a7 100644 --- a/Include/internal/pycore_magic_number.h +++ b/Include/internal/pycore_magic_number.h @@ -298,6 +298,7 @@ Known values: Python 3.15a8 3665 (Add FOR_ITER_VIRTUAL and GET_ITER specializations) Python 3.15b1 3666 (Add SEND_VIRTUAL and SEND_ASYNC_GEN specializations) Python 3.16a0 3700 (Initial version) + Python 3.16a0 3701 (Add CONSTANT_EMPTY_TUPLE to LOAD_COMMON_CONSTANT) Python 3.17 will start with 3750 @@ -311,7 +312,7 @@ PC/launcher.c must also be updated. */ -#define PYC_MAGIC_NUMBER 3700 +#define PYC_MAGIC_NUMBER 3701 /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes (little-endian) and then appending b'\r\n'. */ #define PYC_MAGIC_NUMBER_TOKEN \ diff --git a/Include/internal/pycore_opcode_utils.h b/Include/internal/pycore_opcode_utils.h index 3e2c4ae411c925..c5659b534d9908 100644 --- a/Include/internal/pycore_opcode_utils.h +++ b/Include/internal/pycore_opcode_utils.h @@ -80,7 +80,8 @@ extern "C" { #define CONSTANT_TRUE 9 #define CONSTANT_FALSE 10 #define CONSTANT_MINUS_ONE 11 -#define NUM_COMMON_CONSTANTS 12 +#define CONSTANT_EMPTY_TUPLE 12 +#define NUM_COMMON_CONSTANTS 13 /* Values used in the oparg for RESUME */ #define RESUME_AT_FUNC_START 0 diff --git a/Lib/dis.py b/Lib/dis.py index d60507ae473453..707068fa3d8a0c 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -698,8 +698,8 @@ def _get_const_value(op, arg, co_consts): if op == LOAD_SMALL_INT: return arg if op == LOAD_COMMON_CONSTANT: - # Opargs 0-6 are callables; 7-11 are literal values. - if 7 <= arg <= 11: + # Opargs 0-6 are callables; 7-12 are literal values. + if 7 <= arg <= 12: return _common_constants[arg] return UNKNOWN argval = UNKNOWN diff --git a/Lib/opcode.py b/Lib/opcode.py index 4e60fb5af34f22..ea2b12d19ed685 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -44,7 +44,7 @@ builtins.set, # Append-only — must match CONSTANT_* in # Include/internal/pycore_opcode_utils.h. - None, "", True, False, -1] + None, "", True, False, -1, ()] _nb_ops = _opcode.get_nb_ops() hascompare = [opmap["COMPARE_OP"]] diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index d44e61f25f7f55..a48427e5a432ec 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1307,10 +1307,10 @@ def test_build_empty_tuple(self): ('RETURN_VALUE', None, 0), ] after = [ - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', opcode._common_constants.index(()), 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(before, after, consts=[], expected_consts=[()]) + self.cfg_optimization_test(before, after, consts=[], expected_consts=[]) def test_fold_tuple_of_constants(self): before = [ @@ -1365,10 +1365,10 @@ def test_fold_constant_intrinsic_list_to_tuple(self): ('RETURN_VALUE', None, 0) ] after = [ - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', opcode._common_constants.index(()), 0), ('RETURN_VALUE', None, 0) ] - self.cfg_optimization_test(before, after, consts=[], expected_consts=[()]) + self.cfg_optimization_test(before, after, consts=[], expected_consts=[]) # multiple BUILD_LIST 0: ([], 1, [], 2) same = [ diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-18-20-41.gh-issue-148871.AeCbq7.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-18-20-41.gh-issue-148871.AeCbq7.rst new file mode 100644 index 00000000000000..506e369f553e50 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-18-20-41.gh-issue-148871.AeCbq7.rst @@ -0,0 +1,3 @@ +The empty tuple ``()`` is now loaded via :opcode:`LOAD_COMMON_CONSTANT` +instead of :opcode:`LOAD_CONST`, removing it from per-code-object +:attr:`~codeobject.co_consts` tuples. diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 224426b7aa44bc..6e3e5378cfbf15 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1468,6 +1468,10 @@ maybe_instr_make_load_common_const(cfg_instr *instr, PyObject *newconst) && PyUnicode_GET_LENGTH(newconst) == 0) { oparg = CONSTANT_EMPTY_STR; } + else if (PyTuple_CheckExact(newconst) + && PyTuple_GET_SIZE(newconst) == 0) { + oparg = CONSTANT_EMPTY_TUPLE; + } else if (PyLong_CheckExact(newconst)) { int overflow; long val = PyLong_AsLongAndOverflow(newconst, &overflow); diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 46579a45f4cc39..9db3ad473f47d5 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -892,6 +892,8 @@ pycore_init_builtins(PyThreadState *tstate) interp->common_consts[CONSTANT_FALSE] = Py_False; interp->common_consts[CONSTANT_MINUS_ONE] = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS - 1]; + interp->common_consts[CONSTANT_EMPTY_TUPLE] = + Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_TUPLE); for (int i = 0; i < NUM_COMMON_CONSTANTS; i++) { assert(interp->common_consts[i] != NULL); }