diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 4ca108cd6ca43e..5f40ad1d7a0f24 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -2047,5 +2047,25 @@ def load_module_attr_missing(): sys.modules.pop("test_module_with_getattr", None) + @cpython_only + @requires_specialization + def test_load_attr_enum(self): + import enum + + class Color(enum.IntEnum): + RED = 1 + GREEN = 2 + BLUE = 3 + + def load_enum_member(): + for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): + x = Color.RED + assert x == 1 + + load_enum_member() + self.assert_specialized(load_enum_member, + "LOAD_ATTR_CLASS_WITH_METACLASS_CHECK") + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-06-18-25-53.gh-issue-95004.CQeT_H.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-06-18-25-53.gh-issue-95004.CQeT_H.rst new file mode 100644 index 00000000000000..a492982bc62da7 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-06-18-25-53.gh-issue-95004.CQeT_H.rst @@ -0,0 +1 @@ +The specializing interpreter now specializes for :class:`enum.Enum` improving performance and scaling in free-threading. Patch by Kumar Aditya. diff --git a/Python/specialize.c b/Python/specialize.c index 0fe225dcbb6b5f..bfa7b8148e46de 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -1201,22 +1201,33 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr, } } switch (kind) { - case METHOD: - case NON_DESCRIPTOR: - #ifdef Py_GIL_DISABLED - if (!_PyObject_HasDeferredRefcount(descr)) { - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_DESCR_NOT_DEFERRED); + case MUTABLE: + // special case for enums which has Py_TYPE(descr) == cls + // so guarding on type version is sufficient + if (Py_TYPE(descr) != cls) { + SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_MUTABLE_CLASS); Py_XDECREF(descr); return -1; } - #endif - write_u32(cache->type_version, tp_version); + if (Py_TYPE(descr)->tp_descr_get || Py_TYPE(descr)->tp_descr_set) { + SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_OVERRIDING_DESCRIPTOR); + Py_XDECREF(descr); + return -1; + } + _Py_FALLTHROUGH; + case METHOD: + case NON_DESCRIPTOR: +#ifdef Py_GIL_DISABLED + maybe_enable_deferred_ref_count(descr); +#endif write_ptr(cache->descr, descr); if (metaclass_check) { - write_u32(cache->keys_version, meta_version); + write_u32(cache->keys_version, tp_version); + write_u32(cache->type_version, meta_version); specialize(instr, LOAD_ATTR_CLASS_WITH_METACLASS_CHECK); } else { + write_u32(cache->type_version, tp_version); specialize(instr, LOAD_ATTR_CLASS); } Py_XDECREF(descr); diff --git a/Tools/ftscalingbench/ftscalingbench.py b/Tools/ftscalingbench/ftscalingbench.py index a3d87e1f855dcb..60f43b99c0f69d 100644 --- a/Tools/ftscalingbench/ftscalingbench.py +++ b/Tools/ftscalingbench/ftscalingbench.py @@ -295,6 +295,20 @@ def setattr_non_interned(): setattr(obj, f"{prefix}_c", None) +from enum import Enum +class MyEnum(Enum): + X = 1 + Y = 2 + Z = 3 + +@register_benchmark +def enum_attr(): + for _ in range(1000 * WORK_SCALE): + MyEnum.X + MyEnum.Y + MyEnum.Z + + def bench_one_thread(func): t0 = time.perf_counter_ns() func()