Skip to content

Commit b868f14

Browse files
[3.14] gh-142534: Avoid TSan warnings in dictobject.c (gh-142544) (gh-142603)
There are places we use "relaxed" loads where C11 requires "consume" or stronger. Unfortunately, compilers don't really implement "consume" so fake it for our use in a way that avoids upsetting TSan. (cherry picked from commit 0a62f82) Co-authored-by: Sam Gross <[email protected]>
1 parent 12d2b95 commit b868f14

File tree

3 files changed

+20
-6
lines changed

3 files changed

+20
-6
lines changed

Include/cpython/pyatomic.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,17 @@ static inline void _Py_atomic_fence_release(void);
591591

592592
// --- aliases ---------------------------------------------------------------
593593

594+
// Compilers don't really support "consume" semantics, so we fake it. Use
595+
// "acquire" with TSan to support false positives. Use "relaxed" otherwise,
596+
// because CPUs on all platforms we support respect address dependencies without
597+
// extra barriers.
598+
// See 2.6.7 in https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2055r0.pdf
599+
#if defined(_Py_THREAD_SANITIZER)
600+
# define _Py_atomic_load_ptr_consume _Py_atomic_load_ptr_acquire
601+
#else
602+
# define _Py_atomic_load_ptr_consume _Py_atomic_load_ptr_relaxed
603+
#endif
604+
594605
#if SIZEOF_LONG == 8
595606
# define _Py_atomic_load_ulong(p) \
596607
_Py_atomic_load_uint64((uint64_t *)p)

Include/internal/pycore_pyatomic_ft_wrappers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ extern "C" {
3131
_Py_atomic_store_ptr(&value, new_value)
3232
#define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) \
3333
_Py_atomic_load_ptr_acquire(&value)
34+
#define FT_ATOMIC_LOAD_PTR_CONSUME(value) \
35+
_Py_atomic_load_ptr_consume(&value)
3436
#define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) \
3537
_Py_atomic_load_uintptr_acquire(&value)
3638
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) \
@@ -123,6 +125,7 @@ extern "C" {
123125
#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) value
124126
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value
125127
#define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) value
128+
#define FT_ATOMIC_LOAD_PTR_CONSUME(value) value
126129
#define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) value
127130
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) value
128131
#define FT_ATOMIC_LOAD_UINT8(value) value

Objects/dictobject.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ compare_unicode_unicode(PyDictObject *mp, PyDictKeysObject *dk,
10821082
void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
10831083
{
10841084
PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
1085-
PyObject *ep_key = FT_ATOMIC_LOAD_PTR_RELAXED(ep->me_key);
1085+
PyObject *ep_key = FT_ATOMIC_LOAD_PTR_CONSUME(ep->me_key);
10861086
assert(ep_key != NULL);
10871087
assert(PyUnicode_CheckExact(ep_key));
10881088
if (ep_key == key ||
@@ -1375,7 +1375,7 @@ compare_unicode_generic_threadsafe(PyDictObject *mp, PyDictKeysObject *dk,
13751375
void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
13761376
{
13771377
PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
1378-
PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key);
1378+
PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key);
13791379
assert(startkey == NULL || PyUnicode_CheckExact(ep->me_key));
13801380
assert(!PyUnicode_CheckExact(key));
13811381

@@ -1418,7 +1418,7 @@ compare_unicode_unicode_threadsafe(PyDictObject *mp, PyDictKeysObject *dk,
14181418
void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
14191419
{
14201420
PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
1421-
PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key);
1421+
PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key);
14221422
if (startkey == key) {
14231423
assert(PyUnicode_CheckExact(startkey));
14241424
return 1;
@@ -1454,7 +1454,7 @@ compare_generic_threadsafe(PyDictObject *mp, PyDictKeysObject *dk,
14541454
void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
14551455
{
14561456
PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix];
1457-
PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key);
1457+
PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key);
14581458
if (startkey == key) {
14591459
return 1;
14601460
}
@@ -5519,7 +5519,7 @@ dictiter_iternext_threadsafe(PyDictObject *d, PyObject *self,
55195519
k = _Py_atomic_load_ptr_acquire(&d->ma_keys);
55205520
assert(i >= 0);
55215521
if (_PyDict_HasSplitTable(d)) {
5522-
PyDictValues *values = _Py_atomic_load_ptr_relaxed(&d->ma_values);
5522+
PyDictValues *values = _Py_atomic_load_ptr_consume(&d->ma_values);
55235523
if (values == NULL) {
55245524
goto concurrent_modification;
55255525
}
@@ -7111,7 +7111,7 @@ _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr
71117111
Py_BEGIN_CRITICAL_SECTION(dict);
71127112

71137113
if (dict->ma_values == values && FT_ATOMIC_LOAD_UINT8(values->valid)) {
7114-
value = _Py_atomic_load_ptr_relaxed(&values->values[ix]);
7114+
value = _Py_atomic_load_ptr_consume(&values->values[ix]);
71157115
*attr = _Py_XNewRefWithLock(value);
71167116
success = true;
71177117
} else {

0 commit comments

Comments
 (0)