0
0
mirror of https://github.com/python/cpython.git synced 2024-11-21 21:09:37 +01:00
cpython/Modules/_testcapi/pyatomic.c
Sam Gross 1d3cf79a50
gh-121368: Fix seq lock memory ordering in _PyType_Lookup (#121388)
The `_PySeqLock_EndRead` function needs an acquire fence to ensure that
the load of the sequence happens after any loads within the read side
critical section. The missing fence can trigger bugs on macOS arm64.

Additionally, we need a release fence in `_PySeqLock_LockWrite` to
ensure that the sequence update is visible before any modifications to
the cache entry.
2024-07-08 14:52:07 -04:00

193 lines
6.2 KiB
C

/*
* C Extension module to smoke test pyatomic.h API.
*
* This only tests basic functionality, not any synchronizing ordering.
*/
#include "parts.h"
// We define atomic bitwise operations on these types
#define FOR_BITWISE_TYPES(V) \
V(uint8, uint8_t) \
V(uint16, uint16_t) \
V(uint32, uint32_t) \
V(uint64, uint64_t) \
V(uintptr, uintptr_t)
// We define atomic addition on these types
#define FOR_ARITHMETIC_TYPES(V) \
FOR_BITWISE_TYPES(V) \
V(int, int) \
V(uint, unsigned int) \
V(int8, int8_t) \
V(int16, int16_t) \
V(int32, int32_t) \
V(int64, int64_t) \
V(intptr, intptr_t) \
V(ssize, Py_ssize_t)
// We define atomic load, store, exchange, and compare_exchange on these types
#define FOR_ALL_TYPES(V) \
FOR_ARITHMETIC_TYPES(V) \
V(ptr, void*)
#define IMPL_TEST_ADD(suffix, dtype) \
static PyObject * \
test_atomic_add_##suffix(PyObject *self, PyObject *obj) { \
dtype x = 0; \
assert(_Py_atomic_add_##suffix(&x, 1) == 0); \
assert(x == 1); \
assert(_Py_atomic_add_##suffix(&x, 2) == 1); \
assert(x == 3); \
assert(_Py_atomic_add_##suffix(&x, -2) == 3); \
assert(x == 1); \
assert(_Py_atomic_add_##suffix(&x, -1) == 1); \
assert(x == 0); \
assert(_Py_atomic_add_##suffix(&x, -1) == 0); \
assert(x == (dtype)-1); \
assert(_Py_atomic_add_##suffix(&x, -2) == (dtype)-1); \
assert(x == (dtype)-3); \
assert(_Py_atomic_add_##suffix(&x, 2) == (dtype)-3); \
assert(x == (dtype)-1); \
Py_RETURN_NONE; \
}
FOR_ARITHMETIC_TYPES(IMPL_TEST_ADD)
#define IMPL_TEST_COMPARE_EXCHANGE(suffix, dtype) \
static PyObject * \
test_atomic_compare_exchange_##suffix(PyObject *self, PyObject *obj) { \
dtype x = (dtype)0; \
dtype y = (dtype)1; \
dtype z = (dtype)2; \
assert(_Py_atomic_compare_exchange_##suffix(&x, &y, z) == 0); \
assert(x == 0); \
assert(y == 0); \
assert(_Py_atomic_compare_exchange_##suffix(&x, &y, z) == 1); \
assert(x == z); \
assert(y == 0); \
assert(_Py_atomic_compare_exchange_##suffix(&x, &y, z) == 0); \
assert(x == z); \
assert(y == z); \
Py_RETURN_NONE; \
}
FOR_ALL_TYPES(IMPL_TEST_COMPARE_EXCHANGE)
#define IMPL_TEST_EXCHANGE(suffix, dtype) \
static PyObject * \
test_atomic_exchange_##suffix(PyObject *self, PyObject *obj) { \
dtype x = (dtype)0; \
dtype y = (dtype)1; \
dtype z = (dtype)2; \
assert(_Py_atomic_exchange_##suffix(&x, y) == (dtype)0); \
assert(x == (dtype)1); \
assert(_Py_atomic_exchange_##suffix(&x, z) == (dtype)1); \
assert(x == (dtype)2); \
assert(_Py_atomic_exchange_##suffix(&x, y) == (dtype)2); \
assert(x == (dtype)1); \
Py_RETURN_NONE; \
}
FOR_ALL_TYPES(IMPL_TEST_EXCHANGE)
#define IMPL_TEST_LOAD_STORE(suffix, dtype) \
static PyObject * \
test_atomic_load_store_##suffix(PyObject *self, PyObject *obj) { \
dtype x = (dtype)0; \
dtype y = (dtype)1; \
dtype z = (dtype)2; \
assert(_Py_atomic_load_##suffix(&x) == (dtype)0); \
assert(x == (dtype)0); \
_Py_atomic_store_##suffix(&x, y); \
assert(_Py_atomic_load_##suffix(&x) == (dtype)1); \
assert(x == (dtype)1); \
_Py_atomic_store_##suffix##_relaxed(&x, z); \
assert(_Py_atomic_load_##suffix##_relaxed(&x) == (dtype)2); \
assert(x == (dtype)2); \
Py_RETURN_NONE; \
}
FOR_ALL_TYPES(IMPL_TEST_LOAD_STORE)
#define IMPL_TEST_AND_OR(suffix, dtype) \
static PyObject * \
test_atomic_and_or_##suffix(PyObject *self, PyObject *obj) { \
dtype x = (dtype)0; \
dtype y = (dtype)1; \
dtype z = (dtype)3; \
assert(_Py_atomic_or_##suffix(&x, z) == (dtype)0); \
assert(x == (dtype)3); \
assert(_Py_atomic_and_##suffix(&x, y) == (dtype)3); \
assert(x == (dtype)1); \
Py_RETURN_NONE; \
}
FOR_BITWISE_TYPES(IMPL_TEST_AND_OR)
static PyObject *
test_atomic_fences(PyObject *self, PyObject *obj) {
// Just make sure that the fences compile. We are not
// testing any synchronizing ordering.
_Py_atomic_fence_seq_cst();
_Py_atomic_fence_acquire();
_Py_atomic_fence_release();
Py_RETURN_NONE;
}
static PyObject *
test_atomic_release_acquire(PyObject *self, PyObject *obj) {
void *x = NULL;
void *y = &y;
assert(_Py_atomic_load_ptr_acquire(&x) == NULL);
_Py_atomic_store_ptr_release(&x, y);
assert(x == y);
assert(_Py_atomic_load_ptr_acquire(&x) == y);
Py_RETURN_NONE;
}
static PyObject *
test_atomic_load_store_int_release_acquire(PyObject *self, PyObject *obj) { \
int x = 0;
int y = 1;
int z = 2;
assert(_Py_atomic_load_int_acquire(&x) == 0);
_Py_atomic_store_int_release(&x, y);
assert(x == y);
assert(_Py_atomic_load_int_acquire(&x) == y);
_Py_atomic_store_int_release(&x, z);
assert(x == z);
assert(_Py_atomic_load_int_acquire(&x) == z);
Py_RETURN_NONE;
}
// NOTE: all tests should start with "test_atomic_" to be included
// in test_pyatomic.py
#define BIND_TEST_ADD(suffix, dtype) \
{"test_atomic_add_" #suffix, test_atomic_add_##suffix, METH_NOARGS},
#define BIND_TEST_COMPARE_EXCHANGE(suffix, dtype) \
{"test_atomic_compare_exchange_" #suffix, test_atomic_compare_exchange_##suffix, METH_NOARGS},
#define BIND_TEST_EXCHANGE(suffix, dtype) \
{"test_atomic_exchange_" #suffix, test_atomic_exchange_##suffix, METH_NOARGS},
#define BIND_TEST_LOAD_STORE(suffix, dtype) \
{"test_atomic_load_store_" #suffix, test_atomic_load_store_##suffix, METH_NOARGS},
#define BIND_TEST_AND_OR(suffix, dtype) \
{"test_atomic_and_or_" #suffix, test_atomic_and_or_##suffix, METH_NOARGS},
static PyMethodDef test_methods[] = {
FOR_ARITHMETIC_TYPES(BIND_TEST_ADD)
FOR_ALL_TYPES(BIND_TEST_COMPARE_EXCHANGE)
FOR_ALL_TYPES(BIND_TEST_EXCHANGE)
FOR_ALL_TYPES(BIND_TEST_LOAD_STORE)
FOR_BITWISE_TYPES(BIND_TEST_AND_OR)
{"test_atomic_fences", test_atomic_fences, METH_NOARGS},
{"test_atomic_release_acquire", test_atomic_release_acquire, METH_NOARGS},
{"test_atomic_load_store_int_release_acquire", test_atomic_load_store_int_release_acquire, METH_NOARGS},
{NULL, NULL} /* sentinel */
};
int
_PyTestCapi_Init_PyAtomic(PyObject *mod)
{
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}
return 0;
}