https://github.com/python/cpython/commit/39bd44fc7091dde09abfdc27af2559c1fb52c17d
commit: 39bd44fc7091dde09abfdc27af2559c1fb52c17d
branch: main
author: Neko Asakura <[email protected]>
committer: markshannon <[email protected]>
date: 2026-05-28T12:27:37+01:00
summary:
gh-148871: make `LOAD_COMMON_CONSTANT` use immortal stackref borrows (GH-149625)
files:
M Include/internal/pycore_interp_structs.h
M Include/internal/pycore_stackref.h
M Modules/_testinternalcapi/test_cases.c.h
M Python/bytecodes.c
M Python/executor_cases.c.h
M Python/flowgraph.c
M Python/generated_cases.c.h
M Python/optimizer_bytecodes.c
M Python/optimizer_cases.c.h
M Python/pylifecycle.c
M Python/pystate.c
M Tools/cases_generator/analyzer.py
diff --git a/Include/internal/pycore_interp_structs.h
b/Include/internal/pycore_interp_structs.h
index f13bc2178b1e7eb..4c8ad11447aa591 100644
--- a/Include/internal/pycore_interp_structs.h
+++ b/Include/internal/pycore_interp_structs.h
@@ -1001,7 +1001,7 @@ struct _is {
struct ast_state ast;
struct types_state types;
struct callable_cache callable_cache;
- PyObject *common_consts[NUM_COMMON_CONSTANTS];
+ _PyStackRef common_consts[NUM_COMMON_CONSTANTS];
bool jit;
bool compiling;
diff --git a/Include/internal/pycore_stackref.h
b/Include/internal/pycore_stackref.h
index ca4a7c216eda532..9495ccc8ac38896 100644
--- a/Include/internal/pycore_stackref.h
+++ b/Include/internal/pycore_stackref.h
@@ -263,6 +263,18 @@ _PyStackRef_DUP(_PyStackRef ref, const char *filename, int
linenumber)
}
#define PyStackRef_DUP(REF) _PyStackRef_DUP(REF, __FILE__, __LINE__)
+static inline _PyStackRef
+_PyStackRef_DupImmortal(_PyStackRef ref, const char *filename, int linenumber)
+{
+ assert(!PyStackRef_IsError(ref));
+ assert(!PyStackRef_IsTaggedInt(ref));
+ assert(!PyStackRef_RefcountOnObject(ref));
+ PyObject *obj = _Py_stackref_get_object(ref);
+ assert(_Py_IsImmortal(obj));
+ return _Py_stackref_create(obj, Py_TAG_REFCNT, filename, linenumber);
+}
+#define PyStackRef_DupImmortal(REF) _PyStackRef_DupImmortal((REF), __FILE__,
__LINE__)
+
static inline void
_PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct, const char
*filename, int linenumber)
{
@@ -633,6 +645,15 @@ PyStackRef_DUP(_PyStackRef ref)
}
#endif
+static inline _PyStackRef
+PyStackRef_DupImmortal(_PyStackRef ref)
+{
+ assert(!PyStackRef_IsNull(ref));
+ assert(!PyStackRef_RefcountOnObject(ref));
+ assert(_Py_IsImmortal(BITS_TO_PTR_MASKED(ref)));
+ return ref;
+}
+
static inline bool
PyStackRef_IsHeapSafe(_PyStackRef ref)
{
diff --git a/Modules/_testinternalcapi/test_cases.c.h
b/Modules/_testinternalcapi/test_cases.c.h
index b463bb18b160564..11dfcc68eb2dacd 100644
--- a/Modules/_testinternalcapi/test_cases.c.h
+++ b/Modules/_testinternalcapi/test_cases.c.h
@@ -9314,7 +9314,7 @@
INSTRUCTION_STATS(LOAD_COMMON_CONSTANT);
_PyStackRef value;
assert(oparg < NUM_COMMON_CONSTANTS);
- value =
PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
+ value =
PyStackRef_DupImmortal(tstate->interp->common_consts[oparg]);
stack_pointer[0] = value;
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 300b7da753c2baf..993d231751409ba 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -1974,7 +1974,7 @@ dummy_func(
inst(LOAD_COMMON_CONSTANT, ( -- value)) {
// Keep in sync with _common_constants in opcode.py
assert(oparg < NUM_COMMON_CONSTANTS);
- value =
PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
+ value =
PyStackRef_DupImmortal(tstate->interp->common_consts[oparg]);
}
inst(LOAD_BUILD_CLASS, ( -- bc)) {
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 952860f01b8a682..9aaf9639b9b9015 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -9396,7 +9396,7 @@
_PyStackRef value;
oparg = CURRENT_OPARG();
assert(oparg < NUM_COMMON_CONSTANTS);
- value =
PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
+ value =
PyStackRef_DupImmortal(tstate->interp->common_consts[oparg]);
_tos_cache0 = value;
SET_CURRENT_CACHED_VALUES(1);
assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
@@ -9410,7 +9410,7 @@
_PyStackRef _stack_item_0 = _tos_cache0;
oparg = CURRENT_OPARG();
assert(oparg < NUM_COMMON_CONSTANTS);
- value =
PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
+ value =
PyStackRef_DupImmortal(tstate->interp->common_consts[oparg]);
_tos_cache1 = value;
_tos_cache0 = _stack_item_0;
SET_CURRENT_CACHED_VALUES(2);
@@ -9426,7 +9426,7 @@
_PyStackRef _stack_item_1 = _tos_cache1;
oparg = CURRENT_OPARG();
assert(oparg < NUM_COMMON_CONSTANTS);
- value =
PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
+ value =
PyStackRef_DupImmortal(tstate->interp->common_consts[oparg]);
_tos_cache2 = value;
_tos_cache1 = _stack_item_1;
_tos_cache0 = _stack_item_0;
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index 6e3e5378cfbf155..eb0faf8cd183885 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -11,6 +11,7 @@
#include "pycore_opcode_utils.h"
#include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc
#include "pycore_pystate.h" // _PyInterpreterState_GET()
+#include "pycore_stackref.h" // PyStackRef_AsPyObjectBorrow()
#include <stdbool.h>
@@ -1330,7 +1331,8 @@ get_const_value(int opcode, int oparg, PyObject
*co_consts)
}
if (opcode == LOAD_COMMON_CONSTANT) {
assert(oparg < NUM_COMMON_CONSTANTS);
- return Py_NewRef(_PyInterpreterState_GET()->common_consts[oparg]);
+ return PyStackRef_AsPyObjectBorrow(
+ _PyInterpreterState_GET()->common_consts[oparg]);
}
if (constant == NULL) {
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index 83051cf41cc043b..94384d5db3c107f 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -9313,7 +9313,7 @@
INSTRUCTION_STATS(LOAD_COMMON_CONSTANT);
_PyStackRef value;
assert(oparg < NUM_COMMON_CONSTANTS);
- value =
PyStackRef_FromPyObjectNew(tstate->interp->common_consts[oparg]);
+ value =
PyStackRef_DupImmortal(tstate->interp->common_consts[oparg]);
stack_pointer[0] = value;
stack_pointer += 1;
ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c
index c968185d77c3317..edb4c644bccbf6f 100644
--- a/Python/optimizer_bytecodes.c
+++ b/Python/optimizer_bytecodes.c
@@ -2,6 +2,7 @@
#include "pycore_long.h"
#include "pycore_opcode_utils.h"
#include "pycore_optimizer.h"
+#include "pycore_stackref.h"
#include "pycore_typeobject.h"
#include "pycore_uops.h"
#include "pycore_uop_ids.h"
@@ -870,15 +871,11 @@ dummy_func(void) {
op(_LOAD_COMMON_CONSTANT, (-- value)) {
assert(oparg < NUM_COMMON_CONSTANTS);
- PyObject *val = _PyInterpreterState_GET()->common_consts[oparg];
- if (_Py_IsImmortal(val)) {
- ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val);
- value = PyJitRef_Borrow(sym_new_const(ctx, val));
- }
- else {
- ADD_OP(_LOAD_CONST_INLINE, 0, (uintptr_t)val);
- value = sym_new_const(ctx, val);
- }
+ PyObject *val = PyStackRef_AsPyObjectBorrow(
+ _PyInterpreterState_GET()->common_consts[oparg]);
+ assert(_Py_IsImmortal(val));
+ ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val);
+ value = PyJitRef_Borrow(sym_new_const(ctx, val));
}
op(_LOAD_SMALL_INT, (-- value)) {
diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h
index d52ebb9804197da..8895e02d47b1693 100644
--- a/Python/optimizer_cases.c.h
+++ b/Python/optimizer_cases.c.h
@@ -1991,15 +1991,11 @@
case _LOAD_COMMON_CONSTANT: {
JitOptRef value;
assert(oparg < NUM_COMMON_CONSTANTS);
- PyObject *val = _PyInterpreterState_GET()->common_consts[oparg];
- if (_Py_IsImmortal(val)) {
- ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val);
- value = PyJitRef_Borrow(sym_new_const(ctx, val));
- }
- else {
- ADD_OP(_LOAD_CONST_INLINE, 0, (uintptr_t)val);
- value = sym_new_const(ctx, val);
- }
+ PyObject *val = PyStackRef_AsPyObjectBorrow(
+ _PyInterpreterState_GET()->common_consts[oparg]);
+ assert(_Py_IsImmortal(val));
+ ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)val);
+ value = PyJitRef_Borrow(sym_new_const(ctx, val));
CHECK_STACK_BOUNDS(1);
stack_pointer[0] = value;
stack_pointer += 1;
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index a17c3baca3d006c..182233329498076 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -28,6 +28,7 @@
#include "pycore_runtime.h" // _Py_ID()
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
#include "pycore_setobject.h" // _PySet_NextEntry()
+#include "pycore_stackref.h" // PyStackRef_FromPyObjectBorrow()
#include "pycore_stats.h" // _PyStats_InterpInit()
#include "pycore_sysmodule.h" // _PySys_ClearAttrString()
#include "pycore_traceback.h" // PyUnstable_TracebackThreads()
@@ -878,25 +879,28 @@ pycore_init_builtins(PyThreadState *tstate)
goto error;
}
- interp->common_consts[CONSTANT_ASSERTIONERROR] = PyExc_AssertionError;
- interp->common_consts[CONSTANT_NOTIMPLEMENTEDERROR] =
PyExc_NotImplementedError;
- interp->common_consts[CONSTANT_BUILTIN_TUPLE] = (PyObject *)&PyTuple_Type;
- interp->common_consts[CONSTANT_BUILTIN_ALL] = all;
- interp->common_consts[CONSTANT_BUILTIN_ANY] = any;
- interp->common_consts[CONSTANT_BUILTIN_LIST] = (PyObject *)&PyList_Type;
- interp->common_consts[CONSTANT_BUILTIN_SET] = (PyObject *)&PySet_Type;
- interp->common_consts[CONSTANT_NONE] = Py_None;
- interp->common_consts[CONSTANT_EMPTY_STR] =
+ PyObject *common_objs[NUM_COMMON_CONSTANTS] = {NULL};
+ common_objs[CONSTANT_ASSERTIONERROR] = PyExc_AssertionError;
+ common_objs[CONSTANT_NOTIMPLEMENTEDERROR] = PyExc_NotImplementedError;
+ common_objs[CONSTANT_BUILTIN_TUPLE] = (PyObject *)&PyTuple_Type;
+ common_objs[CONSTANT_BUILTIN_ALL] = all;
+ common_objs[CONSTANT_BUILTIN_ANY] = any;
+ common_objs[CONSTANT_BUILTIN_LIST] = (PyObject *)&PyList_Type;
+ common_objs[CONSTANT_BUILTIN_SET] = (PyObject *)&PySet_Type;
+ common_objs[CONSTANT_NONE] = Py_None;
+ common_objs[CONSTANT_EMPTY_STR] =
Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_STR);
- interp->common_consts[CONSTANT_TRUE] = Py_True;
- interp->common_consts[CONSTANT_FALSE] = Py_False;
- interp->common_consts[CONSTANT_MINUS_ONE] =
+ common_objs[CONSTANT_TRUE] = Py_True;
+ common_objs[CONSTANT_FALSE] = Py_False;
+ common_objs[CONSTANT_MINUS_ONE] =
(PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS - 1];
- interp->common_consts[CONSTANT_BUILTIN_FROZENSET] = (PyObject
*)&PyFrozenSet_Type;
- interp->common_consts[CONSTANT_EMPTY_TUPLE] =
+ common_objs[CONSTANT_BUILTIN_FROZENSET] = (PyObject *)&PyFrozenSet_Type;
+ common_objs[CONSTANT_EMPTY_TUPLE] =
Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_TUPLE);
for (int i = 0; i < NUM_COMMON_CONSTANTS; i++) {
- assert(interp->common_consts[i] != NULL);
+ assert(common_objs[i] != NULL);
+ _Py_SetImmortal(common_objs[i]);
+ interp->common_consts[i] =
PyStackRef_FromPyObjectBorrow(common_objs[i]);
}
PyObject *list_append = _PyType_Lookup(&PyList_Type, &_Py_ID(append));
diff --git a/Python/pystate.c b/Python/pystate.c
index 530bd567b770be3..fed1df0173bacf1 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -12,8 +12,9 @@
#include "pycore_freelist.h" // _PyObject_ClearFreeLists()
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_interpframe.h" // _PyThreadState_HasStackSpace()
-#include "pycore_object.h" // _PyType_InitCache()
+#include "pycore_object.h" // _PyType_InitCache(), _Py_ClearImmortal()
#include "pycore_obmalloc.h" // _PyMem_obmalloc_state_on_heap()
+#include "pycore_opcode_utils.h" // NUM_COMMON_CONSTANTS
#include "pycore_optimizer.h" // JIT_CLEANUP_THRESHOLD
#include "pycore_parking_lot.h" // _PyParkingLot_AfterFork()
#include "pycore_pyerrors.h" // _PyErr_Clear()
@@ -21,7 +22,7 @@
#include "pycore_pymem.h" // _PyMem_DebugEnabled()
#include "pycore_runtime.h" // _PyRuntime
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
-#include "pycore_stackref.h" // Py_STACKREF_DEBUG
+#include "pycore_stackref.h" // PyStackRef_AsPyObjectBorrow()
#include "pycore_stats.h" // FT_STAT_WORLD_STOP_INC()
#include "pycore_time.h" // _PyTime_Init()
#include "pycore_uniqueid.h" // _PyObject_FinalizePerThreadRefcounts()
@@ -778,6 +779,36 @@ extern void
_Py_stackref_report_leaks(PyInterpreterState *interp);
#endif
+static int
+common_const_is_initialized(_PyStackRef ref)
+{
+#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
+ return !PyStackRef_IsNull(ref);
+#else
+ return ref.bits != 0 && !PyStackRef_IsNull(ref);
+#endif
+}
+
+
+static void
+common_constants_clear(PyInterpreterState *interp)
+{
+ for (int i = 0; i < NUM_COMMON_CONSTANTS; i++) {
+ _PyStackRef ref = interp->common_consts[i];
+ if (!common_const_is_initialized(ref)) {
+ continue;
+ }
+ PyObject *obj = PyStackRef_AsPyObjectBorrow(ref);
+ PyStackRef_XCLOSE(ref);
+ interp->common_consts[i] = PyStackRef_NULL;
+ // Refcount reclamation skips heap immortals; release manually.
+ if (_Py_IsImmortal(obj) && !_Py_IsStaticImmortal(obj)) {
+ _Py_ClearImmortal(obj);
+ }
+ }
+}
+
+
static void
interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
{
@@ -904,6 +935,7 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState
*tstate)
PyDict_Clear(interp->builtins);
Py_CLEAR(interp->sysdict);
Py_CLEAR(interp->builtins);
+ common_constants_clear(interp);
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
# ifdef Py_STACKREF_CLOSE_DEBUG
diff --git a/Tools/cases_generator/analyzer.py
b/Tools/cases_generator/analyzer.py
index 42459eedad6b1d7..22a321b4953de7d 100644
--- a/Tools/cases_generator/analyzer.py
+++ b/Tools/cases_generator/analyzer.py
@@ -609,6 +609,7 @@ def has_error_without_pop(op: parser.CodeDef) -> bool:
"PyStackRef_CLEAR",
"PyStackRef_CLOSE_SPECIALIZED",
"PyStackRef_DUP",
+ "PyStackRef_DupImmortal",
"PyStackRef_False",
"PyStackRef_FromPyObjectBorrow",
"PyStackRef_FromPyObjectNew",
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]