https://github.com/python/cpython/commit/3e2f5c133f37f13f627404f3cbd54a5fc163887a
commit: 3e2f5c133f37f13f627404f3cbd54a5fc163887a
branch: main
author: Donghee Na <[email protected]>
committer: corona10 <[email protected]>
date: 2026-02-19T01:10:53+09:00
summary:

gh-141510: Update specializer to support frozendict (gh-144949)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-02-18-21-44-39.gh-issue-141510.7LST2O.rst
M Include/internal/pycore_interp_structs.h
M Lib/test/test_opcache.py
M Modules/_testinternalcapi/test_cases.c.h
M Python/bytecodes.c
M Python/executor_cases.c.h
M Python/generated_cases.c.h
M Python/specialize.c

diff --git a/Include/internal/pycore_interp_structs.h 
b/Include/internal/pycore_interp_structs.h
index 3ebc8967c3dc7f..1e69c64bcd1fc0 100644
--- a/Include/internal/pycore_interp_structs.h
+++ b/Include/internal/pycore_interp_structs.h
@@ -496,7 +496,7 @@ struct _py_func_state {
 
 /* For now we hard-code this to a value for which we are confident
    all the static builtin types will fit (for all builds). */
-#define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES 200
+#define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES 201
 #define _Py_MAX_MANAGED_STATIC_EXT_TYPES 10
 #define _Py_MAX_MANAGED_STATIC_TYPES \
     (_Py_MAX_MANAGED_STATIC_BUILTIN_TYPES + _Py_MAX_MANAGED_STATIC_EXT_TYPES)
diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py
index 343711ce3a9cef..1f5b0596107704 100644
--- a/Lib/test/test_opcache.py
+++ b/Lib/test/test_opcache.py
@@ -1548,6 +1548,27 @@ def contains_op_dict():
         self.assert_specialized(contains_op_dict, "CONTAINS_OP_DICT")
         self.assert_no_opcode(contains_op_dict, "CONTAINS_OP")
 
+        def contains_op_frozen_dict():
+            for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
+                a, b = 1, frozendict({1: 2, 2: 5})
+                self.assertTrue(a in b)
+                self.assertFalse(3 in b)
+
+        contains_op_frozen_dict()
+        self.assert_specialized(contains_op_frozen_dict, "CONTAINS_OP_DICT")
+        self.assert_no_opcode(contains_op_frozen_dict, "CONTAINS_OP")
+
+        def contains_op_frozen_dict_subclass():
+            class MyFrozenDict(frozendict):
+                pass
+            for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
+                a, b = 1, MyFrozenDict({1: 2, 2: 5})
+                self.assertTrue(a in b)
+                self.assertFalse(3 in b)
+
+        contains_op_frozen_dict_subclass()
+        self.assert_no_opcode(contains_op_frozen_dict_subclass, 
"CONTAINS_OP_DICT")
+
         def contains_op_set():
             for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
                 a, b = 1, {1, 2}
@@ -1808,6 +1829,27 @@ def binary_subscr_dict():
         self.assert_specialized(binary_subscr_dict, "BINARY_OP_SUBSCR_DICT")
         self.assert_no_opcode(binary_subscr_dict, "BINARY_OP")
 
+        def binary_subscr_frozen_dict():
+            for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
+                a = frozendict({1: 2, 2: 3})
+                self.assertEqual(a[1], 2)
+                self.assertEqual(a[2], 3)
+
+        binary_subscr_frozen_dict()
+        self.assert_specialized(binary_subscr_frozen_dict, 
"BINARY_OP_SUBSCR_DICT")
+        self.assert_no_opcode(binary_subscr_frozen_dict, "BINARY_OP")
+
+        def binary_subscr_frozen_dict_subclass():
+            class MyFrozenDict(frozendict):
+                pass
+            for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
+                a = MyFrozenDict({1: 2, 2: 3})
+                self.assertEqual(a[1], 2)
+                self.assertEqual(a[2], 3)
+
+        binary_subscr_frozen_dict_subclass()
+        self.assert_no_opcode(binary_subscr_frozen_dict_subclass, 
"BINARY_OP_SUBSCR_DICT")
+
         def binary_subscr_str_int():
             for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
                 a = "foobar"
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-18-21-44-39.gh-issue-141510.7LST2O.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-18-21-44-39.gh-issue-141510.7LST2O.rst
new file mode 100644
index 00000000000000..87d6a2a6df96a1
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-18-21-44-39.gh-issue-141510.7LST2O.rst
@@ -0,0 +1 @@
+Update specializer to support frozendict. Patch by Donghee Na.
diff --git a/Modules/_testinternalcapi/test_cases.c.h 
b/Modules/_testinternalcapi/test_cases.c.h
index ddd8fcdc231bf1..a9cd0574a596a1 100644
--- a/Modules/_testinternalcapi/test_cases.c.h
+++ b/Modules/_testinternalcapi/test_cases.c.h
@@ -642,7 +642,7 @@
             {
                 nos = stack_pointer[-2];
                 PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-                if (!PyDict_CheckExact(o)) {
+                if (!PyAnyDict_CheckExact(o)) {
                     UPDATE_MISS_STATS(BINARY_OP);
                     assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
                     JUMP_TO_PREDICTED(BINARY_OP);
@@ -655,7 +655,7 @@
                 dict_st = nos;
                 PyObject *sub = PyStackRef_AsPyObjectBorrow(sub_st);
                 PyObject *dict = PyStackRef_AsPyObjectBorrow(dict_st);
-                assert(PyDict_CheckExact(dict));
+                assert(PyAnyDict_CheckExact(dict));
                 STAT_INC(BINARY_OP, hit);
                 PyObject *res_o;
                 _PyFrame_SetStackPointer(frame, stack_pointer);
@@ -5139,7 +5139,7 @@
             {
                 tos = stack_pointer[-1];
                 PyObject *o = PyStackRef_AsPyObjectBorrow(tos);
-                if (!PyDict_CheckExact(o)) {
+                if (!PyAnyDict_CheckExact(o)) {
                     UPDATE_MISS_STATS(CONTAINS_OP);
                     assert(_PyOpcode_Deopt[opcode] == (CONTAINS_OP));
                     JUMP_TO_PREDICTED(CONTAINS_OP);
@@ -5152,7 +5152,7 @@
                 left = stack_pointer[-2];
                 PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
                 PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
-                assert(PyDict_CheckExact(right_o));
+                assert(PyAnyDict_CheckExact(right_o));
                 STAT_INC(CONTAINS_OP, hit);
                 _PyFrame_SetStackPointer(frame, stack_pointer);
                 int res = PyDict_Contains(right_o, left_o);
@@ -11482,7 +11482,7 @@
             {
                 nos = stack_pointer[-2];
                 PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-                if (!PyDict_CheckExact(o)) {
+                if (!PyAnyDict_CheckExact(o)) {
                     UPDATE_MISS_STATS(STORE_SUBSCR);
                     assert(_PyOpcode_Deopt[opcode] == (STORE_SUBSCR));
                     JUMP_TO_PREDICTED(STORE_SUBSCR);
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index b461f9b5bea8a6..63a4222264985a 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -1058,12 +1058,12 @@ dummy_func(
 
         op(_GUARD_NOS_DICT, (nos, unused -- nos, unused)) {
             PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-            EXIT_IF(!PyDict_CheckExact(o));
+            EXIT_IF(!PyAnyDict_CheckExact(o));
         }
 
         op(_GUARD_TOS_DICT, (tos -- tos)) {
             PyObject *o = PyStackRef_AsPyObjectBorrow(tos);
-            EXIT_IF(!PyDict_CheckExact(o));
+            EXIT_IF(!PyAnyDict_CheckExact(o));
         }
 
         macro(BINARY_OP_SUBSCR_DICT) =
@@ -1073,7 +1073,7 @@ dummy_func(
             PyObject *sub = PyStackRef_AsPyObjectBorrow(sub_st);
             PyObject *dict = PyStackRef_AsPyObjectBorrow(dict_st);
 
-            assert(PyDict_CheckExact(dict));
+            assert(PyAnyDict_CheckExact(dict));
             STAT_INC(BINARY_OP, hit);
             PyObject *res_o;
             int rc = PyDict_GetItemRef(dict, sub, &res_o);
@@ -2940,7 +2940,7 @@ dummy_func(
             PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
             PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
 
-            assert(PyDict_CheckExact(right_o));
+            assert(PyAnyDict_CheckExact(right_o));
             STAT_INC(CONTAINS_OP, hit);
             int res = PyDict_Contains(right_o, left_o);
             if (res < 0) {
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 9dead4eecc7826..1b3de80e4443b1 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -5920,7 +5920,7 @@
             _PyStackRef nos;
             nos = stack_pointer[-2];
             PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-            if (!PyDict_CheckExact(o)) {
+            if (!PyAnyDict_CheckExact(o)) {
                 UOP_STAT_INC(uopcode, miss);
                 SET_CURRENT_CACHED_VALUES(0);
                 JUMP_TO_JUMP_TARGET();
@@ -5941,7 +5941,7 @@
             _PyStackRef _stack_item_0 = _tos_cache0;
             nos = stack_pointer[-1];
             PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-            if (!PyDict_CheckExact(o)) {
+            if (!PyAnyDict_CheckExact(o)) {
                 UOP_STAT_INC(uopcode, miss);
                 _tos_cache0 = _stack_item_0;
                 SET_CURRENT_CACHED_VALUES(1);
@@ -5964,7 +5964,7 @@
             _PyStackRef _stack_item_1 = _tos_cache1;
             nos = _stack_item_0;
             PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-            if (!PyDict_CheckExact(o)) {
+            if (!PyAnyDict_CheckExact(o)) {
                 UOP_STAT_INC(uopcode, miss);
                 _tos_cache1 = _stack_item_1;
                 _tos_cache0 = nos;
@@ -5987,7 +5987,7 @@
             _PyStackRef _stack_item_2 = _tos_cache2;
             nos = _stack_item_1;
             PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-            if (!PyDict_CheckExact(o)) {
+            if (!PyAnyDict_CheckExact(o)) {
                 UOP_STAT_INC(uopcode, miss);
                 _tos_cache2 = _stack_item_2;
                 _tos_cache1 = nos;
@@ -6009,7 +6009,7 @@
             _PyStackRef tos;
             tos = stack_pointer[-1];
             PyObject *o = PyStackRef_AsPyObjectBorrow(tos);
-            if (!PyDict_CheckExact(o)) {
+            if (!PyAnyDict_CheckExact(o)) {
                 UOP_STAT_INC(uopcode, miss);
                 SET_CURRENT_CACHED_VALUES(0);
                 JUMP_TO_JUMP_TARGET();
@@ -6029,7 +6029,7 @@
             _PyStackRef _stack_item_0 = _tos_cache0;
             tos = _stack_item_0;
             PyObject *o = PyStackRef_AsPyObjectBorrow(tos);
-            if (!PyDict_CheckExact(o)) {
+            if (!PyAnyDict_CheckExact(o)) {
                 UOP_STAT_INC(uopcode, miss);
                 _tos_cache0 = tos;
                 SET_CURRENT_CACHED_VALUES(1);
@@ -6049,7 +6049,7 @@
             _PyStackRef _stack_item_1 = _tos_cache1;
             tos = _stack_item_1;
             PyObject *o = PyStackRef_AsPyObjectBorrow(tos);
-            if (!PyDict_CheckExact(o)) {
+            if (!PyAnyDict_CheckExact(o)) {
                 UOP_STAT_INC(uopcode, miss);
                 _tos_cache1 = tos;
                 _tos_cache0 = _stack_item_0;
@@ -6072,7 +6072,7 @@
             _PyStackRef _stack_item_2 = _tos_cache2;
             tos = _stack_item_2;
             PyObject *o = PyStackRef_AsPyObjectBorrow(tos);
-            if (!PyDict_CheckExact(o)) {
+            if (!PyAnyDict_CheckExact(o)) {
                 UOP_STAT_INC(uopcode, miss);
                 _tos_cache2 = tos;
                 _tos_cache1 = _stack_item_1;
@@ -6102,7 +6102,7 @@
             dict_st = _stack_item_0;
             PyObject *sub = PyStackRef_AsPyObjectBorrow(sub_st);
             PyObject *dict = PyStackRef_AsPyObjectBorrow(dict_st);
-            assert(PyDict_CheckExact(dict));
+            assert(PyAnyDict_CheckExact(dict));
             STAT_INC(BINARY_OP, hit);
             PyObject *res_o;
             stack_pointer[0] = dict_st;
@@ -10393,7 +10393,7 @@
             left = _stack_item_0;
             PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
             PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
-            assert(PyDict_CheckExact(right_o));
+            assert(PyAnyDict_CheckExact(right_o));
             STAT_INC(CONTAINS_OP, hit);
             stack_pointer[0] = left;
             stack_pointer[1] = right;
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index 37fa6d679190dd..829a6988954e5f 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -642,7 +642,7 @@
             {
                 nos = stack_pointer[-2];
                 PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-                if (!PyDict_CheckExact(o)) {
+                if (!PyAnyDict_CheckExact(o)) {
                     UPDATE_MISS_STATS(BINARY_OP);
                     assert(_PyOpcode_Deopt[opcode] == (BINARY_OP));
                     JUMP_TO_PREDICTED(BINARY_OP);
@@ -655,7 +655,7 @@
                 dict_st = nos;
                 PyObject *sub = PyStackRef_AsPyObjectBorrow(sub_st);
                 PyObject *dict = PyStackRef_AsPyObjectBorrow(dict_st);
-                assert(PyDict_CheckExact(dict));
+                assert(PyAnyDict_CheckExact(dict));
                 STAT_INC(BINARY_OP, hit);
                 PyObject *res_o;
                 _PyFrame_SetStackPointer(frame, stack_pointer);
@@ -5139,7 +5139,7 @@
             {
                 tos = stack_pointer[-1];
                 PyObject *o = PyStackRef_AsPyObjectBorrow(tos);
-                if (!PyDict_CheckExact(o)) {
+                if (!PyAnyDict_CheckExact(o)) {
                     UPDATE_MISS_STATS(CONTAINS_OP);
                     assert(_PyOpcode_Deopt[opcode] == (CONTAINS_OP));
                     JUMP_TO_PREDICTED(CONTAINS_OP);
@@ -5152,7 +5152,7 @@
                 left = stack_pointer[-2];
                 PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
                 PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
-                assert(PyDict_CheckExact(right_o));
+                assert(PyAnyDict_CheckExact(right_o));
                 STAT_INC(CONTAINS_OP, hit);
                 _PyFrame_SetStackPointer(frame, stack_pointer);
                 int res = PyDict_Contains(right_o, left_o);
@@ -11479,7 +11479,7 @@
             {
                 nos = stack_pointer[-2];
                 PyObject *o = PyStackRef_AsPyObjectBorrow(nos);
-                if (!PyDict_CheckExact(o)) {
+                if (!PyAnyDict_CheckExact(o)) {
                     UPDATE_MISS_STATS(STORE_SUBSCR);
                     assert(_PyOpcode_Deopt[opcode] == (STORE_SUBSCR));
                     JUMP_TO_PREDICTED(STORE_SUBSCR);
diff --git a/Python/specialize.c b/Python/specialize.c
index 5ba016f83ea077..4d3ba4acbbf038 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -2287,7 +2287,7 @@ _Py_Specialize_BinaryOp(_PyStackRef lhs_st, _PyStackRef 
rhs_st, _Py_CODEUNIT *in
                     }
                 }
             }
-            if (PyDict_CheckExact(lhs)) {
+            if (PyAnyDict_CheckExact(lhs)) {
                 specialize(instr, BINARY_OP_SUBSCR_DICT);
                 return;
             }
@@ -2767,7 +2767,7 @@ _Py_Specialize_ContainsOp(_PyStackRef value_st, 
_Py_CODEUNIT *instr)
 
     assert(ENABLE_SPECIALIZATION);
     assert(_PyOpcode_Caches[CONTAINS_OP] == INLINE_CACHE_ENTRIES_COMPARE_OP);
-    if (PyDict_CheckExact(value)) {
+    if (PyAnyDict_CheckExact(value)) {
         specialize(instr, CONTAINS_OP_DICT);
         return;
     }

_______________________________________________
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]

Reply via email to