From 127ccd877d2b48905f3b445ab2c89a09dee919a8 Mon Sep 17 00:00:00 2001
From: Thomas Wouters <thomas@python.org>
Date: Fri, 1 Nov 2024 15:43:21 +0100
Subject: [PATCH 1/2] Add free-threaded specialization for COMPARE_OP, and
 tests for COMPARE_OP specialization in general.

---
 Lib/test/test_opcache.py   | 33 +++++++++++++++++++++++++++++++++
 Python/bytecodes.c         |  2 +-
 Python/generated_cases.c.h |  2 +-
 Python/specialize.c        | 16 +++++++---------
 4 files changed, 42 insertions(+), 11 deletions(-)

diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py
index 50b5f365165921..61e9070f9c9e73 100644
--- a/Lib/test/test_opcache.py
+++ b/Lib/test/test_opcache.py
@@ -1520,6 +1520,39 @@ def binary_subscr_str_int():
         self.assert_specialized(binary_subscr_str_int, "BINARY_SUBSCR_STR_INT")
         self.assert_no_opcode(binary_subscr_str_int, "BINARY_SUBSCR")
 
+    @cpython_only
+    @requires_specialization_ft
+    def test_compare_op(self):
+        def compare_op_int():
+            for _ in range(100):
+                a, b = 1, 2
+                c = a == b
+                self.assertFalse(c)
+
+        compare_op_int()
+        self.assert_specialized(compare_op_int, "COMPARE_OP_INT")
+        self.assert_no_opcode(compare_op_int, "COMPARE_OP")
+
+        def compare_op_float():
+            for _ in range(100):
+                a, b = 1.0, 2.0
+                c = a == b
+                self.assertFalse(c)
+
+        compare_op_float()
+        self.assert_specialized(compare_op_float, "COMPARE_OP_FLOAT")
+        self.assert_no_opcode(compare_op_float, "COMPARE_OP")
+
+        def compare_op_str():
+            for _ in range(100):
+                a, b = "spam", "ham"
+                c = a == b
+                self.assertFalse(c)
+
+        compare_op_str()
+        self.assert_specialized(compare_op_str, "COMPARE_OP_STR")
+        self.assert_no_opcode(compare_op_str, "COMPARE_OP")
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 3d280941b35244..7a411f4d47e26a 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -2404,7 +2404,7 @@ dummy_func(
         };
 
         specializing op(_SPECIALIZE_COMPARE_OP, (counter/1, left, right -- left, right)) {
-            #if ENABLE_SPECIALIZATION
+            #if ENABLE_SPECIALIZATION_FT
             if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
                 next_instr = this_instr;
                 _Py_Specialize_CompareOp(left, right, next_instr, oparg);
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index 33f32aba1e5145..c8e7d04e361556 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -3223,7 +3223,7 @@
                 left = stack_pointer[-2];
                 uint16_t counter = read_u16(&this_instr[1].cache);
                 (void)counter;
-                #if ENABLE_SPECIALIZATION
+                #if ENABLE_SPECIALIZATION_FT
                 if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
                     next_instr = this_instr;
                     _PyFrame_SetStackPointer(frame, stack_pointer);
diff --git a/Python/specialize.c b/Python/specialize.c
index d3fea717243847..b39fc7256455a5 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -2415,8 +2415,9 @@ _Py_Specialize_CompareOp(_PyStackRef lhs_st, _PyStackRef rhs_st, _Py_CODEUNIT *i
 {
     PyObject *lhs = PyStackRef_AsPyObjectBorrow(lhs_st);
     PyObject *rhs = PyStackRef_AsPyObjectBorrow(rhs_st);
+    uint8_t specialized_op;
 
-    assert(ENABLE_SPECIALIZATION);
+    assert(ENABLE_SPECIALIZATION_FT);
     assert(_PyOpcode_Caches[COMPARE_OP] == INLINE_CACHE_ENTRIES_COMPARE_OP);
     // All of these specializations compute boolean values, so they're all valid
     // regardless of the fifth-lowest oparg bit.
@@ -2426,12 +2427,12 @@ _Py_Specialize_CompareOp(_PyStackRef lhs_st, _PyStackRef rhs_st, _Py_CODEUNIT *i
         goto failure;
     }
     if (PyFloat_CheckExact(lhs)) {
-        instr->op.code = COMPARE_OP_FLOAT;
+        specialized_op = COMPARE_OP_FLOAT;
         goto success;
     }
     if (PyLong_CheckExact(lhs)) {
         if (_PyLong_IsCompact((PyLongObject *)lhs) && _PyLong_IsCompact((PyLongObject *)rhs)) {
-            instr->op.code = COMPARE_OP_INT;
+            specialized_op = COMPARE_OP_INT;
             goto success;
         }
         else {
@@ -2446,19 +2447,16 @@ _Py_Specialize_CompareOp(_PyStackRef lhs_st, _PyStackRef rhs_st, _Py_CODEUNIT *i
             goto failure;
         }
         else {
-            instr->op.code = COMPARE_OP_STR;
+            specialized_op = COMPARE_OP_STR;
             goto success;
         }
     }
     SPECIALIZATION_FAIL(COMPARE_OP, compare_op_fail_kind(lhs, rhs));
 failure:
-    STAT_INC(COMPARE_OP, failure);
-    instr->op.code = COMPARE_OP;
-    cache->counter = adaptive_counter_backoff(cache->counter);
+    unspecialize(instr);
     return;
 success:
-    STAT_INC(COMPARE_OP, success);
-    cache->counter = adaptive_counter_cooldown();
+    specialize(instr, specialized_op);
 }
 
 #ifdef Py_STATS

From 47785d84f018c9976741c06442edbc265ef8b344 Mon Sep 17 00:00:00 2001
From: Thomas Wouters <thomas@python.org>
Date: Wed, 11 Dec 2024 15:13:24 +0100
Subject: [PATCH 2/2] Drop now-unused variable.

---
 Python/specialize.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Python/specialize.c b/Python/specialize.c
index b39fc7256455a5..07a6079690b69b 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -2421,7 +2421,6 @@ _Py_Specialize_CompareOp(_PyStackRef lhs_st, _PyStackRef rhs_st, _Py_CODEUNIT *i
     assert(_PyOpcode_Caches[COMPARE_OP] == INLINE_CACHE_ENTRIES_COMPARE_OP);
     // All of these specializations compute boolean values, so they're all valid
     // regardless of the fifth-lowest oparg bit.
-    _PyCompareOpCache *cache = (_PyCompareOpCache *)(instr + 1);
     if (Py_TYPE(lhs) != Py_TYPE(rhs)) {
         SPECIALIZATION_FAIL(COMPARE_OP, compare_op_fail_kind(lhs, rhs));
         goto failure;