From 27e5bc805ac2af3c95670dcbcef96c73b51b5790 Mon Sep 17 00:00:00 2001 From: Manjusaka Date: Tue, 16 Dec 2025 05:24:54 +0800 Subject: [PATCH 1/2] gh-134584: Eliminate redundant refcounting from _STORE_ATTR_WITH_HINT Signed-off-by: Manjusaka --- Include/internal/pycore_opcode_metadata.h | 2 +- Include/internal/pycore_uop_ids.h | 2 +- Include/internal/pycore_uop_metadata.h | 6 +++--- Lib/test/test_capi/test_opt.py | 19 +++++++++++++++++++ ...-12-16-05-24-24.gh-issue-134584.tJ1usH.rst | 1 + Python/bytecodes.c | 8 +++++--- Python/executor_cases.c.h | 14 +++++++++----- Python/generated_cases.c.h | 15 +++++++++++++-- Python/optimizer_bytecodes.c | 5 +++++ Python/optimizer_cases.c.h | 13 +++++++++++-- 10 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-16-05-24-24.gh-issue-134584.tJ1usH.rst diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 15e35fd53605e3..7f8f04ea96094d 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1484,7 +1484,7 @@ _PyOpcode_macro_expansion[256] = { [STORE_ATTR] = { .nuops = 1, .uops = { { _STORE_ATTR, OPARG_SIMPLE, 3 } } }, [STORE_ATTR_INSTANCE_VALUE] = { .nuops = 4, .uops = { { _GUARD_TYPE_VERSION_AND_LOCK, 2, 1 }, { _GUARD_DORV_NO_DICT, OPARG_SIMPLE, 3 }, { _STORE_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 } } }, [STORE_ATTR_SLOT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_SLOT, 1, 3 } } }, - [STORE_ATTR_WITH_HINT] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_WITH_HINT, 1, 3 } } }, + [STORE_ATTR_WITH_HINT] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _STORE_ATTR_WITH_HINT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 } } }, [STORE_DEREF] = { .nuops = 1, .uops = { { _STORE_DEREF, OPARG_SIMPLE, 0 } } }, [STORE_FAST] = { .nuops = 1, .uops = { { _STORE_FAST, OPARG_SIMPLE, 0 } } }, [STORE_FAST_LOAD_FAST] = { .nuops = 2, .uops = { { _STORE_FAST, OPARG_TOP, 0 }, { _LOAD_FAST, OPARG_BOTTOM, 0 } } }, diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index f2c92668e079a9..6f6d56d4e267ea 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -1039,7 +1039,7 @@ extern "C" { #define _STORE_ATTR_r20 1232 #define _STORE_ATTR_INSTANCE_VALUE_r21 1233 #define _STORE_ATTR_SLOT_r20 1234 -#define _STORE_ATTR_WITH_HINT_r20 1235 +#define _STORE_ATTR_WITH_HINT_r21 1235 #define _STORE_DEREF_r10 1236 #define _STORE_FAST_r10 1237 #define _STORE_FAST_0_r10 1238 diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 97e7478ab3f0b4..b63eb1a78a768c 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -1837,7 +1837,7 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { .entries = { { -1, -1, -1 }, { -1, -1, -1 }, - { 0, 2, _STORE_ATTR_WITH_HINT_r20 }, + { 1, 2, _STORE_ATTR_WITH_HINT_r21 }, { -1, -1, -1 }, }, }, @@ -3547,7 +3547,7 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_GUARD_DORV_NO_DICT_r22] = _GUARD_DORV_NO_DICT, [_GUARD_DORV_NO_DICT_r33] = _GUARD_DORV_NO_DICT, [_STORE_ATTR_INSTANCE_VALUE_r21] = _STORE_ATTR_INSTANCE_VALUE, - [_STORE_ATTR_WITH_HINT_r20] = _STORE_ATTR_WITH_HINT, + [_STORE_ATTR_WITH_HINT_r21] = _STORE_ATTR_WITH_HINT, [_STORE_ATTR_SLOT_r20] = _STORE_ATTR_SLOT, [_COMPARE_OP_r21] = _COMPARE_OP, [_COMPARE_OP_FLOAT_r01] = _COMPARE_OP_FLOAT, @@ -4788,7 +4788,7 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_STORE_ATTR_SLOT] = "_STORE_ATTR_SLOT", [_STORE_ATTR_SLOT_r20] = "_STORE_ATTR_SLOT_r20", [_STORE_ATTR_WITH_HINT] = "_STORE_ATTR_WITH_HINT", - [_STORE_ATTR_WITH_HINT_r20] = "_STORE_ATTR_WITH_HINT_r20", + [_STORE_ATTR_WITH_HINT_r21] = "_STORE_ATTR_WITH_HINT_r21", [_STORE_DEREF] = "_STORE_DEREF", [_STORE_DEREF_r10] = "_STORE_DEREF_r10", [_STORE_FAST] = "_STORE_FAST", diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index cfb5d6ef91cb5b..f256bf797a6375 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2528,6 +2528,25 @@ class C: self.assertNotIn("_POP_TOP", uops) self.assertIn("_POP_TOP_NOP", uops) + def test_store_attr_with_hint(self): + def testfunc(n): + class C: + pass + c = C() + for i in range(_testinternalcapi.SHARED_KEYS_MAX_SIZE - 1): + setattr(c, f"_{i}", None) + + for i in range(n): + c.x = i + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, None) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + + self.assertIn("_STORE_ATTR_WITH_HINT", uops) + self.assertNotIn("_POP_TOP", uops) + self.assertIn("_POP_TOP_NOP", uops) + def test_store_subscr_int(self): def testfunc(n): l = [0, 0, 0, 0] diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-16-05-24-24.gh-issue-134584.tJ1usH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-16-05-24-24.gh-issue-134584.tJ1usH.rst new file mode 100644 index 00000000000000..aa096fc827dbc4 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-16-05-24-24.gh-issue-134584.tJ1usH.rst @@ -0,0 +1 @@ +Eliminate redundant refcounting from ``_STORE_ATTR_WITH_HINT``. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index cf5bf0b6769e2a..69d9e0906b5d0f 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2601,7 +2601,7 @@ dummy_func( _STORE_ATTR_INSTANCE_VALUE + POP_TOP; - op(_STORE_ATTR_WITH_HINT, (hint/1, value, owner --)) { + op(_STORE_ATTR_WITH_HINT, (hint/1, value, owner -- o)) { PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); assert(Py_TYPE(owner_o)->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictObject *dict = _PyObject_GetManagedDict(owner_o); @@ -2631,14 +2631,16 @@ dummy_func( // old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault, // when dict only holds the strong reference to value in ep->me_value. STAT_INC(STORE_ATTR, hit); - PyStackRef_CLOSE(owner); + o = owner; + DEAD(owner); Py_XDECREF(old_value); } macro(STORE_ATTR_WITH_HINT) = unused/1 + _GUARD_TYPE_VERSION + - _STORE_ATTR_WITH_HINT; + _STORE_ATTR_WITH_HINT + + POP_TOP; op(_STORE_ATTR_SLOT, (index/1, value, owner --)) { PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index d8f33bc979710d..446bb2e477b06a 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -8768,11 +8768,12 @@ break; } - case _STORE_ATTR_WITH_HINT_r20: { + case _STORE_ATTR_WITH_HINT_r21: { CHECK_CURRENT_CACHED_VALUES(2); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); _PyStackRef owner; _PyStackRef value; + _PyStackRef o; _PyStackRef _stack_item_0 = _tos_cache0; _PyStackRef _stack_item_1 = _tos_cache1; oparg = CURRENT_OPARG(); @@ -8841,16 +8842,19 @@ FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value)); UNLOCK_OBJECT(dict); STAT_INC(STORE_ATTR, hit); - stack_pointer += -2; + o = owner; + stack_pointer[-2] = o; + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(owner); Py_XDECREF(old_value); stack_pointer = _PyFrame_GetStackPointer(frame); - _tos_cache0 = PyStackRef_ZERO_BITS; + _tos_cache0 = o; _tos_cache1 = PyStackRef_ZERO_BITS; _tos_cache2 = PyStackRef_ZERO_BITS; - SET_CURRENT_CACHED_VALUES(0); + SET_CURRENT_CACHED_VALUES(1); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE()); break; } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 1e7778c7e752bb..9bf45d5fbe44a4 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -10673,6 +10673,7 @@ static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); _PyStackRef owner; _PyStackRef value; + _PyStackRef o; /* Skip 1 cache entry */ // _GUARD_TYPE_VERSION { @@ -10738,13 +10739,23 @@ FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, PyStackRef_AsPyObjectSteal(value)); UNLOCK_OBJECT(dict); STAT_INC(STORE_ATTR, hit); - stack_pointer += -2; + o = owner; + stack_pointer[-2] = o; + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); - PyStackRef_CLOSE(owner); Py_XDECREF(old_value); stack_pointer = _PyFrame_GetStackPointer(frame); } + // _POP_TOP + { + value = o; + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + PyStackRef_XCLOSE(value); + stack_pointer = _PyFrame_GetStackPointer(frame); + } DISPATCH(); } diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 9e9af6186c4df5..6846902d446d80 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -104,6 +104,11 @@ dummy_func(void) { o = owner; } + op(_STORE_ATTR_WITH_HINT, (hint/1, value, owner -- o)) { + (void)value; + o = owner; + } + op(_STORE_FAST, (value --)) { GETLOCAL(oparg) = value; } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index b79e9125d6bf2e..58746126338ecb 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1901,8 +1901,17 @@ } case _STORE_ATTR_WITH_HINT: { - CHECK_STACK_BOUNDS(-2); - stack_pointer += -2; + JitOptRef owner; + JitOptRef value; + JitOptRef o; + owner = stack_pointer[-1]; + value = stack_pointer[-2]; + uint16_t hint = (uint16_t)this_instr->operand0; + (void)value; + o = owner; + CHECK_STACK_BOUNDS(-1); + stack_pointer[-2] = o; + stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); break; } From a10bbbbe433998b0efcba674cebf0b6f81bd7669 Mon Sep 17 00:00:00 2001 From: Manjusaka Date: Tue, 16 Dec 2025 05:27:50 +0800 Subject: [PATCH 2/2] update test Signed-off-by: Manjusaka --- Lib/test/test_capi/test_opt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index f256bf797a6375..04060f2d1ee3f9 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2538,8 +2538,9 @@ class C: for i in range(n): c.x = i + return c.x res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) - self.assertEqual(res, None) + self.assertEqual(res, TIER2_THRESHOLD - 1) self.assertIsNotNone(ex) uops = get_opnames(ex)