summaryrefslogtreecommitdiffstats
path: root/Modules/_testcapi
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2023-08-31 21:41:18 (GMT)
committerGitHub <noreply@github.com>2023-08-31 21:41:18 (GMT)
commit2bd960b57944107fbfbd8ff005b4223e1ea6555f (patch)
tree792ca10d0f2ed817b47df46085079b374bc043d7 /Modules/_testcapi
parentc6d56135e151a19c79d002cb1f1dbcd1c766c51d (diff)
downloadcpython-2bd960b57944107fbfbd8ff005b4223e1ea6555f.zip
cpython-2bd960b57944107fbfbd8ff005b4223e1ea6555f.tar.gz
cpython-2bd960b57944107fbfbd8ff005b4223e1ea6555f.tar.bz2
gh-108337: Add pyatomic.h header (#108701)
This adds a new header that provides atomic operations on common data types. The intention is that this will be exposed through Python.h, although that is not the case yet. The only immediate use is in the test file. Co-authored-by: Sam Gross <colesbury@gmail.com>
Diffstat (limited to 'Modules/_testcapi')
-rw-r--r--Modules/_testcapi/parts.h1
-rw-r--r--Modules/_testcapi/pyatomic.c180
2 files changed, 181 insertions, 0 deletions
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index 9c6d615..c162dbc 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -22,6 +22,7 @@ int _PyTestCapi_Init_Structmember(PyObject *module);
int _PyTestCapi_Init_Exceptions(PyObject *module);
int _PyTestCapi_Init_Code(PyObject *module);
int _PyTestCapi_Init_Buffer(PyObject *module);
+int _PyTestCapi_Init_PyAtomic(PyObject *module);
int _PyTestCapi_Init_PyOS(PyObject *module);
int _PyTestCapi_Init_Immortal(PyObject *module);
int _PyTestCapi_Init_GC(PyObject *mod);
diff --git a/Modules/_testcapi/pyatomic.c b/Modules/_testcapi/pyatomic.c
new file mode 100644
index 0000000..15602ce
--- /dev/null
+++ b/Modules/_testcapi/pyatomic.c
@@ -0,0 +1,180 @@
+/*
+ * C Extension module to smoke test pyatomic.h API.
+ *
+ * This only tests basic functionality, not any synchronizing ordering.
+ */
+
+/* Always enable assertions */
+#undef NDEBUG
+
+#include "Python.h"
+#include "cpython/pyatomic.h"
+#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_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;
+}
+
+// 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},
+ {NULL, NULL} /* sentinel */
+};
+
+int
+_PyTestCapi_Init_PyAtomic(PyObject *mod)
+{
+ if (PyModule_AddFunctions(mod, test_methods) < 0) {
+ return -1;
+ }
+ return 0;
+}