]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-115999: Add free-threaded specialization for CONTAINS_OP (gh-126450)
authorDonghee Na <donghee.na@python.org>
Wed, 6 Nov 2024 03:35:10 +0000 (12:35 +0900)
committerGitHub <noreply@github.com>
Wed, 6 Nov 2024 03:35:10 +0000 (03:35 +0000)
- The specialization logic determines the appropriate specialization using only the operand's type, which is safe to read non-atomically (changing it requires stopping the world). We are guaranteed that the type will not change in between when it is checked and when we specialize the bytecode because the types involved are immutable (you cannot assign to `__class__` for exact instances of `dict`, `set`, or `frozenset`). The bytecode is mutated atomically using helpers.
- The specialized instructions rely on the operand type not changing in between the `DEOPT_IF` checks and the calls to the appropriate type-specific helpers (e.g. `_PySet_Contains`). This is a correctness requirement in the default builds and there are no changes to the opcodes in the free-threaded builds that would invalidate this.

Lib/test/test_dis.py
Python/bytecodes.c
Python/generated_cases.c.h
Python/specialize.c

index a991c67fca46bea516a9fb9f2530d6770199b5a7..337ee3bbb05136bcd99f44ec9072250cc8e5dc70 100644 (file)
@@ -1335,6 +1335,27 @@ class DisTests(DisTestBase):
         got = self.get_disassembly(co, adaptive=True)
         self.do_disassembly_compare(got, call_quicken)
 
+    @cpython_only
+    @requires_specialization_ft
+    def test_contains_specialize(self):
+        contains_op_quicken = """\
+  0           RESUME_CHECK             0
+
+  1           LOAD_NAME                0 (a)
+              LOAD_NAME                1 (b)
+              %s
+              RETURN_VALUE
+"""
+        co_dict = compile('a in b', "<dict>", "eval")
+        self.code_quicken(lambda: exec(co_dict, {}, {'a': 1, 'b': {1: 5}}))
+        got = self.get_disassembly(co_dict, adaptive=True)
+        self.do_disassembly_compare(got, contains_op_quicken % "CONTAINS_OP_DICT         0 (in)")
+
+        co_set = compile('a in b', "<set>", "eval")
+        self.code_quicken(lambda: exec(co_set, {}, {'a': 1.0, 'b': {1, 2, 3}}))
+        got = self.get_disassembly(co_set, adaptive=True)
+        self.do_disassembly_compare(got, contains_op_quicken % "CONTAINS_OP_SET          0 (in)")
+
     @cpython_only
     @requires_specialization
     def test_loop_quicken(self):
index 8c52db6ab684369579b3d9a7c0a80da726caa78d..7ae0f20369641a70da1e3ad3d73ac2539efedea5 100644 (file)
@@ -2508,7 +2508,7 @@ dummy_func(
         }
 
         specializing op(_SPECIALIZE_CONTAINS_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_ContainsOp(right, next_instr);
index d346875ea4455f0780f592745c457db4c4452bdb..03b4d2224922f0e3ae0e1f282a2f572744e22dea 100644 (file)
                 right = stack_pointer[-1];
                 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);
index 86cb997ca2ced3adea1b96efb55907b68f51294f..17e661b2bd3c769a9b1460342e4c0c87ac640d3c 100644 (file)
@@ -2747,25 +2747,27 @@ _Py_Specialize_ContainsOp(_PyStackRef value_st, _Py_CODEUNIT *instr)
 {
     PyObject *value = PyStackRef_AsPyObjectBorrow(value_st);
 
-    assert(ENABLE_SPECIALIZATION);
+    assert(ENABLE_SPECIALIZATION_FT);
     assert(_PyOpcode_Caches[CONTAINS_OP] == INLINE_CACHE_ENTRIES_COMPARE_OP);
+    uint8_t specialized_op;
     _PyContainsOpCache *cache = (_PyContainsOpCache  *)(instr + 1);
     if (PyDict_CheckExact(value)) {
-        instr->op.code = CONTAINS_OP_DICT;
+        specialized_op = CONTAINS_OP_DICT;
         goto success;
     }
     if (PySet_CheckExact(value) || PyFrozenSet_CheckExact(value)) {
-        instr->op.code = CONTAINS_OP_SET;
+        specialized_op = CONTAINS_OP_SET;
         goto success;
     }
 
     SPECIALIZATION_FAIL(CONTAINS_OP, containsop_fail_kind(value));
     STAT_INC(CONTAINS_OP, failure);
-    instr->op.code = CONTAINS_OP;
+    SET_OPCODE_OR_RETURN(instr, CONTAINS_OP);
     cache->counter = adaptive_counter_backoff(cache->counter);
     return;
 success:
     STAT_INC(CONTAINS_OP, success);
+    SET_OPCODE_OR_RETURN(instr, specialized_op);
     cache->counter = adaptive_counter_cooldown();
 }