Skip to content

Commit ac9cdb3

Browse files
corona10ebonnal
authored andcommitted
pythongh-115999: Add free-threaded specialization for CONTAINS_OP (pythongh-126450)
- 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.
1 parent 0112e78 commit ac9cdb3

File tree

4 files changed

+29
-6
lines changed

4 files changed

+29
-6
lines changed

Diff for: Lib/test/test_dis.py

+21
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,27 @@ def test_call_specialize(self):
13351335
got = self.get_disassembly(co, adaptive=True)
13361336
self.do_disassembly_compare(got, call_quicken)
13371337

1338+
@cpython_only
1339+
@requires_specialization_ft
1340+
def test_contains_specialize(self):
1341+
contains_op_quicken = """\
1342+
0 RESUME_CHECK 0
1343+
1344+
1 LOAD_NAME 0 (a)
1345+
LOAD_NAME 1 (b)
1346+
%s
1347+
RETURN_VALUE
1348+
"""
1349+
co_dict = compile('a in b', "<dict>", "eval")
1350+
self.code_quicken(lambda: exec(co_dict, {}, {'a': 1, 'b': {1: 5}}))
1351+
got = self.get_disassembly(co_dict, adaptive=True)
1352+
self.do_disassembly_compare(got, contains_op_quicken % "CONTAINS_OP_DICT 0 (in)")
1353+
1354+
co_set = compile('a in b', "<set>", "eval")
1355+
self.code_quicken(lambda: exec(co_set, {}, {'a': 1.0, 'b': {1, 2, 3}}))
1356+
got = self.get_disassembly(co_set, adaptive=True)
1357+
self.do_disassembly_compare(got, contains_op_quicken % "CONTAINS_OP_SET 0 (in)")
1358+
13381359
@cpython_only
13391360
@requires_specialization
13401361
def test_loop_quicken(self):

Diff for: Python/bytecodes.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -2508,7 +2508,7 @@ dummy_func(
25082508
}
25092509

25102510
specializing op(_SPECIALIZE_CONTAINS_OP, (counter/1, left, right -- left, right)) {
2511-
#if ENABLE_SPECIALIZATION
2511+
#if ENABLE_SPECIALIZATION_FT
25122512
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
25132513
next_instr = this_instr;
25142514
_Py_Specialize_ContainsOp(right, next_instr);

Diff for: Python/generated_cases.c.h

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Python/specialize.c

+6-4
Original file line numberDiff line numberDiff line change
@@ -2747,25 +2747,27 @@ _Py_Specialize_ContainsOp(_PyStackRef value_st, _Py_CODEUNIT *instr)
27472747
{
27482748
PyObject *value = PyStackRef_AsPyObjectBorrow(value_st);
27492749

2750-
assert(ENABLE_SPECIALIZATION);
2750+
assert(ENABLE_SPECIALIZATION_FT);
27512751
assert(_PyOpcode_Caches[CONTAINS_OP] == INLINE_CACHE_ENTRIES_COMPARE_OP);
2752+
uint8_t specialized_op;
27522753
_PyContainsOpCache *cache = (_PyContainsOpCache *)(instr + 1);
27532754
if (PyDict_CheckExact(value)) {
2754-
instr->op.code = CONTAINS_OP_DICT;
2755+
specialized_op = CONTAINS_OP_DICT;
27552756
goto success;
27562757
}
27572758
if (PySet_CheckExact(value) || PyFrozenSet_CheckExact(value)) {
2758-
instr->op.code = CONTAINS_OP_SET;
2759+
specialized_op = CONTAINS_OP_SET;
27592760
goto success;
27602761
}
27612762

27622763
SPECIALIZATION_FAIL(CONTAINS_OP, containsop_fail_kind(value));
27632764
STAT_INC(CONTAINS_OP, failure);
2764-
instr->op.code = CONTAINS_OP;
2765+
SET_OPCODE_OR_RETURN(instr, CONTAINS_OP);
27652766
cache->counter = adaptive_counter_backoff(cache->counter);
27662767
return;
27672768
success:
27682769
STAT_INC(CONTAINS_OP, success);
2770+
SET_OPCODE_OR_RETURN(instr, specialized_op);
27692771
cache->counter = adaptive_counter_cooldown();
27702772
}
27712773

0 commit comments

Comments
 (0)