From e9a08b060eebebe66ef1d6a904978b5b25cf3456 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 10 Apr 2026 18:45:13 +0530 Subject: [PATCH 1/4] constant fold classmethod and staticmethod in JIT --- Include/internal/pycore_opcode_metadata.h | 4 +- Lib/test/test_capi/test_opt.py | 14 ++++++- Python/bytecodes.c | 1 + Python/optimizer_bytecodes.c | 40 ++++++++++++++++-- Python/optimizer_cases.c.h | 51 +++++++++++++++++++---- Python/record_functions.c.h | 1 + Tools/cases_generator/analyzer.py | 3 +- 7 files changed, 98 insertions(+), 16 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 79ca8a1bfa88c0..9c4364cfcb3200 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1220,7 +1220,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [JUMP_FORWARD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [LIST_APPEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, [LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG }, [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG | HAS_RECORDS_VALUE_FLAG }, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_SYNC_SP_FLAG | HAS_NEEDS_GUARD_IP_FLAG }, @@ -1441,7 +1441,7 @@ _PyOpcode_macro_expansion[256] = { [JUMP_BACKWARD_NO_JIT] = { .nuops = 2, .uops = { { _CHECK_PERIODIC, OPARG_SIMPLE, 1 }, { _JUMP_BACKWARD_NO_INTERRUPT, OPARG_REPLACED, 1 } } }, [LIST_APPEND] = { .nuops = 1, .uops = { { _LIST_APPEND, OPARG_SIMPLE, 0 } } }, [LIST_EXTEND] = { .nuops = 2, .uops = { { _LIST_EXTEND, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 } } }, - [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, OPARG_SIMPLE, 8 } } }, + [LOAD_ATTR] = { .nuops = 2, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 0 }, { _LOAD_ATTR, OPARG_SIMPLE, 8 } } }, [LOAD_ATTR_CLASS] = { .nuops = 3, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_CLASS, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 6, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 2c6584592b81ee..192a9d9af3940e 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3173,11 +3173,20 @@ def m(self): class E(Exception): def m(self): return 1 + class F: + @classmethod + def class_method(cls): + return 1 + @staticmethod + def static_method(): + return 1 + def f(n): x = 0 c = C() d = D() e = E() + f = F() for _ in range(n): x += C.A # _LOAD_ATTR_CLASS x += c.A # _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES @@ -3185,12 +3194,15 @@ def f(n): x += c.m() # _LOAD_ATTR_METHOD_WITH_VALUES x += d.m() # _LOAD_ATTR_METHOD_NO_DICT x += e.m() # _LOAD_ATTR_METHOD_LAZY_DICT + x += f.class_method() # _LOAD_ATTR + x += f.static_method() # _LOAD_ATTR return x res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) - self.assertEqual(res, 6 * TIER2_THRESHOLD) + self.assertEqual(res, 8 * TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) + self.assertNotIn("_LOAD_ATTR", uops) self.assertNotIn("_LOAD_ATTR_CLASS", uops) self.assertNotIn("_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", uops) self.assertNotIn("_LOAD_ATTR_NONDESCRIPTOR_NO_DICT", uops) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index bd7228350be678..59b637c0f90387 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2728,6 +2728,7 @@ dummy_func( macro(LOAD_ATTR) = _SPECIALIZE_LOAD_ATTR + + _RECORD_TOS_TYPE + unused/8 + _LOAD_ATTR; diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index e95ccd4d448e18..49c20f6c8adec6 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -873,10 +873,42 @@ dummy_func(void) { } op(_LOAD_ATTR, (owner -- attr, self_or_null[oparg&1])) { - (void)owner; - attr = sym_new_not_null(ctx); - if (oparg & 1) { - self_or_null[0] = sym_new_unknown(ctx); + PyTypeObject *type = sym_get_probable_type(owner); + if (oparg & 1 && type != NULL) { + PyObject *name = get_co_name(ctx, oparg >> 1); + PyObject *descr = _PyType_Lookup(type, name); + bool class_method = descr && Py_IS_TYPE(descr, &PyClassMethod_Type); + bool static_method = descr && Py_IS_TYPE(descr, &PyStaticMethod_Type); + if (class_method || static_method) { + PyObject *callable = NULL; + if (class_method) { + callable = _PyClassMethod_GetFunc(descr); + } + else { + assert(static_method); + callable = _PyStaticMethod_GetFunc(descr); + } + assert(callable); + bool immortal = _Py_IsImmortal(callable) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); + ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); + ADD_OP(_POP_TOP, 0, 0); + ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)callable); + if (class_method) { + ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); + self_or_null[0] = sym_new_const(ctx, (PyObject *)type); + } else if (static_method) { + ADD_OP(_PUSH_NULL, 0, 0); + self_or_null[0] = sym_new_null(ctx); + } + attr = sym_new_const(ctx, callable); + PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); + _Py_BloomFilter_Add(dependencies, (PyTypeObject *)type); + } else { + attr = sym_new_not_null(ctx); + self_or_null[0] = sym_new_unknown(ctx); + } + } else { + attr = sym_new_not_null(ctx); } } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index e8a7ec216b040b..90db2783037dfe 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2409,15 +2409,50 @@ JitOptRef *self_or_null; owner = stack_pointer[-1]; self_or_null = &stack_pointer[0]; - (void)owner; - attr = sym_new_not_null(ctx); - if (oparg & 1) { - self_or_null[0] = sym_new_unknown(ctx); + PyTypeObject *type = sym_get_probable_type(owner); + if (oparg & 1 && type != NULL) { + PyObject *name = get_co_name(ctx, oparg >> 1); + PyObject *descr = _PyType_Lookup(type, name); + bool class_method = descr && Py_IS_TYPE(descr, &PyClassMethod_Type); + bool static_method = descr && Py_IS_TYPE(descr, &PyStaticMethod_Type); + if (class_method || static_method) { + PyObject *callable = NULL; + if (class_method) { + callable = _PyClassMethod_GetFunc(descr); + } + else { + assert(static_method); + callable = _PyStaticMethod_GetFunc(descr); + } + assert(callable); + bool immortal = _Py_IsImmortal(callable) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); + ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); + ADD_OP(_POP_TOP, 0, 0); + ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)callable); + if (class_method) { + ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); + self_or_null[0] = sym_new_const(ctx, (PyObject *)type); + } else if (static_method) { + ADD_OP(_PUSH_NULL, 0, 0); + self_or_null[0] = sym_new_null(ctx); + } + attr = sym_new_const(ctx, callable); + CHECK_STACK_BOUNDS((oparg&1)); + stack_pointer[-1] = attr; + stack_pointer += (oparg&1); + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); + _Py_BloomFilter_Add(dependencies, (PyTypeObject *)type); + } else { + attr = sym_new_not_null(ctx); + self_or_null[0] = sym_new_unknown(ctx); + stack_pointer += (oparg&1); + } + } else { + attr = sym_new_not_null(ctx); + stack_pointer += (oparg&1); } - CHECK_STACK_BOUNDS((oparg&1)); - stack_pointer[-1] = attr; - stack_pointer += (oparg&1); - ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + stack_pointer[-1 - (oparg&1)] = attr; break; } diff --git a/Python/record_functions.c.h b/Python/record_functions.c.h index 33fd2f19b1230d..38a503cc03f2ec 100644 --- a/Python/record_functions.c.h +++ b/Python/record_functions.c.h @@ -97,6 +97,7 @@ const uint8_t _PyOpcode_RecordFunctionIndices[256] = { [BINARY_OP_SUBSCR_GETITEM] = _RECORD_NOS_INDEX, [SEND_GEN] = _RECORD_3OS_GEN_FUNC_INDEX, [LOAD_SUPER_ATTR_METHOD] = _RECORD_NOS_INDEX, + [LOAD_ATTR] = _RECORD_TOS_TYPE_INDEX, [LOAD_ATTR_INSTANCE_VALUE] = _RECORD_TOS_TYPE_INDEX, [LOAD_ATTR_WITH_HINT] = _RECORD_TOS_TYPE_INDEX, [LOAD_ATTR_SLOT] = _RECORD_TOS_TYPE_INDEX, diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 6ba9c43ef1f0c3..de6538b8d26462 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -1149,7 +1149,8 @@ def add_macro( f"Recording uop {part.name} must be first in macro", macro.tokens[0]) parts.append(uop) - first = False + if uop.properties.tier != 1: + first = False case parser.CacheEffect(): parts.append(Skip(part.size)) case _: From ac515b80ad5f38ae65d2983db912900318e28c0d Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Sun, 12 Apr 2026 14:41:10 +0530 Subject: [PATCH 2/4] constant fold on classes as well --- Include/internal/pycore_opcode_metadata.h | 2 +- Lib/test/test_capi/test_opt.py | 4 +++- Python/bytecodes.c | 2 +- Python/optimizer_bytecodes.c | 26 ++++++++++++++++++----- Python/optimizer_cases.c.h | 25 +++++++++++++++++----- Python/record_functions.c.h | 14 ++++++------ 6 files changed, 54 insertions(+), 19 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 9c4364cfcb3200..42f2e0e6073a8f 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1441,7 +1441,7 @@ _PyOpcode_macro_expansion[256] = { [JUMP_BACKWARD_NO_JIT] = { .nuops = 2, .uops = { { _CHECK_PERIODIC, OPARG_SIMPLE, 1 }, { _JUMP_BACKWARD_NO_INTERRUPT, OPARG_REPLACED, 1 } } }, [LIST_APPEND] = { .nuops = 1, .uops = { { _LIST_APPEND, OPARG_SIMPLE, 0 } } }, [LIST_EXTEND] = { .nuops = 2, .uops = { { _LIST_EXTEND, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 } } }, - [LOAD_ATTR] = { .nuops = 2, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 0 }, { _LOAD_ATTR, OPARG_SIMPLE, 8 } } }, + [LOAD_ATTR] = { .nuops = 2, .uops = { { _RECORD_TOS, OPARG_SIMPLE, 0 }, { _LOAD_ATTR, OPARG_SIMPLE, 8 } } }, [LOAD_ATTR_CLASS] = { .nuops = 3, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_CLASS, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 6, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 192a9d9af3940e..b10f350285ae25 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3196,10 +3196,12 @@ def f(n): x += e.m() # _LOAD_ATTR_METHOD_LAZY_DICT x += f.class_method() # _LOAD_ATTR x += f.static_method() # _LOAD_ATTR + x += F.class_method() # _LOAD_ATTR + x += F.static_method() # _LOAD_ATTR return x res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) - self.assertEqual(res, 8 * TIER2_THRESHOLD) + self.assertEqual(res, 10 * TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) self.assertNotIn("_LOAD_ATTR", uops) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 59b637c0f90387..5b91ca491e79a5 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2728,7 +2728,7 @@ dummy_func( macro(LOAD_ATTR) = _SPECIALIZE_LOAD_ATTR + - _RECORD_TOS_TYPE + + _RECORD_TOS + unused/8 + _LOAD_ATTR; diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 49c20f6c8adec6..0c1a930ea07f93 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -873,7 +873,15 @@ dummy_func(void) { } op(_LOAD_ATTR, (owner -- attr, self_or_null[oparg&1])) { - PyTypeObject *type = sym_get_probable_type(owner); + PyObject *value = sym_get_probable_value(owner); + PyTypeObject *type = NULL; + if (value != NULL && PyType_Check(value)) { + type = (PyTypeObject *)value; + } + else { + type = sym_get_probable_type(owner); + } + if (oparg & 1 && type != NULL) { PyObject *name = get_co_name(ctx, oparg >> 1); PyObject *descr = _PyType_Lookup(type, name); @@ -890,24 +898,32 @@ dummy_func(void) { } assert(callable); bool immortal = _Py_IsImmortal(callable) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); - ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); + if (PyType_Check(value)) { + ADD_OP(_CHECK_ATTR_CLASS, 0, type->tp_version_tag); + } + else { + ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); + } ADD_OP(_POP_TOP, 0, 0); ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)callable); if (class_method) { ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); self_or_null[0] = sym_new_const(ctx, (PyObject *)type); - } else if (static_method) { + } + else { ADD_OP(_PUSH_NULL, 0, 0); self_or_null[0] = sym_new_null(ctx); } attr = sym_new_const(ctx, callable); PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); _Py_BloomFilter_Add(dependencies, (PyTypeObject *)type); - } else { + } + else { attr = sym_new_not_null(ctx); self_or_null[0] = sym_new_unknown(ctx); } - } else { + } + else { attr = sym_new_not_null(ctx); } } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 90db2783037dfe..61585c774ccb2e 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2409,7 +2409,14 @@ JitOptRef *self_or_null; owner = stack_pointer[-1]; self_or_null = &stack_pointer[0]; - PyTypeObject *type = sym_get_probable_type(owner); + PyObject *value = sym_get_probable_value(owner); + PyTypeObject *type = NULL; + if (value != NULL && PyType_Check(value)) { + type = (PyTypeObject *)value; + } + else { + type = sym_get_probable_type(owner); + } if (oparg & 1 && type != NULL) { PyObject *name = get_co_name(ctx, oparg >> 1); PyObject *descr = _PyType_Lookup(type, name); @@ -2426,13 +2433,19 @@ } assert(callable); bool immortal = _Py_IsImmortal(callable) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); - ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); + if (PyType_Check(value)) { + ADD_OP(_CHECK_ATTR_CLASS, 0, type->tp_version_tag); + } + else { + ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); + } ADD_OP(_POP_TOP, 0, 0); ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)callable); if (class_method) { ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); self_or_null[0] = sym_new_const(ctx, (PyObject *)type); - } else if (static_method) { + } + else { ADD_OP(_PUSH_NULL, 0, 0); self_or_null[0] = sym_new_null(ctx); } @@ -2443,12 +2456,14 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); _Py_BloomFilter_Add(dependencies, (PyTypeObject *)type); - } else { + } + else { attr = sym_new_not_null(ctx); self_or_null[0] = sym_new_unknown(ctx); stack_pointer += (oparg&1); } - } else { + } + else { attr = sym_new_not_null(ctx); stack_pointer += (oparg&1); } diff --git a/Python/record_functions.c.h b/Python/record_functions.c.h index 38a503cc03f2ec..64aff01be83260 100644 --- a/Python/record_functions.c.h +++ b/Python/record_functions.c.h @@ -88,16 +88,17 @@ void _PyOpcode_RecordFunction_CODE(_PyInterpreterFrame *frame, _PyStackRef *stac #define _RECORD_TOS_TYPE_INDEX 1 #define _RECORD_NOS_INDEX 2 #define _RECORD_3OS_GEN_FUNC_INDEX 3 -#define _RECORD_NOS_GEN_FUNC_INDEX 4 -#define _RECORD_CALLABLE_INDEX 5 -#define _RECORD_BOUND_METHOD_INDEX 6 -#define _RECORD_4OS_INDEX 7 +#define _RECORD_TOS_INDEX 4 +#define _RECORD_NOS_GEN_FUNC_INDEX 5 +#define _RECORD_CALLABLE_INDEX 6 +#define _RECORD_BOUND_METHOD_INDEX 7 +#define _RECORD_4OS_INDEX 8 const uint8_t _PyOpcode_RecordFunctionIndices[256] = { [TO_BOOL_ALWAYS_TRUE] = _RECORD_TOS_TYPE_INDEX, [BINARY_OP_SUBSCR_GETITEM] = _RECORD_NOS_INDEX, [SEND_GEN] = _RECORD_3OS_GEN_FUNC_INDEX, [LOAD_SUPER_ATTR_METHOD] = _RECORD_NOS_INDEX, - [LOAD_ATTR] = _RECORD_TOS_TYPE_INDEX, + [LOAD_ATTR] = _RECORD_TOS_INDEX, [LOAD_ATTR_INSTANCE_VALUE] = _RECORD_TOS_TYPE_INDEX, [LOAD_ATTR_WITH_HINT] = _RECORD_TOS_TYPE_INDEX, [LOAD_ATTR_SLOT] = _RECORD_TOS_TYPE_INDEX, @@ -127,11 +128,12 @@ const uint8_t _PyOpcode_RecordFunctionIndices[256] = { [CALL_EX_PY] = _RECORD_4OS_INDEX, }; -const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[8] = { +const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[9] = { [0] = NULL, [_RECORD_TOS_TYPE_INDEX] = _PyOpcode_RecordFunction_TOS_TYPE, [_RECORD_NOS_INDEX] = _PyOpcode_RecordFunction_NOS, [_RECORD_3OS_GEN_FUNC_INDEX] = _PyOpcode_RecordFunction_3OS_GEN_FUNC, + [_RECORD_TOS_INDEX] = _PyOpcode_RecordFunction_TOS, [_RECORD_NOS_GEN_FUNC_INDEX] = _PyOpcode_RecordFunction_NOS_GEN_FUNC, [_RECORD_CALLABLE_INDEX] = _PyOpcode_RecordFunction_CALLABLE, [_RECORD_BOUND_METHOD_INDEX] = _PyOpcode_RecordFunction_BOUND_METHOD, From 55bf43a569bd99e52343d4fdb4780ef569bb5d2a Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Sun, 12 Apr 2026 15:09:10 +0530 Subject: [PATCH 3/4] Revert "constant fold on classes as well" This reverts commit ac515b80ad5f38ae65d2983db912900318e28c0d. --- Include/internal/pycore_opcode_metadata.h | 2 +- Lib/test/test_capi/test_opt.py | 4 +--- Python/bytecodes.c | 2 +- Python/optimizer_bytecodes.c | 26 +++++------------------ Python/optimizer_cases.c.h | 25 +++++----------------- Python/record_functions.c.h | 14 ++++++------ 6 files changed, 19 insertions(+), 54 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 42f2e0e6073a8f..9c4364cfcb3200 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1441,7 +1441,7 @@ _PyOpcode_macro_expansion[256] = { [JUMP_BACKWARD_NO_JIT] = { .nuops = 2, .uops = { { _CHECK_PERIODIC, OPARG_SIMPLE, 1 }, { _JUMP_BACKWARD_NO_INTERRUPT, OPARG_REPLACED, 1 } } }, [LIST_APPEND] = { .nuops = 1, .uops = { { _LIST_APPEND, OPARG_SIMPLE, 0 } } }, [LIST_EXTEND] = { .nuops = 2, .uops = { { _LIST_EXTEND, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 } } }, - [LOAD_ATTR] = { .nuops = 2, .uops = { { _RECORD_TOS, OPARG_SIMPLE, 0 }, { _LOAD_ATTR, OPARG_SIMPLE, 8 } } }, + [LOAD_ATTR] = { .nuops = 2, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 0 }, { _LOAD_ATTR, OPARG_SIMPLE, 8 } } }, [LOAD_ATTR_CLASS] = { .nuops = 3, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_CLASS, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 6, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index b10f350285ae25..192a9d9af3940e 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3196,12 +3196,10 @@ def f(n): x += e.m() # _LOAD_ATTR_METHOD_LAZY_DICT x += f.class_method() # _LOAD_ATTR x += f.static_method() # _LOAD_ATTR - x += F.class_method() # _LOAD_ATTR - x += F.static_method() # _LOAD_ATTR return x res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) - self.assertEqual(res, 10 * TIER2_THRESHOLD) + self.assertEqual(res, 8 * TIER2_THRESHOLD) self.assertIsNotNone(ex) uops = get_opnames(ex) self.assertNotIn("_LOAD_ATTR", uops) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 5b91ca491e79a5..59b637c0f90387 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2728,7 +2728,7 @@ dummy_func( macro(LOAD_ATTR) = _SPECIALIZE_LOAD_ATTR + - _RECORD_TOS + + _RECORD_TOS_TYPE + unused/8 + _LOAD_ATTR; diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 0c1a930ea07f93..49c20f6c8adec6 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -873,15 +873,7 @@ dummy_func(void) { } op(_LOAD_ATTR, (owner -- attr, self_or_null[oparg&1])) { - PyObject *value = sym_get_probable_value(owner); - PyTypeObject *type = NULL; - if (value != NULL && PyType_Check(value)) { - type = (PyTypeObject *)value; - } - else { - type = sym_get_probable_type(owner); - } - + PyTypeObject *type = sym_get_probable_type(owner); if (oparg & 1 && type != NULL) { PyObject *name = get_co_name(ctx, oparg >> 1); PyObject *descr = _PyType_Lookup(type, name); @@ -898,32 +890,24 @@ dummy_func(void) { } assert(callable); bool immortal = _Py_IsImmortal(callable) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); - if (PyType_Check(value)) { - ADD_OP(_CHECK_ATTR_CLASS, 0, type->tp_version_tag); - } - else { - ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); - } + ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); ADD_OP(_POP_TOP, 0, 0); ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)callable); if (class_method) { ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); self_or_null[0] = sym_new_const(ctx, (PyObject *)type); - } - else { + } else if (static_method) { ADD_OP(_PUSH_NULL, 0, 0); self_or_null[0] = sym_new_null(ctx); } attr = sym_new_const(ctx, callable); PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); _Py_BloomFilter_Add(dependencies, (PyTypeObject *)type); - } - else { + } else { attr = sym_new_not_null(ctx); self_or_null[0] = sym_new_unknown(ctx); } - } - else { + } else { attr = sym_new_not_null(ctx); } } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 61585c774ccb2e..90db2783037dfe 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2409,14 +2409,7 @@ JitOptRef *self_or_null; owner = stack_pointer[-1]; self_or_null = &stack_pointer[0]; - PyObject *value = sym_get_probable_value(owner); - PyTypeObject *type = NULL; - if (value != NULL && PyType_Check(value)) { - type = (PyTypeObject *)value; - } - else { - type = sym_get_probable_type(owner); - } + PyTypeObject *type = sym_get_probable_type(owner); if (oparg & 1 && type != NULL) { PyObject *name = get_co_name(ctx, oparg >> 1); PyObject *descr = _PyType_Lookup(type, name); @@ -2433,19 +2426,13 @@ } assert(callable); bool immortal = _Py_IsImmortal(callable) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); - if (PyType_Check(value)) { - ADD_OP(_CHECK_ATTR_CLASS, 0, type->tp_version_tag); - } - else { - ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); - } + ADD_OP(_GUARD_TYPE_VERSION, 0, type->tp_version_tag); ADD_OP(_POP_TOP, 0, 0); ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE, 0, (uintptr_t)callable); if (class_method) { ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); self_or_null[0] = sym_new_const(ctx, (PyObject *)type); - } - else { + } else if (static_method) { ADD_OP(_PUSH_NULL, 0, 0); self_or_null[0] = sym_new_null(ctx); } @@ -2456,14 +2443,12 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); _Py_BloomFilter_Add(dependencies, (PyTypeObject *)type); - } - else { + } else { attr = sym_new_not_null(ctx); self_or_null[0] = sym_new_unknown(ctx); stack_pointer += (oparg&1); } - } - else { + } else { attr = sym_new_not_null(ctx); stack_pointer += (oparg&1); } diff --git a/Python/record_functions.c.h b/Python/record_functions.c.h index 64aff01be83260..38a503cc03f2ec 100644 --- a/Python/record_functions.c.h +++ b/Python/record_functions.c.h @@ -88,17 +88,16 @@ void _PyOpcode_RecordFunction_CODE(_PyInterpreterFrame *frame, _PyStackRef *stac #define _RECORD_TOS_TYPE_INDEX 1 #define _RECORD_NOS_INDEX 2 #define _RECORD_3OS_GEN_FUNC_INDEX 3 -#define _RECORD_TOS_INDEX 4 -#define _RECORD_NOS_GEN_FUNC_INDEX 5 -#define _RECORD_CALLABLE_INDEX 6 -#define _RECORD_BOUND_METHOD_INDEX 7 -#define _RECORD_4OS_INDEX 8 +#define _RECORD_NOS_GEN_FUNC_INDEX 4 +#define _RECORD_CALLABLE_INDEX 5 +#define _RECORD_BOUND_METHOD_INDEX 6 +#define _RECORD_4OS_INDEX 7 const uint8_t _PyOpcode_RecordFunctionIndices[256] = { [TO_BOOL_ALWAYS_TRUE] = _RECORD_TOS_TYPE_INDEX, [BINARY_OP_SUBSCR_GETITEM] = _RECORD_NOS_INDEX, [SEND_GEN] = _RECORD_3OS_GEN_FUNC_INDEX, [LOAD_SUPER_ATTR_METHOD] = _RECORD_NOS_INDEX, - [LOAD_ATTR] = _RECORD_TOS_INDEX, + [LOAD_ATTR] = _RECORD_TOS_TYPE_INDEX, [LOAD_ATTR_INSTANCE_VALUE] = _RECORD_TOS_TYPE_INDEX, [LOAD_ATTR_WITH_HINT] = _RECORD_TOS_TYPE_INDEX, [LOAD_ATTR_SLOT] = _RECORD_TOS_TYPE_INDEX, @@ -128,12 +127,11 @@ const uint8_t _PyOpcode_RecordFunctionIndices[256] = { [CALL_EX_PY] = _RECORD_4OS_INDEX, }; -const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[9] = { +const _Py_RecordFuncPtr _PyOpcode_RecordFunctions[8] = { [0] = NULL, [_RECORD_TOS_TYPE_INDEX] = _PyOpcode_RecordFunction_TOS_TYPE, [_RECORD_NOS_INDEX] = _PyOpcode_RecordFunction_NOS, [_RECORD_3OS_GEN_FUNC_INDEX] = _PyOpcode_RecordFunction_3OS_GEN_FUNC, - [_RECORD_TOS_INDEX] = _PyOpcode_RecordFunction_TOS, [_RECORD_NOS_GEN_FUNC_INDEX] = _PyOpcode_RecordFunction_NOS_GEN_FUNC, [_RECORD_CALLABLE_INDEX] = _PyOpcode_RecordFunction_CALLABLE, [_RECORD_BOUND_METHOD_INDEX] = _PyOpcode_RecordFunction_BOUND_METHOD, From 5b789539f19eb2f68d09309a2e12a3c1848ea4cd Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Sun, 12 Apr 2026 15:14:13 +0530 Subject: [PATCH 4/4] fix formatting --- Python/optimizer_bytecodes.c | 9 ++++++--- Python/optimizer_cases.c.h | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 49c20f6c8adec6..e7693e117e93f0 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -896,18 +896,21 @@ dummy_func(void) { if (class_method) { ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); self_or_null[0] = sym_new_const(ctx, (PyObject *)type); - } else if (static_method) { + } + else { ADD_OP(_PUSH_NULL, 0, 0); self_or_null[0] = sym_new_null(ctx); } attr = sym_new_const(ctx, callable); PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); _Py_BloomFilter_Add(dependencies, (PyTypeObject *)type); - } else { + } + else { attr = sym_new_not_null(ctx); self_or_null[0] = sym_new_unknown(ctx); } - } else { + } + else { attr = sym_new_not_null(ctx); } } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 90db2783037dfe..9ab1e5e487a30b 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2432,7 +2432,8 @@ if (class_method) { ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)type); self_or_null[0] = sym_new_const(ctx, (PyObject *)type); - } else if (static_method) { + } + else { ADD_OP(_PUSH_NULL, 0, 0); self_or_null[0] = sym_new_null(ctx); } @@ -2443,12 +2444,14 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); _Py_BloomFilter_Add(dependencies, (PyTypeObject *)type); - } else { + } + else { attr = sym_new_not_null(ctx); self_or_null[0] = sym_new_unknown(ctx); stack_pointer += (oparg&1); } - } else { + } + else { attr = sym_new_not_null(ctx); stack_pointer += (oparg&1); }