From a73ba4d46e4106aed29dd73a7698abd01ff94b07 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 15 Jan 2026 09:52:29 +0900 Subject: [PATCH 1/3] gh-143632: Skip unittest for mmap.set_name at musl environment (gh-143839) --- Lib/test/test_mmap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 48bf246cadd2f8..177fe45e8d9749 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -1183,6 +1183,7 @@ def test_flush_parameters(self): @unittest.skipUnless(sys.platform == 'linux', 'Linux only') @support.requires_linux_version(5, 17, 0) + @unittest.skipIf(support.linked_to_musl(), "musl libc issue, gh-143632") def test_set_name(self): # Test setting name on anonymous mmap m = mmap.mmap(-1, PAGESIZE) From 794f758cd862fbe1872545d73ddc3fc7067272fb Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 15 Jan 2026 09:53:00 +0900 Subject: [PATCH 2/3] gh-141504: Refactor policy object into a single opt_config (gh-143644) --- Include/internal/pycore_backoff.h | 14 ++--- Include/internal/pycore_interp_structs.h | 18 ++++++ Include/internal/pycore_tstate.h | 16 ----- Lib/test/test_capi/test_opt.py | 2 +- Objects/codeobject.c | 16 ++--- Python/optimizer.c | 7 ++- Python/pystate.c | 76 +++++++++++++++--------- Python/specialize.c | 4 +- 8 files changed, 89 insertions(+), 64 deletions(-) diff --git a/Include/internal/pycore_backoff.h b/Include/internal/pycore_backoff.h index fadd11f04ec5ca..ee907ae0534e4f 100644 --- a/Include/internal/pycore_backoff.h +++ b/Include/internal/pycore_backoff.h @@ -12,7 +12,7 @@ extern "C" { #include #include #include "pycore_structs.h" // _Py_BackoffCounter -#include "pycore_tstate.h" // _PyPolicy +#include "pycore_interp_structs.h" // _PyOptimizationConfig /* 16-bit countdown counters using exponential backoff. @@ -128,11 +128,11 @@ trigger_backoff_counter(void) #define JUMP_BACKWARD_INITIAL_VALUE 4000 #define JUMP_BACKWARD_INITIAL_BACKOFF 6 static inline _Py_BackoffCounter -initial_jump_backoff_counter(_PyPolicy *policy) +initial_jump_backoff_counter(_PyOptimizationConfig *opt_config) { return make_backoff_counter( - policy->interp.jump_backward_initial_value, - policy->interp.jump_backward_initial_backoff); + opt_config->jump_backward_initial_value, + opt_config->jump_backward_initial_backoff); } /* Initial exit temperature. @@ -143,11 +143,11 @@ initial_jump_backoff_counter(_PyPolicy *policy) #define SIDE_EXIT_INITIAL_BACKOFF 6 static inline _Py_BackoffCounter -initial_temperature_backoff_counter(_PyPolicy *policy) +initial_temperature_backoff_counter(_PyOptimizationConfig *opt_config) { return make_backoff_counter( - policy->jit.side_exit_initial_value, - policy->jit.side_exit_initial_backoff); + opt_config->side_exit_initial_value, + opt_config->side_exit_initial_backoff); } /* Unreachable backoff counter. */ diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 3fe1fdaa1589b6..f11448b06696ad 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -398,6 +398,21 @@ typedef struct _rare_events { uint8_t func_modification; } _rare_events; +// Optimization configuration for the interpreter. +// This groups all thresholds and optimization flags for both JIT and interpreter. +typedef struct _PyOptimizationConfig { + // Interpreter optimization thresholds + uint16_t jump_backward_initial_value; + uint16_t jump_backward_initial_backoff; + + // JIT optimization thresholds + uint16_t side_exit_initial_value; + uint16_t side_exit_initial_backoff; + + // Optimization flags + bool specialization_enabled; +} _PyOptimizationConfig; + struct Bigint { struct Bigint *next; @@ -945,6 +960,9 @@ struct _is { PyObject *common_consts[NUM_COMMON_CONSTANTS]; bool jit; bool compiling; + + // Optimization configuration (thresholds and flags for JIT and interpreter) + _PyOptimizationConfig opt_config; struct _PyExecutorObject *executor_list_head; struct _PyExecutorObject *executor_deletion_list_head; struct _PyExecutorObject *cold_executor; diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 25f4f6ed7078df..dc4444717b4e87 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -64,21 +64,6 @@ typedef struct _PyJitTracerState { #endif -typedef struct _PyJitPolicy { - uint16_t side_exit_initial_value; - uint16_t side_exit_initial_backoff; -} _PyJitPolicy; - -typedef struct _PyInterpreterPolicy { - uint16_t jump_backward_initial_value; - uint16_t jump_backward_initial_backoff; -} _PyInterpreterPolicy; - -typedef struct _PyPolicy { - _PyJitPolicy jit; - _PyInterpreterPolicy interp; -} _PyPolicy; - // Every PyThreadState is actually allocated as a _PyThreadStateImpl. The // PyThreadState fields are exposed as part of the C API, although most fields // are intended to be private. The _PyThreadStateImpl fields not exposed. @@ -157,7 +142,6 @@ typedef struct _PyThreadStateImpl { #if _Py_TIER2 _PyJitTracerState *jit_tracer_state; #endif - _PyPolicy policy; } _PyThreadStateImpl; #ifdef __cplusplus diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index c0aecb8d841224..79c7f530b8ae89 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3789,7 +3789,7 @@ def __next__(self): pass f1() - """), PYTHON_JIT="1", PYTHON_JIT_JUMP_BACKWARD_INITIAL_VALUE="64") + """), PYTHON_JIT="1", PYTHON_JIT_STRESS="1") self.assertEqual(result[0].rc, 0, result) def global_identity(x): diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 3aea2038fd17e7..ed3cc41480ab5c 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -580,9 +580,10 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con) } co->_co_firsttraceable = entry_point; #ifdef Py_GIL_DISABLED - _PyCode_Quicken(_PyCode_CODE(co), Py_SIZE(co), interp->config.tlbc_enabled); + int enable_counters = interp->config.tlbc_enabled && interp->opt_config.specialization_enabled; + _PyCode_Quicken(_PyCode_CODE(co), Py_SIZE(co), enable_counters); #else - _PyCode_Quicken(_PyCode_CODE(co), Py_SIZE(co), 1); + _PyCode_Quicken(_PyCode_CODE(co), Py_SIZE(co), interp->opt_config.specialization_enabled); #endif notify_code_watchers(PY_CODE_EVENT_CREATE, co); return 0; @@ -3369,13 +3370,13 @@ deopt_code_unit(PyCodeObject *code, int i) } static void -copy_code(_Py_CODEUNIT *dst, PyCodeObject *co) +copy_code(PyInterpreterState *interp, _Py_CODEUNIT *dst, PyCodeObject *co) { int code_len = (int) Py_SIZE(co); for (int i = 0; i < code_len; i += _PyInstruction_GetLength(co, i)) { dst[i] = deopt_code_unit(co, i); } - _PyCode_Quicken(dst, code_len, 1); + _PyCode_Quicken(dst, code_len, interp->opt_config.specialization_enabled); } static Py_ssize_t @@ -3391,7 +3392,7 @@ get_pow2_greater(Py_ssize_t initial, Py_ssize_t limit) } static _Py_CODEUNIT * -create_tlbc_lock_held(PyCodeObject *co, Py_ssize_t idx) +create_tlbc_lock_held(PyInterpreterState *interp, PyCodeObject *co, Py_ssize_t idx) { _PyCodeArray *tlbc = co->co_tlbc; if (idx >= tlbc->size) { @@ -3414,7 +3415,7 @@ create_tlbc_lock_held(PyCodeObject *co, Py_ssize_t idx) PyErr_NoMemory(); return NULL; } - copy_code((_Py_CODEUNIT *) bc, co); + copy_code(interp, (_Py_CODEUNIT *) bc, co); assert(tlbc->entries[idx] == NULL); tlbc->entries[idx] = bc; return (_Py_CODEUNIT *) bc; @@ -3429,7 +3430,8 @@ get_tlbc_lock_held(PyCodeObject *co) if (idx < tlbc->size && tlbc->entries[idx] != NULL) { return (_Py_CODEUNIT *)tlbc->entries[idx]; } - return create_tlbc_lock_held(co, idx); + PyInterpreterState *interp = tstate->base.interp; + return create_tlbc_lock_held(interp, co, idx); } _Py_CODEUNIT * diff --git a/Python/optimizer.c b/Python/optimizer.c index c5f47d7f22c3f0..9892a9731e24f0 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1105,7 +1105,7 @@ _PyJit_FinalizeTracing(PyThreadState *tstate, int err) tracer->initial_state.jump_backward_instr[1].counter = restart_backoff_counter(counter); } else { - tracer->initial_state.jump_backward_instr[1].counter = initial_jump_backoff_counter(&_tstate->policy); + tracer->initial_state.jump_backward_instr[1].counter = initial_jump_backoff_counter(&tstate->interp->opt_config); } } else if (tracer->initial_state.executor->vm_data.valid) { @@ -1115,7 +1115,7 @@ _PyJit_FinalizeTracing(PyThreadState *tstate, int err) exit->temperature = restart_backoff_counter(exit->temperature); } else { - exit->temperature = initial_temperature_backoff_counter(&_tstate->policy); + exit->temperature = initial_temperature_backoff_counter(&tstate->interp->opt_config); } } Py_CLEAR(tracer->initial_state.code); @@ -1384,9 +1384,10 @@ make_executor_from_uops(_PyThreadStateImpl *tstate, _PyUOpInstruction *buffer, i _PyExecutorObject *cold = _PyExecutor_GetColdExecutor(); _PyExecutorObject *cold_dynamic = _PyExecutor_GetColdDynamicExecutor(); cold->vm_data.chain_depth = chain_depth; + PyInterpreterState *interp = tstate->base.interp; for (int i = 0; i < exit_count; i++) { executor->exits[i].index = i; - executor->exits[i].temperature = initial_temperature_backoff_counter(&tstate->policy); + executor->exits[i].temperature = initial_temperature_backoff_counter(&interp->opt_config); } int next_exit = exit_count-1; _PyUOpInstruction *dest = (_PyUOpInstruction *)&executor->trace[length]; diff --git a/Python/pystate.c b/Python/pystate.c index ebe56b8f32c06b..86dee70734a097 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -514,6 +514,28 @@ _Py_LazyJitShim( main interpreter. We fix those fields here, in addition to the other dynamically initialized fields. */ + +static inline bool +is_env_enabled(const char *env_name) +{ + char *env = Py_GETENV(env_name); + return env && *env != '\0' && *env != '0'; +} + +static inline void +init_policy(uint16_t *target, const char *env_name, uint16_t default_value, + long min_value, long max_value) +{ + *target = default_value; + char *env = Py_GETENV(env_name); + if (env && *env != '\0') { + long value = atol(env); + if (value >= min_value && value <= max_value) { + *target = (uint16_t)value; + } + } +} + static PyStatus init_interpreter(PyInterpreterState *interp, _PyRuntimeState *runtime, int64_t id, @@ -572,6 +594,31 @@ init_interpreter(PyInterpreterState *interp, interp->executor_list_head = NULL; interp->executor_deletion_list_head = NULL; interp->executor_creation_counter = JIT_CLEANUP_THRESHOLD; + + // Initialize optimization configuration from environment variables + // PYTHON_JIT_STRESS sets aggressive defaults for testing, but can be overridden + uint16_t jump_default = JUMP_BACKWARD_INITIAL_VALUE; + uint16_t side_exit_default = SIDE_EXIT_INITIAL_VALUE; + + if (is_env_enabled("PYTHON_JIT_STRESS")) { + jump_default = 63; + side_exit_default = 63; + } + + init_policy(&interp->opt_config.jump_backward_initial_value, + "PYTHON_JIT_JUMP_BACKWARD_INITIAL_VALUE", + jump_default, 1, MAX_VALUE); + init_policy(&interp->opt_config.jump_backward_initial_backoff, + "PYTHON_JIT_JUMP_BACKWARD_INITIAL_BACKOFF", + JUMP_BACKWARD_INITIAL_BACKOFF, 0, MAX_BACKOFF); + init_policy(&interp->opt_config.side_exit_initial_value, + "PYTHON_JIT_SIDE_EXIT_INITIAL_VALUE", + side_exit_default, 1, MAX_VALUE); + init_policy(&interp->opt_config.side_exit_initial_backoff, + "PYTHON_JIT_SIDE_EXIT_INITIAL_BACKOFF", + SIDE_EXIT_INITIAL_BACKOFF, 0, MAX_BACKOFF); + + interp->opt_config.specialization_enabled = !is_env_enabled("PYTHON_SPECIALIZATION_OFF"); if (interp != &runtime->_main_interpreter) { /* Fix the self-referential, statically initialized fields. */ interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp); @@ -1439,20 +1486,6 @@ decref_threadstate(_PyThreadStateImpl *tstate) } } -static inline void -init_policy(uint16_t *target, const char *env_name, uint16_t default_value, - long min_value, long max_value) -{ - *target = default_value; - char *env = Py_GETENV(env_name); - if (env && *env != '\0') { - long value = atol(env); - if (value >= min_value && value <= max_value) { - *target = (uint16_t)value; - } - } -} - /* Get the thread state to a minimal consistent state. Further init happens in pylifecycle.c before it can be used. All fields not initialized here are expected to be zeroed out, @@ -1538,21 +1571,8 @@ init_threadstate(_PyThreadStateImpl *_tstate, _tstate->asyncio_running_loop = NULL; _tstate->asyncio_running_task = NULL; - // Initialize interpreter policy from environment variables - init_policy(&_tstate->policy.interp.jump_backward_initial_value, - "PYTHON_JIT_JUMP_BACKWARD_INITIAL_VALUE", - JUMP_BACKWARD_INITIAL_VALUE, 1, MAX_VALUE); - init_policy(&_tstate->policy.interp.jump_backward_initial_backoff, - "PYTHON_JIT_JUMP_BACKWARD_INITIAL_BACKOFF", - JUMP_BACKWARD_INITIAL_BACKOFF, 0, MAX_BACKOFF); + #ifdef _Py_TIER2 - // Initialize JIT policy from environment variables - init_policy(&_tstate->policy.jit.side_exit_initial_value, - "PYTHON_JIT_SIDE_EXIT_INITIAL_VALUE", - SIDE_EXIT_INITIAL_VALUE, 1, MAX_VALUE); - init_policy(&_tstate->policy.jit.side_exit_initial_backoff, - "PYTHON_JIT_SIDE_EXIT_INITIAL_BACKOFF", - SIDE_EXIT_INITIAL_BACKOFF, 0, MAX_BACKOFF); _tstate->jit_tracer_state = NULL; #endif tstate->delete_later = NULL; diff --git a/Python/specialize.c b/Python/specialize.c index 80db7d01f38f1e..2f82fb4ff4ef84 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -48,8 +48,8 @@ _PyCode_Quicken(_Py_CODEUNIT *instructions, Py_ssize_t size, int enable_counters _Py_BackoffCounter jump_counter, adaptive_counter; if (enable_counters) { PyThreadState *tstate = _PyThreadState_GET(); - _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; - jump_counter = initial_jump_backoff_counter(&tstate_impl->policy); + PyInterpreterState *interp = tstate->interp; + jump_counter = initial_jump_backoff_counter(&interp->opt_config); adaptive_counter = adaptive_counter_warmup(); } else { From bdba5f0db2ab29f3deedb9416f3c143d33e4ab66 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 15 Jan 2026 06:50:47 +0300 Subject: [PATCH 3/3] gh-143635: Fix crash in `ga_repr_items_list` (#143670) --- Lib/test/test_genericalias.py | 50 +++++++++++++++++++ ...-01-11-20-11-36.gh-issue-143670.klnGoD.rst | 1 + Objects/genericaliasobject.c | 7 ++- 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-01-11-20-11-36.gh-issue-143670.klnGoD.rst diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 0017c093166dd2..2b9cee6433b5b8 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -245,6 +245,56 @@ class MyGeneric: self.assertEndsWith(repr(MyGeneric[[]]), 'MyGeneric[[]]') self.assertEndsWith(repr(MyGeneric[[int, str]]), 'MyGeneric[[int, str]]') + def test_evil_repr1(self): + # gh-143635 + class Zap: + def __init__(self, container): + self.container = container + def __getattr__(self, name): + if name == "__origin__": + self.container.clear() + return None + if name == "__args__": + return () + raise AttributeError + + params = [] + params.append(Zap(params)) + alias = GenericAlias(list, (params,)) + repr_str = repr(alias) + self.assertTrue(repr_str.startswith("list[["), repr_str) + + def test_evil_repr2(self): + class Zap: + def __init__(self, container): + self.container = container + def __getattr__(self, name): + if name == "__qualname__": + self.container.clear() + return "abcd" + if name == "__module__": + return None + raise AttributeError + + params = [] + params.append(Zap(params)) + alias = GenericAlias(list, (params,)) + repr_str = repr(alias) + self.assertTrue(repr_str.startswith("list[["), repr_str) + + def test_evil_repr3(self): + # gh-143823 + lst = [] + class X: + def __repr__(self): + lst.clear() + return "x" + + lst += [X(), 1] + ga = GenericAlias(int, lst) + with self.assertRaises(IndexError): + repr(ga) + def test_exposed_type(self): import types a = types.GenericAlias(list, int) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-11-20-11-36.gh-issue-143670.klnGoD.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-11-20-11-36.gh-issue-143670.klnGoD.rst new file mode 100644 index 00000000000000..4ce0e71a47e145 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-11-20-11-36.gh-issue-143670.klnGoD.rst @@ -0,0 +1 @@ +Fixes a crash in ``ga_repr_items_list`` function. diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 8b526f43f1e053..119dd4b5c2dd00 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -68,10 +68,15 @@ ga_repr_items_list(PyUnicodeWriter *writer, PyObject *p) return -1; } } - PyObject *item = PyList_GET_ITEM(p, i); + PyObject *item = PyList_GetItemRef(p, i); + if (item == NULL) { + return -1; // list can be mutated in a callback + } if (_Py_typing_type_repr(writer, item) < 0) { + Py_DECREF(item); return -1; } + Py_DECREF(item); } if (PyUnicodeWriter_WriteChar(writer, ']') < 0) {