Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-142534: Avoid TSan warnings in dictobject.c
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.
  • Loading branch information
colesbury committed Dec 10, 2025
commit 0dae3baeb9775fc4fcb913ff2bf305e10c190362
11 changes: 11 additions & 0 deletions Include/cpython/pyatomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,17 @@ static inline void _Py_atomic_fence_release(void);

// --- aliases ---------------------------------------------------------------

// Compilers don't really support "consume" semantics, so we fake it. Use
// "acquire" with TSan to support false positives. Use "relaxed" otherwise,
// because CPUs on all platforms we support respect address dependencies without
// extra barriers.
// See 2.6.7 in https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2055r0.pdf
#if defined(_Py_THREAD_SANITIZER)
# define _Py_atomic_load_ptr_consume _Py_atomic_load_ptr_acquire
#else
# define _Py_atomic_load_ptr_consume _Py_atomic_load_ptr_relaxed
#endif

#if SIZEOF_LONG == 8
# define _Py_atomic_load_ulong(p) \
_Py_atomic_load_uint64((uint64_t *)p)
Expand Down
1 change: 0 additions & 1 deletion Include/cpython/pyatomic_gcc.h
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,6 @@ static inline void
_Py_atomic_store_llong_relaxed(long long *obj, long long value)
{ __atomic_store_n(obj, value, __ATOMIC_RELAXED); }


// --- _Py_atomic_load_ptr_acquire / _Py_atomic_store_ptr_release ------------

static inline void *
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_pyatomic_ft_wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ extern "C" {
_Py_atomic_store_ptr(&value, new_value)
#define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) \
_Py_atomic_load_ptr_acquire(&value)
#define FT_ATOMIC_LOAD_PTR_CONSUME(value) \
_Py_atomic_load_ptr_consume(&value)
#define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) \
_Py_atomic_load_uintptr_acquire(&value)
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) \
Expand Down
12 changes: 6 additions & 6 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@
void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
{
PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
PyObject *ep_key = FT_ATOMIC_LOAD_PTR_RELAXED(ep->me_key);
PyObject *ep_key = FT_ATOMIC_LOAD_PTR_CONSUME(ep->me_key);

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Windows / Build and test (x64)

'initializing': 'PyObject *' differs in levels of indirection from 'int' [D:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Windows / Build and test (x64)

'FT_ATOMIC_LOAD_PTR_CONSUME' undefined; assuming extern returning int [D:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

initialization of ‘PyObject *’ {aka ‘struct _object *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]

Check failure on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

implicit declaration of function ‘FT_ATOMIC_LOAD_PTR_CONSUME’; did you mean ‘FT_ATOMIC_LOAD_PTR_ACQUIRE’? [-Werror=implicit-function-declaration]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

initialization of ‘PyObject *’ {aka ‘struct _object *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]

Check failure on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

implicit declaration of function ‘FT_ATOMIC_LOAD_PTR_CONSUME’; did you mean ‘FT_ATOMIC_LOAD_PTR_ACQUIRE’? [-Werror=implicit-function-declaration]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Windows / Build and test (arm64)

'initializing': 'PyObject *' differs in levels of indirection from 'int' [C:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Windows / Build and test (arm64)

'FT_ATOMIC_LOAD_PTR_CONSUME' undefined; assuming extern returning int [C:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

initialization of ‘PyObject *’ {aka ‘struct _object *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]

Check failure on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

implicit declaration of function ‘FT_ATOMIC_LOAD_PTR_CONSUME’; did you mean ‘FT_ATOMIC_LOAD_PTR_ACQUIRE’? [-Werror=implicit-function-declaration]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

initialization of ‘PyObject *’ {aka ‘struct _object *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]

Check failure on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

implicit declaration of function ‘FT_ATOMIC_LOAD_PTR_CONSUME’; did you mean ‘FT_ATOMIC_LOAD_PTR_ACQUIRE’? [-Werror=implicit-function-declaration]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

initialization of ‘PyObject *’ {aka ‘struct _object *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]

Check failure on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

implicit declaration of function ‘FT_ATOMIC_LOAD_PTR_CONSUME’; did you mean ‘FT_ATOMIC_LOAD_PTR_ACQUIRE’? [-Werror=implicit-function-declaration]

Check warning on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

initialization of ‘PyObject *’ {aka ‘struct _object *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]

Check failure on line 1081 in Objects/dictobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

implicit declaration of function ‘FT_ATOMIC_LOAD_PTR_CONSUME’; did you mean ‘FT_ATOMIC_LOAD_PTR_ACQUIRE’? [-Werror=implicit-function-declaration]
assert(ep_key != NULL);
assert(PyUnicode_CheckExact(ep_key));
if (ep_key == key ||
Expand Down Expand Up @@ -1371,7 +1371,7 @@
void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
{
PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key);
PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key);
assert(startkey == NULL || PyUnicode_CheckExact(ep->me_key));
assert(!PyUnicode_CheckExact(key));

Expand Down Expand Up @@ -1414,7 +1414,7 @@
void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
{
PyDictUnicodeEntry *ep = &((PyDictUnicodeEntry *)ep0)[ix];
PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key);
PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key);
if (startkey == key) {
assert(PyUnicode_CheckExact(startkey));
return 1;
Expand Down Expand Up @@ -1450,7 +1450,7 @@
void *ep0, Py_ssize_t ix, PyObject *key, Py_hash_t hash)
{
PyDictKeyEntry *ep = &((PyDictKeyEntry *)ep0)[ix];
PyObject *startkey = _Py_atomic_load_ptr_relaxed(&ep->me_key);
PyObject *startkey = _Py_atomic_load_ptr_consume(&ep->me_key);
if (startkey == key) {
return 1;
}
Expand Down Expand Up @@ -5526,7 +5526,7 @@
k = _Py_atomic_load_ptr_acquire(&d->ma_keys);
assert(i >= 0);
if (_PyDict_HasSplitTable(d)) {
PyDictValues *values = _Py_atomic_load_ptr_relaxed(&d->ma_values);
PyDictValues *values = _Py_atomic_load_ptr_consume(&d->ma_values);
if (values == NULL) {
goto concurrent_modification;
}
Expand Down Expand Up @@ -7114,7 +7114,7 @@
Py_BEGIN_CRITICAL_SECTION(dict);

if (dict->ma_values == values && FT_ATOMIC_LOAD_UINT8(values->valid)) {
value = _Py_atomic_load_ptr_relaxed(&values->values[ix]);
value = _Py_atomic_load_ptr_consume(&values->values[ix]);
*attr = _Py_XNewRefWithLock(value);
success = true;
} else {
Expand Down
Loading