Skip to content

Commit 11761fc

Browse files
Eclips4mpage
authored andcommitted
pythongh-115999: Add free-threaded specialization for UNPACK_SEQUENCE (python#126600)
Add free-threaded specialization for `UNPACK_SEQUENCE` opcode. `UNPACK_SEQUENCE_TUPLE/UNPACK_SEQUENCE_TWO_TUPLE` are already thread safe since tuples are immutable. `UNPACK_SEQUENCE_LIST` is not thread safe because of nature of lists (there is nothing preventing another thread from adding items to or removing them the list while the instruction is executing). To achieve thread safety we add a critical section to the implementation of `UNPACK_SEQUENCE_LIST`, especially around the parts where we check the size of the list and push items onto the stack. --------- Co-authored-by: Matt Page <mpage@meta.com> Co-authored-by: mpage <mpage@cs.stanford.edu>
1 parent 185c5c4 commit 11761fc

File tree

7 files changed

+103
-30
lines changed

7 files changed

+103
-30
lines changed

Include/internal/pycore_opcode_metadata.h

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

Include/internal/pycore_uop_metadata.h

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

Lib/test/test_opcache.py

+31
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,37 @@ def to_bool_str():
13391339
self.assert_specialized(to_bool_str, "TO_BOOL_STR")
13401340
self.assert_no_opcode(to_bool_str, "TO_BOOL")
13411341

1342+
@cpython_only
1343+
@requires_specialization_ft
1344+
def test_unpack_sequence(self):
1345+
def f():
1346+
for _ in range(100):
1347+
a, b = 1, 2
1348+
self.assertEqual(a, 1)
1349+
self.assertEqual(b, 2)
1350+
1351+
f()
1352+
self.assert_specialized(f, "UNPACK_SEQUENCE_TWO_TUPLE")
1353+
self.assert_no_opcode(f, "UNPACK_SEQUENCE")
1354+
1355+
def g():
1356+
for _ in range(100):
1357+
a, = 1,
1358+
self.assertEqual(a, 1)
1359+
1360+
g()
1361+
self.assert_specialized(g, "UNPACK_SEQUENCE_TUPLE")
1362+
self.assert_no_opcode(g, "UNPACK_SEQUENCE")
1363+
1364+
def x():
1365+
for _ in range(100):
1366+
a, b = [1, 2]
1367+
self.assertEqual(a, 1)
1368+
self.assertEqual(b, 2)
1369+
1370+
x()
1371+
self.assert_specialized(x, "UNPACK_SEQUENCE_LIST")
1372+
self.assert_no_opcode(x, "UNPACK_SEQUENCE")
13421373

13431374
if __name__ == "__main__":
13441375
unittest.main()

Python/bytecodes.c

+16-4
Original file line numberDiff line numberDiff line change
@@ -1381,15 +1381,15 @@ dummy_func(
13811381
};
13821382

13831383
specializing op(_SPECIALIZE_UNPACK_SEQUENCE, (counter/1, seq -- seq)) {
1384-
#if ENABLE_SPECIALIZATION
1384+
#if ENABLE_SPECIALIZATION_FT
13851385
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
13861386
next_instr = this_instr;
13871387
_Py_Specialize_UnpackSequence(seq, next_instr, oparg);
13881388
DISPATCH_SAME_OPARG();
13891389
}
13901390
OPCODE_DEFERRED_INC(UNPACK_SEQUENCE);
13911391
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
1392-
#endif /* ENABLE_SPECIALIZATION */
1392+
#endif /* ENABLE_SPECIALIZATION_FT */
13931393
(void)seq;
13941394
(void)counter;
13951395
}
@@ -1429,12 +1429,24 @@ dummy_func(
14291429
inst(UNPACK_SEQUENCE_LIST, (unused/1, seq -- values[oparg])) {
14301430
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
14311431
DEOPT_IF(!PyList_CheckExact(seq_o));
1432-
DEOPT_IF(PyList_GET_SIZE(seq_o) != oparg);
1432+
#ifdef Py_GIL_DISABLED
1433+
PyCriticalSection cs;
1434+
PyCriticalSection_Begin(&cs, seq_o);
1435+
#endif
1436+
if (PyList_GET_SIZE(seq_o) != oparg) {
1437+
#ifdef Py_GIL_DISABLED
1438+
PyCriticalSection_End(&cs);
1439+
#endif
1440+
DEOPT_IF(true);
1441+
}
14331442
STAT_INC(UNPACK_SEQUENCE, hit);
14341443
PyObject **items = _PyList_ITEMS(seq_o);
14351444
for (int i = oparg; --i >= 0; ) {
14361445
*values++ = PyStackRef_FromPyObjectNew(items[i]);
14371446
}
1447+
#ifdef Py_GIL_DISABLED
1448+
PyCriticalSection_End(&cs);
1449+
#endif
14381450
DECREF_INPUTS();
14391451
}
14401452

@@ -2525,7 +2537,7 @@ dummy_func(
25252537
}
25262538
OPCODE_DEFERRED_INC(CONTAINS_OP);
25272539
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
2528-
#endif /* ENABLE_SPECIALIZATION */
2540+
#endif /* ENABLE_SPECIALIZATION_FT */
25292541
}
25302542

25312543
macro(CONTAINS_OP) = _SPECIALIZE_CONTAINS_OP + _CONTAINS_OP;

Python/executor_cases.c.h

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

Python/generated_cases.c.h

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

Python/specialize.c

+12-18
Original file line numberDiff line numberDiff line change
@@ -2487,39 +2487,33 @@ _Py_Specialize_UnpackSequence(_PyStackRef seq_st, _Py_CODEUNIT *instr, int oparg
24872487
{
24882488
PyObject *seq = PyStackRef_AsPyObjectBorrow(seq_st);
24892489

2490-
assert(ENABLE_SPECIALIZATION);
2490+
assert(ENABLE_SPECIALIZATION_FT);
24912491
assert(_PyOpcode_Caches[UNPACK_SEQUENCE] ==
24922492
INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE);
2493-
_PyUnpackSequenceCache *cache = (_PyUnpackSequenceCache *)(instr + 1);
24942493
if (PyTuple_CheckExact(seq)) {
24952494
if (PyTuple_GET_SIZE(seq) != oparg) {
24962495
SPECIALIZATION_FAIL(UNPACK_SEQUENCE, SPEC_FAIL_EXPECTED_ERROR);
2497-
goto failure;
2496+
unspecialize(instr);
2497+
return;
24982498
}
24992499
if (PyTuple_GET_SIZE(seq) == 2) {
2500-
instr->op.code = UNPACK_SEQUENCE_TWO_TUPLE;
2501-
goto success;
2500+
specialize(instr, UNPACK_SEQUENCE_TWO_TUPLE);
2501+
return;
25022502
}
2503-
instr->op.code = UNPACK_SEQUENCE_TUPLE;
2504-
goto success;
2503+
specialize(instr, UNPACK_SEQUENCE_TUPLE);
2504+
return;
25052505
}
25062506
if (PyList_CheckExact(seq)) {
25072507
if (PyList_GET_SIZE(seq) != oparg) {
25082508
SPECIALIZATION_FAIL(UNPACK_SEQUENCE, SPEC_FAIL_EXPECTED_ERROR);
2509-
goto failure;
2509+
unspecialize(instr);
2510+
return;
25102511
}
2511-
instr->op.code = UNPACK_SEQUENCE_LIST;
2512-
goto success;
2512+
specialize(instr, UNPACK_SEQUENCE_LIST);
2513+
return;
25132514
}
25142515
SPECIALIZATION_FAIL(UNPACK_SEQUENCE, unpack_sequence_fail_kind(seq));
2515-
failure:
2516-
STAT_INC(UNPACK_SEQUENCE, failure);
2517-
instr->op.code = UNPACK_SEQUENCE;
2518-
cache->counter = adaptive_counter_backoff(cache->counter);
2519-
return;
2520-
success:
2521-
STAT_INC(UNPACK_SEQUENCE, success);
2522-
cache->counter = adaptive_counter_cooldown();
2516+
unspecialize(instr);
25232517
}
25242518

25252519
#ifdef Py_STATS

0 commit comments

Comments
 (0)