Skip to content

Commit 62b0307

Browse files
committed
gh-115999: Add free-threaded specialization for TO_BOOL
* None / bool / int / str are immutable types, so they is thread-safe. * list is mutable, but by using ``PyList_GET_SIZE`` we can make it as thread-safe.
1 parent 6293d00 commit 62b0307

File tree

5 files changed

+125
-69
lines changed

5 files changed

+125
-69
lines changed

Lib/test/test_opcache.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,72 @@ def g():
12551255
self.assert_specialized(g, "CONTAINS_OP_SET")
12561256
self.assert_no_opcode(g, "CONTAINS_OP")
12571257

1258+
@cpython_only
1259+
@requires_specialization_ft
1260+
def test_to_bool(self):
1261+
def to_bool_bool():
1262+
true_cnt, false_cnt = 0, 0
1263+
elems = [e % 2 == 0 for e in range(100)]
1264+
for e in elems:
1265+
if e:
1266+
true_cnt += 1
1267+
else:
1268+
false_cnt -= 1
1269+
self.assertEqual(true_cnt, 50)
1270+
self.assertEqual(false_cnt, -50)
1271+
1272+
to_bool_bool()
1273+
self.assert_specialized(to_bool_bool, "TO_BOOL_BOOL")
1274+
self.assert_no_opcode(to_bool_bool, "TO_BOOL")
1275+
1276+
def to_bool_int():
1277+
count = 0
1278+
for i in range(100):
1279+
if i:
1280+
count += 1
1281+
else:
1282+
count -= 1
1283+
self.assertEqual(count, 98)
1284+
1285+
to_bool_int()
1286+
self.assert_specialized(to_bool_int, "TO_BOOL_INT")
1287+
self.assert_no_opcode(to_bool_int, "TO_BOOL")
1288+
1289+
def to_bool_list():
1290+
count = 0
1291+
elems = [1, 2, 3]
1292+
while elems:
1293+
count += elems.pop()
1294+
self.assertEqual(elems, [])
1295+
self.assertEqual(count, 6)
1296+
1297+
to_bool_list()
1298+
self.assert_specialized(to_bool_list, "TO_BOOL_LIST")
1299+
self.assert_no_opcode(to_bool_list, "TO_BOOL")
1300+
1301+
def to_bool_none():
1302+
count = 0
1303+
elems = [None, None, None, None]
1304+
for e in elems:
1305+
if not e:
1306+
count += 1
1307+
self.assertEqual(count, len(elems))
1308+
1309+
to_bool_none()
1310+
self.assert_specialized(to_bool_none, "TO_BOOL_NONE")
1311+
self.assert_no_opcode(to_bool_none, "TO_BOOL")
1312+
1313+
def to_bool_str():
1314+
count = 0
1315+
elems = ["", "foo", ""]
1316+
for e in elems:
1317+
if e:
1318+
count += 1
1319+
self.assertEqual(count, 1)
1320+
1321+
to_bool_str()
1322+
self.assert_specialized(to_bool_str, "TO_BOOL_STR")
1323+
self.assert_no_opcode(to_bool_str, "TO_BOOL")
12581324

12591325

12601326
if __name__ == "__main__":

Python/bytecodes.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,15 +391,15 @@ dummy_func(
391391
};
392392

393393
specializing op(_SPECIALIZE_TO_BOOL, (counter/1, value -- value)) {
394-
#if ENABLE_SPECIALIZATION
394+
#if ENABLE_SPECIALIZATION_FT
395395
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
396396
next_instr = this_instr;
397397
_Py_Specialize_ToBool(value, next_instr);
398398
DISPATCH_SAME_OPARG();
399399
}
400400
OPCODE_DEFERRED_INC(TO_BOOL);
401401
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
402-
#endif /* ENABLE_SPECIALIZATION */
402+
#endif /* ENABLE_SPECIALIZATION_FT */
403403
}
404404

405405
op(_TO_BOOL, (value -- res)) {
@@ -435,7 +435,7 @@ dummy_func(
435435
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
436436
EXIT_IF(!PyList_CheckExact(value_o));
437437
STAT_INC(TO_BOOL, hit);
438-
res = Py_SIZE(value_o) ? PyStackRef_True : PyStackRef_False;
438+
res = PyList_GET_SIZE(value_o) ? PyStackRef_True : PyStackRef_False;
439439
DECREF_INPUTS();
440440
}
441441

Python/executor_cases.c.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/specialize.c

Lines changed: 52 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2663,101 +2663,91 @@ _Py_Specialize_Send(_PyStackRef receiver_st, _Py_CODEUNIT *instr)
26632663
cache->counter = adaptive_counter_cooldown();
26642664
}
26652665

2666+
static int
2667+
to_bool_fail_kind(PyObject *value)
2668+
{
2669+
if (PyByteArray_CheckExact(value)) {
2670+
return SPEC_FAIL_TO_BOOL_BYTEARRAY;
2671+
}
2672+
if (PyBytes_CheckExact(value)) {
2673+
return SPEC_FAIL_TO_BOOL_BYTES;
2674+
}
2675+
if (PyDict_CheckExact(value)) {
2676+
return SPEC_FAIL_TO_BOOL_DICT;
2677+
}
2678+
if (PyFloat_CheckExact(value)) {
2679+
return SPEC_FAIL_TO_BOOL_FLOAT;
2680+
}
2681+
if (PyMemoryView_Check(value)) {
2682+
return SPEC_FAIL_TO_BOOL_MEMORY_VIEW;
2683+
}
2684+
if (PyAnySet_CheckExact(value)) {
2685+
return SPEC_FAIL_TO_BOOL_SET;
2686+
}
2687+
if (PyTuple_CheckExact(value)) {
2688+
return SPEC_FAIL_TO_BOOL_TUPLE;
2689+
}
2690+
return SPEC_FAIL_OTHER;
2691+
}
2692+
26662693
void
26672694
_Py_Specialize_ToBool(_PyStackRef value_o, _Py_CODEUNIT *instr)
26682695
{
2669-
assert(ENABLE_SPECIALIZATION);
2696+
assert(ENABLE_SPECIALIZATION_FT);
26702697
assert(_PyOpcode_Caches[TO_BOOL] == INLINE_CACHE_ENTRIES_TO_BOOL);
26712698
_PyToBoolCache *cache = (_PyToBoolCache *)(instr + 1);
26722699
PyObject *value = PyStackRef_AsPyObjectBorrow(value_o);
26732700
if (PyBool_Check(value)) {
2674-
instr->op.code = TO_BOOL_BOOL;
2675-
goto success;
2701+
specialize(instr, TO_BOOL_BOOL);
2702+
return;
26762703
}
26772704
if (PyLong_CheckExact(value)) {
2678-
instr->op.code = TO_BOOL_INT;
2679-
goto success;
2705+
specialize(instr, TO_BOOL_INT);
2706+
return;
26802707
}
26812708
if (PyList_CheckExact(value)) {
2682-
instr->op.code = TO_BOOL_LIST;
2683-
goto success;
2709+
specialize(instr, TO_BOOL_LIST);
2710+
return;
26842711
}
26852712
if (Py_IsNone(value)) {
2686-
instr->op.code = TO_BOOL_NONE;
2687-
goto success;
2713+
specialize(instr, TO_BOOL_NONE);
2714+
return;
26882715
}
26892716
if (PyUnicode_CheckExact(value)) {
2690-
instr->op.code = TO_BOOL_STR;
2691-
goto success;
2717+
specialize(instr, TO_BOOL_STR);
2718+
return;
26922719
}
26932720
if (PyType_HasFeature(Py_TYPE(value), Py_TPFLAGS_HEAPTYPE)) {
26942721
PyNumberMethods *nb = Py_TYPE(value)->tp_as_number;
26952722
if (nb && nb->nb_bool) {
2696-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_NUMBER);
2697-
goto failure;
2723+
unspecialize(instr, SPEC_FAIL_TO_BOOL_NUMBER);
2724+
return;
26982725
}
26992726
PyMappingMethods *mp = Py_TYPE(value)->tp_as_mapping;
27002727
if (mp && mp->mp_length) {
2701-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_MAPPING);
2702-
goto failure;
2728+
unspecialize(instr, SPEC_FAIL_TO_BOOL_MAPPING);
2729+
return;
27032730
}
27042731
PySequenceMethods *sq = Py_TYPE(value)->tp_as_sequence;
27052732
if (sq && sq->sq_length) {
2706-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_SEQUENCE);
2707-
goto failure;
2733+
unspecialize(instr, SPEC_FAIL_TO_BOOL_SEQUENCE);
2734+
return;
27082735
}
27092736
if (!PyUnstable_Type_AssignVersionTag(Py_TYPE(value))) {
2710-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_OUT_OF_VERSIONS);
2711-
goto failure;
2737+
unspecialize(instr, SPEC_FAIL_OUT_OF_VERSIONS);
2738+
return;
27122739
}
2713-
uint32_t version = type_get_version(Py_TYPE(value), TO_BOOL);
2740+
uint32_t version = FT_ATOMIC_LOAD_UINT32_RELAXED(Py_TYPE(value)->tp_version_tag);
27142741
if (version == 0) {
2715-
goto failure;
2742+
unspecialize(instr, SPEC_FAIL_OUT_OF_VERSIONS);
2743+
return;
27162744
}
2717-
instr->op.code = TO_BOOL_ALWAYS_TRUE;
2745+
specialize(instr, TO_BOOL_ALWAYS_TRUE);
27182746
write_u32(cache->version, version);
27192747
assert(version);
2720-
goto success;
2721-
}
2722-
#ifdef Py_STATS
2723-
if (PyByteArray_CheckExact(value)) {
2724-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_BYTEARRAY);
2725-
goto failure;
2726-
}
2727-
if (PyBytes_CheckExact(value)) {
2728-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_BYTES);
2729-
goto failure;
2730-
}
2731-
if (PyDict_CheckExact(value)) {
2732-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_DICT);
2733-
goto failure;
2734-
}
2735-
if (PyFloat_CheckExact(value)) {
2736-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_FLOAT);
2737-
goto failure;
2738-
}
2739-
if (PyMemoryView_Check(value)) {
2740-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_MEMORY_VIEW);
2741-
goto failure;
2742-
}
2743-
if (PyAnySet_CheckExact(value)) {
2744-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_SET);
2745-
goto failure;
2746-
}
2747-
if (PyTuple_CheckExact(value)) {
2748-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_TO_BOOL_TUPLE);
2749-
goto failure;
2748+
return;
27502749
}
2751-
SPECIALIZATION_FAIL(TO_BOOL, SPEC_FAIL_OTHER);
2752-
#endif // Py_STATS
2753-
failure:
2754-
STAT_INC(TO_BOOL, failure);
2755-
instr->op.code = TO_BOOL;
2756-
cache->counter = adaptive_counter_backoff(cache->counter);
2757-
return;
2758-
success:
2759-
STAT_INC(TO_BOOL, success);
2760-
cache->counter = adaptive_counter_cooldown();
2750+
unspecialize(instr, to_bool_fail_kind(value));
27612751
}
27622752

27632753
static int

0 commit comments

Comments
 (0)