diff --git a/CMakeLists.txt b/CMakeLists.txt index fafa7ac7..cae401b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.18) -project(cereggii-dev) +project(cereggii) add_subdirectory(src) diff --git a/pyproject.toml b/pyproject.toml index c7f8dd6d..857ee1f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,6 +103,8 @@ ignore = [ "T201", # Import block is un-sorted or un-formatted "I001", + # Unnecessary `list` / `tuple` / `dict` call (rewrite as a literal) + "C408", ] unfixable = [ # Don't touch unused imports diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2879332d..d6f421c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,8 +20,6 @@ find_package(Python3 REQUIRED COMPONENTS Development.Module) # Add the module to compile Python3_add_library(_cereggii MODULE - "cereggii/cereggii.c" - "cereggii/atomic_ref.c" "cereggii/atomic_dict/atomic_dict.c" "cereggii/atomic_dict/blocks.c" "cereggii/atomic_dict/delete.c" @@ -32,6 +30,10 @@ Python3_add_library(_cereggii MODULE "cereggii/atomic_dict/node_ops.c" "cereggii/atomic_dict/node_sizes_table.c" "cereggii/atomic_dict/reservation_buffer.c" + "cereggii/atomic_int/atomic_int.c" + "cereggii/atomic_int/handle.c" + "cereggii/atomic_ref.c" + "cereggii/cereggii.c" ) # Install the module diff --git a/src/cereggii/__init__.py b/src/cereggii/__init__.py index d2d7007c..91c9695e 100644 --- a/src/cereggii/__init__.py +++ b/src/cereggii/__init__.py @@ -13,8 +13,10 @@ from .__about__ import __license__, __version__, __version_tuple__ # noqa: F401 from .atomic_dict import AtomicDict # noqa: F401 +from .atomic_int import AtomicInt, AtomicIntHandle # noqa: F401 from .atomic_ref import AtomicRef # noqa: F401 + if not getattr(sys.flags, "nogil", False): warnings.warn( "this library is meant to be used with nogil python: https://github.com/colesbury/nogil", stacklevel=1 diff --git a/src/cereggii/_cereggii.pyi b/src/cereggii/_cereggii.pyi index db219501..1b7d909c 100644 --- a/src/cereggii/_cereggii.pyi +++ b/src/cereggii/_cereggii.pyi @@ -1,9 +1,11 @@ -from typing import NewType +from typing import Callable, NewType, SupportsComplex, SupportsFloat, SupportsInt Key = NewType("Key", object) Value = NewType("Value", object) Cancel = NewType("Cancel", object) +Number = SupportsInt | SupportsFloat | SupportsComplex + class AtomicDict: """A thread-safe dictionary (hashmap), that's almost-lock-freeā„¢.""" @@ -128,8 +130,52 @@ class AtomicDict: class AtomicRef: """An object reference that may be updated atomically.""" - def __init__(self, reference: object): ... - def compare_and_set(self, expected: object, new: object) -> bool: ... + def __init__(self, reference: object | None = None): ... + def compare_and_set(self, expected: object, updated: object) -> bool: ... def get(self) -> object: ... - def get_and_set(self, new: object) -> object: ... - def set(self, new: object): ... # noqa: A003 + def get_and_set(self, updated: object) -> object: ... + def set(self, updated: object): ... # noqa: A003 + +class AtomicInt(int): + """An int that may be updated atomically.""" + + def __init__(self, initial_value: int | None = None): ... + def compare_and_set(self, expected: int, updated: int) -> bool: ... + def get(self) -> int: ... + def get_and_set(self, updated: int) -> int: ... + def set(self, updated: int) -> None: ... # noqa: A003 + def increment_and_get(self, amount: int | None = None) -> int: ... + def get_and_increment(self, amount: int | None = None) -> int: ... + def decrement_and_get(self, amount: int | None = None) -> int: ... + def get_and_decrement(self, amount: int | None = None) -> int: ... + def update_and_get(self, other: Callable[[int], int]) -> int: ... + def get_and_update(self, other: Callable[[int], int]) -> int: ... + def get_handle(self) -> AtomicIntHandle: ... + def __itruediv__(self, other): + raise NotImplementedError + def as_integer_ratio(self): + raise NotImplementedError + def bit_length(self): + raise NotImplementedError + def conjugate(self): + raise NotImplementedError + @classmethod + def from_bytes(cls, *args, **kwargs): + raise NotImplementedError + def to_bytes(self, *args, **kwargs): + raise NotImplementedError + @property + def denominator(self): + raise NotImplementedError + @property + def numerator(self): + raise NotImplementedError + @property + def imag(self): + raise NotImplementedError + @property + def real(self): + raise NotImplementedError + +class AtomicIntHandle(AtomicInt): + pass diff --git a/src/cereggii/atomic_int/__init__.py b/src/cereggii/atomic_int/__init__.py new file mode 100644 index 00000000..e43deb3f --- /dev/null +++ b/src/cereggii/atomic_int/__init__.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2023-present dpdani +# +# SPDX-License-Identifier: Apache-2.0 + +import warnings + + +try: + from cereggii import _cereggii +except ImportError as exc: # building sdist (without compiled modules) + + class AtomicInt: + def __init__(self): + print("dummy") + + class AtomicIntHandle: + def __init__(self): + print("dummy") + + warnings.warn(str(exc), stacklevel=1) # "UserWarning: No module named 'cereggii'" is expected during sdist build + +else: + AtomicInt = _cereggii.AtomicInt + AtomicIntHandle = _cereggii.AtomicIntHandle diff --git a/src/cereggii/atomic_int/atomic_int.c b/src/cereggii/atomic_int/atomic_int.c new file mode 100644 index 00000000..69e79ce0 --- /dev/null +++ b/src/cereggii/atomic_int/atomic_int.c @@ -0,0 +1,1343 @@ +// SPDX-FileCopyrightText: 2023-present dpdani +// +// SPDX-License-Identifier: Apache-2.0 + +#include "atomic_int.h" +#include "atomic_int_internal.h" + +#include "pyhash.h" + + +inline int +AtomicInt_ConvertToCLongOrSetException(PyObject *py_integer /* borrowed */, int64_t *integer) +{ + if (py_integer == NULL) + return 0; + + if (!PyLong_Check(py_integer)) { + PyErr_SetObject( + PyExc_TypeError, + PyUnicode_FromFormat("not isinstance(%R, int)", py_integer) + ); + return 0; + } + + int overflow; + *integer = PyLong_AsLongAndOverflow(py_integer, &overflow); + if (PyErr_Occurred()) + return 0; + + if (overflow) { + PyErr_SetObject( + PyExc_OverflowError, + PyUnicode_FromFormat("%R > %ld == (2 ** 63) - 1 or %R < %ld", py_integer, INT64_MAX, py_integer, INT64_MIN) + ); // todo: docs link + return 0; + } + + return 1; +} + +inline int +AtomicInt_AddOrSetOverflow(int64_t current, int64_t to_add, int64_t *result) +{ + int overflowed = __builtin_saddl_overflow(current, to_add, result); + + if (overflowed) { + PyErr_SetObject( + PyExc_OverflowError, + PyUnicode_FromFormat("%ld + %ld > %ld == (2 ** 63) - 1 " + "or %ld + %ld < %ld", current, to_add, INT64_MAX, current, to_add, INT64_MIN) + ); + } + + return overflowed; +} + +inline int +AtomicInt_SubOrSetOverflow(int64_t current, int64_t to_sub, int64_t *result) +{ + int overflowed = __builtin_ssubl_overflow(current, to_sub, result); + + if (overflowed) { + PyErr_SetObject( + PyExc_OverflowError, + PyUnicode_FromFormat("%ld - %ld > %ld == (2 ** 63) - 1 " + "or %ld - %ld < %ld", current, to_sub, INT64_MAX, current, to_sub, INT64_MIN) + ); + } + + return overflowed; +} + +inline int +AtomicInt_MulOrSetOverflow(int64_t current, int64_t to_mul, int64_t *result) +{ + int overflowed = __builtin_smull_overflow(current, to_mul, result); + + if (overflowed) { + PyErr_SetObject( + PyExc_OverflowError, + PyUnicode_FromFormat("%ld * %ld > %ld == (2 ** 63) - 1 " + "or %ld * %ld < %ld", current, to_mul, INT64_MAX, current, to_mul, INT64_MIN) + ); + } + + return overflowed; +} + +inline int +AtomicInt_DivOrSetException(int64_t current, int64_t to_div, int64_t *result) +{ + int overflowed = 0; + + if (to_div == 0) { + PyErr_SetString(PyExc_ZeroDivisionError, "division by zero"); + return 1; + } + +#if (INT64_MIN != -INT64_MAX) // => two's complement + overflowed = (current == INT64_MIN && to_div == -1); +#endif + + *result = current / to_div; + + if (overflowed) { + PyErr_SetObject( + PyExc_OverflowError, + PyUnicode_FromFormat("%ld / %ld > %ld == (2 ** 63) - 1 " + "or %ld / %ld < %ld", current, to_div, INT64_MAX, current, to_div, INT64_MIN) + ); + } + + return overflowed; +} + + +PyObject * +AtomicInt_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + AtomicInt *self; + self = (AtomicInt *) type->tp_alloc(type, 0); + + return (PyObject *) self; +} + +int +AtomicInt_init(AtomicInt *self, PyObject *args, PyObject *kwargs) +{ + PyObject *py_integer = NULL; + + char *kw_list[] = {"initial_value", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kw_list, &py_integer)) + goto fail; + + if (py_integer != NULL) { + if (!AtomicInt_ConvertToCLongOrSetException(py_integer, &self->integer)) + goto fail; + } else { + self->integer = 0; + } + + return 0; + + fail: + Py_XDECREF(py_integer); + return -1; +} + +void +AtomicInt_dealloc(AtomicInt *self) +{ + Py_TYPE(self)->tp_free((PyObject *) self); +} + + +inline int64_t +AtomicInt_Get(AtomicInt *self) +{ + return self->integer; +} + +PyObject * +AtomicInt_Get_callable(AtomicInt *self) +{ + int64_t integer = self->integer; + + return PyLong_FromLong(integer); // NULL on error => return NULL +} + +void +AtomicInt_Set(AtomicInt *self, int64_t updated) +{ + int64_t current = self->integer; + + while (!_Py_atomic_compare_exchange_int64(&self->integer, current, updated)) { + current = self->integer; + } +} + +PyObject * +AtomicInt_Set_callable(AtomicInt *self, PyObject *py_integer) +{ + assert(py_integer != NULL); + + int64_t updated; + + if (!AtomicInt_ConvertToCLongOrSetException(py_integer, &updated)) + goto fail; + + AtomicInt_Set(self, updated); + + Py_RETURN_NONE; + fail: + return NULL; +} + +inline int +AtomicInt_CompareAndSet(AtomicInt *self, int64_t expected, int64_t updated) +{ + return _Py_atomic_compare_exchange_int64(&self->integer, expected, updated); +} + +PyObject * +AtomicInt_CompareAndSet_callable(AtomicInt *self, PyObject *args, PyObject *kwargs) +{ + PyObject *py_expected; + PyObject *py_updated; + int64_t expected; + int64_t updated; + + char *kw_list[] = {"expected", "updated", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kw_list, &py_expected, &py_updated)) { + return NULL; + } + + if (!AtomicInt_ConvertToCLongOrSetException(py_expected, &expected) + || !AtomicInt_ConvertToCLongOrSetException(py_updated, &updated)) + return NULL; + + if (AtomicInt_CompareAndSet(self, expected, updated)) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + +inline int64_t +AtomicInt_GetAndSet(AtomicInt *self, int64_t updated) +{ + return _Py_atomic_exchange_int64(&self->integer, updated); +} + +PyObject * +AtomicInt_GetAndSet_callable(AtomicInt *self, PyObject *args, PyObject *kwargs) +{ + PyObject *py_integer = NULL; + int64_t updated; + + char *kw_list[] = {"updated", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kw_list, &py_integer)) { + return NULL; + } + + if (!AtomicInt_ConvertToCLongOrSetException(py_integer, &updated)) + return NULL; + + int64_t previous = AtomicInt_GetAndSet(self, updated); + + return PyLong_FromLong(previous); // may fail and return NULL +} + +int64_t +AtomicInt_IncrementAndGet(AtomicInt *self, int64_t other, int *overflow) +{ + int64_t current, updated; + + do { + current = AtomicInt_Get(self); + + if ((*overflow = AtomicInt_AddOrSetOverflow(current, other, &updated))) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + return updated; + fail: + return -1; +} + +PyObject * +AtomicInt_IncrementAndGet_callable(AtomicInt *self, PyObject *args) +{ + int overflow; + int64_t other, updated; + PyObject *py_other = NULL; + + if (!PyArg_ParseTuple(args, "|O", &py_other)) + goto fail; + + if (py_other == NULL || py_other == Py_None) { + other = 1; + } else { + if (!AtomicInt_ConvertToCLongOrSetException(py_other, &other)) + goto fail; + } + + updated = AtomicInt_IncrementAndGet(self, other, &overflow); + + if (overflow) + goto fail; + + return PyLong_FromLong(updated); + fail: + return NULL; +} + +int64_t +AtomicInt_GetAndIncrement(AtomicInt *self, int64_t other, int *overflow) +{ + int64_t current, updated; + + do { + current = AtomicInt_Get(self); + + if ((*overflow = AtomicInt_AddOrSetOverflow(current, other, &updated))) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + return current; + fail: + return -1; +} + +PyObject * +AtomicInt_GetAndIncrement_callable(AtomicInt *self, PyObject *args) +{ + int overflow; + int64_t other, updated; + PyObject *py_other = NULL; + + if (!PyArg_ParseTuple(args, "|O", &py_other)) + goto fail; + + if (py_other == NULL || py_other == Py_None) { + other = 1; + } else { + if (!AtomicInt_ConvertToCLongOrSetException(py_other, &other)) + goto fail; + } + + updated = AtomicInt_GetAndIncrement(self, other, &overflow); + + if (overflow) + goto fail; + + return PyLong_FromLong(updated); + fail: + return NULL; +} + +int64_t +AtomicInt_DecrementAndGet(AtomicInt *self, int64_t other, int *overflow) +{ + int64_t current, updated; + + do { + current = AtomicInt_Get(self); + + if ((*overflow = AtomicInt_SubOrSetOverflow(current, other, &updated))) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + return updated; + fail: + return -1; +} + +PyObject * +AtomicInt_DecrementAndGet_callable(AtomicInt *self, PyObject *args) +{ + int overflow; + int64_t other, updated; + PyObject *py_other = NULL; + + if (!PyArg_ParseTuple(args, "|O", &py_other)) + goto fail; + + if (py_other == NULL || py_other == Py_None) { + other = 1; + } else { + if (!AtomicInt_ConvertToCLongOrSetException(py_other, &other)) + goto fail; + } + + updated = AtomicInt_DecrementAndGet(self, other, &overflow); + + if (overflow) + goto fail; + + return PyLong_FromLong(updated); + fail: + return NULL; +} + +int64_t +AtomicInt_GetAndDecrement(AtomicInt *self, int64_t other, int *overflow) +{ + int64_t current, updated; + + do { + current = AtomicInt_Get(self); + + if ((*overflow = AtomicInt_SubOrSetOverflow(current, other, &updated))) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + return current; + fail: + return -1; +} + +PyObject * +AtomicInt_GetAndDecrement_callable(AtomicInt *self, PyObject *args) +{ + int overflow; + int64_t other, updated; + PyObject *py_other = NULL; + + if (!PyArg_ParseTuple(args, "|O", &py_other)) + goto fail; + + if (py_other == NULL || py_other == Py_None) { + other = 1; + } else { + if (!AtomicInt_ConvertToCLongOrSetException(py_other, &other)) + goto fail; + } + + updated = AtomicInt_GetAndDecrement(self, other, &overflow); + + if (overflow) + goto fail; + + return PyLong_FromLong(updated); + fail: + return NULL; +} + +int64_t +AtomicInt_GetAndUpdate(AtomicInt *self, PyObject *callable, int *error) +{ + int64_t current, updated; + PyObject *py_current = NULL, *py_updated = NULL; + *error = 0; + + do { + current = AtomicInt_Get(self); + py_current = PyLong_FromLong(current); + + if (py_current == NULL) + goto fail; + + py_updated = PyObject_CallOneArg(callable, py_current); + + if (!AtomicInt_ConvertToCLongOrSetException(py_updated, &updated)) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + return current; + fail: + *error = 1; + return -1; +} + +PyObject * +AtomicInt_GetAndUpdate_callable(AtomicInt *self, PyObject *callable) +{ + int error; + int64_t updated; + + updated = AtomicInt_GetAndUpdate(self, callable, &error); + + if (error) + goto fail; + + return PyLong_FromLong(updated); + fail: + return NULL; +} + +int64_t +AtomicInt_UpdateAndGet(AtomicInt *self, PyObject *callable, int *error) +{ + int64_t current, updated; + PyObject *py_current = NULL, *py_updated = NULL; + *error = 0; + + do { + current = AtomicInt_Get(self); + py_current = PyLong_FromLong(current); + + if (py_current == NULL) + goto fail; + + py_updated = PyObject_CallOneArg(callable, py_current); + + if (!AtomicInt_ConvertToCLongOrSetException(py_updated, &updated)) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + return updated; + fail: + *error = 1; + return -1; +} + +PyObject * +AtomicInt_UpdateAndGet_callable(AtomicInt *self, PyObject *callable) +{ + int error; + int64_t updated; + + updated = AtomicInt_UpdateAndGet(self, callable, &error); + + if (error) + goto fail; + + return PyLong_FromLong(updated); + fail: + return NULL; +} + + +PyObject * +AtomicInt_GetHandle(AtomicInt *self) +{ + PyObject *handle = NULL; + + handle = AtomicIntHandle_new(&AtomicIntHandle_Type, NULL, NULL); + + if (handle == NULL) + goto fail; + + PyObject *args = Py_BuildValue("(O)", self); + if (AtomicIntHandle_init((AtomicIntHandle *) handle, args, NULL) < 0) + goto fail; + + return handle; + + fail: + return NULL; +} + +Py_hash_t +AtomicInt_Hash(AtomicInt *self) +{ + return _Py_HashPointer(self); +} + +inline PyObject * +AtomicInt_Add(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Add(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Subtract(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Subtract(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Multiply(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Multiply(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Remainder(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Remainder(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Divmod(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Divmod(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Power(AtomicInt *self, PyObject *other, PyObject *mod) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Power(current, other, mod); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Negative(AtomicInt *self) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Negative(current); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Positive(AtomicInt *self) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Positive(current); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Absolute(AtomicInt *self) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Absolute(current); + fail: + return NULL; +} + +inline int +AtomicInt_Bool(AtomicInt *self) +{ + int64_t current = AtomicInt_Get(self); + + if (current) + return 1; + else + return 0; +} + +inline PyObject * +AtomicInt_Invert(AtomicInt *self) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Invert(current); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Lshift(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Lshift(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Rshift(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Rshift(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_And(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_And(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Xor(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Xor(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Or(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Or(current, other); + fail: + return NULL; +} + +inline PyObject * +AtomicInt_Int(AtomicInt *self) +{ + return AtomicInt_Get_callable(self); +} + +inline PyObject * +AtomicInt_Float(AtomicInt *self) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Float(current); + fail: + return NULL; +} + +PyObject * +AtomicInt_FloorDivide(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_FloorDivide(current, other); + fail: + return NULL; +} + +PyObject * +AtomicInt_TrueDivide(AtomicInt *self, PyObject *other) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_TrueDivide(current, other); + fail: + return NULL; +} + +PyObject * +AtomicInt_Index(AtomicInt *self) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyNumber_Index(current); + fail: + return NULL; +} + + +PyObject * +AtomicInt_InplaceAdd_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + + if (AtomicInt_AddOrSetOverflow(current, amount, &updated)) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceAdd(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceAdd_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplaceSubtract_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + + if (AtomicInt_SubOrSetOverflow(current, amount, &updated)) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceSubtract(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceSubtract_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplaceMultiply_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + + if (AtomicInt_MulOrSetOverflow(current, amount, &updated)) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceMultiply(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceMultiply_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplaceRemainder_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + updated = current % amount; + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceRemainder(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceRemainder_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplacePower_internal(AtomicInt *self, PyObject *other, PyObject *mod, int do_refcount) +{ + int64_t current, updated; + PyObject *py_current, *py_updated; + + do { + current = AtomicInt_Get(self); + py_current = PyLong_FromLong(current); + + if (py_current == NULL) + goto fail; + + py_updated = PyNumber_Power(py_current, other, mod); + if (py_updated == NULL) + goto fail; + + if (!AtomicInt_ConvertToCLongOrSetException(py_updated, &updated)) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + Py_DECREF(py_current); + Py_DECREF(py_updated); + return (PyObject *) self; + fail: + Py_XDECREF(py_current); + Py_XDECREF(py_updated); + return NULL; +} + +inline PyObject * +AtomicInt_InplacePower(AtomicInt *self, PyObject *other, PyObject *mod) +{ + return AtomicInt_InplacePower_internal(self, other, mod, 1); +} + +PyObject * +AtomicInt_InplaceLshift_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + updated = current << amount; // todo: raise overflow? + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceLshift(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceLshift_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplaceRshift_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + updated = current >> amount; + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceRshift(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceRshift_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplaceAnd_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + updated = current & amount; + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceAnd(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceAnd_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplaceXor_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + updated = current ^ amount; + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceXor(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceXor_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplaceOr_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + updated = current | amount; + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceOr(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceOr_internal(self, other, 1); +} + +PyObject * +AtomicInt_InplaceFloorDivide_internal(AtomicInt *self, PyObject *other, int do_refcount) +{ + int64_t amount, current, updated; + + if (!AtomicInt_ConvertToCLongOrSetException(other, &amount)) + goto fail; + + do { + current = AtomicInt_Get(self); + + if (AtomicInt_DivOrSetException(current, amount, &updated)) + goto fail; + + } while (!AtomicInt_CompareAndSet(self, current, updated)); + + if (do_refcount) + Py_XINCREF(self); + + return (PyObject *) self; + fail: + return NULL; +} + +inline PyObject * +AtomicInt_InplaceFloorDivide(AtomicInt *self, PyObject *other) +{ + return AtomicInt_InplaceFloorDivide_internal(self, other, 1); +} + +inline PyObject * +AtomicInt_InplaceTrueDivide(AtomicInt *Py_UNUSED(self), PyObject *Py_UNUSED(other)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "AtomicInt cannot store float(), use AtomicInt() //= int() instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_MatrixMultiply(AtomicInt *Py_UNUSED(self), PyObject *other) +{ + // just raise an exception; because it's supposed to be unsupported: + // see https://peps.python.org/pep-0465/#non-definitions-for-built-in-types + // bonus: raise the same exception as `int(...) @ other` + return PyNumber_MatrixMultiply(PyLong_FromLong(0), other); +} + +inline PyObject * +AtomicInt_InplaceMatrixMultiply(AtomicInt *Py_UNUSED(self), PyObject *other) +{ + return PyNumber_InPlaceMatrixMultiply(PyLong_FromLong(0), other); +} + +inline PyObject * +AtomicInt_RichCompare(AtomicInt *self, PyObject *other, int op) +{ + PyObject *current = NULL; + + current = AtomicInt_Get_callable(self); + + if (current == NULL) + goto fail; + + return PyObject_RichCompare(current, other, op); + + fail: + return NULL; +} + +inline PyObject * +AtomicInt_AsIntegerRatio(AtomicInt *Py_UNUSED(self)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().as_integer_ratio instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_BitLength(AtomicInt *Py_UNUSED(self)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().bit_length instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Conjugate(AtomicInt *Py_UNUSED(self)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().conjugate instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_FromBytes(AtomicInt *Py_UNUSED(self), PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwargs)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use int.from_bytes instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_ToBytes(AtomicInt *Py_UNUSED(self), PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwargs)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().to_bytes instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Denominator_Get(AtomicInt *Py_UNUSED(self), void *Py_UNUSED(closure)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().denominator.__get__ instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Denominator_Set(AtomicInt *Py_UNUSED(self), PyObject *Py_UNUSED(value), void *Py_UNUSED(closure)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().denominator.__set__ instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Numerator_Get(AtomicInt *Py_UNUSED(self), void *Py_UNUSED(closure)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().numerator.__get__ instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Numerator_Set(AtomicInt *Py_UNUSED(self), PyObject *Py_UNUSED(value), void *Py_UNUSED(closure)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().numerator.__set__ instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Imag_Get(AtomicInt *Py_UNUSED(self), void *Py_UNUSED(closure)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().imag.__get__ instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Imag_Set(AtomicInt *Py_UNUSED(self), PyObject *Py_UNUSED(value), void *Py_UNUSED(closure)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().imag.__set__ instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Real_Get(AtomicInt *Py_UNUSED(self), void *Py_UNUSED(closure)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().real.__get__ instead." + ); + return NULL; +} + +inline PyObject * +AtomicInt_Real_Set(AtomicInt *Py_UNUSED(self), PyObject *Py_UNUSED(value), void *Py_UNUSED(closure)) +{ + PyErr_SetString( + PyExc_NotImplementedError, + "use AtomicInt().get().real.__set__ instead." + ); + return NULL; +} + diff --git a/src/cereggii/atomic_int/handle.c b/src/cereggii/atomic_int/handle.c new file mode 100644 index 00000000..8818ba2e --- /dev/null +++ b/src/cereggii/atomic_int/handle.c @@ -0,0 +1,474 @@ +// SPDX-FileCopyrightText: 2023-present dpdani +// +// SPDX-License-Identifier: Apache-2.0 + +#include "atomic_int.h" +#include "atomic_int_internal.h" + + +PyObject * +AtomicIntHandle_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwargs)) +{ + AtomicIntHandle *self; + self = (AtomicIntHandle *) type->tp_alloc(type, 0); + + return (PyObject *) self; +} + +int +AtomicIntHandle_init(AtomicIntHandle *self, PyObject *args, PyObject *Py_UNUSED(kwargs)) +{ + PyObject *integer = NULL; + + if (!PyArg_ParseTuple(args, "O", &integer)) + goto fail; + + assert(integer != NULL); + Py_INCREF(integer); + + if (!PyObject_IsInstance(integer, (PyObject *) &AtomicInt_Type)) + goto fail; + + self->integer = (AtomicInt *) integer; + + return 0; + + fail: + Py_XDECREF(integer); + return -1; +} + +void +AtomicIntHandle_dealloc(AtomicIntHandle *self) +{ + Py_XDECREF(self->integer); + Py_TYPE(self)->tp_free((PyObject *) self); +} + +__attribute__((unused)) inline int64_t +AtomicIntHandle_Get(AtomicIntHandle *self) +{ + return AtomicInt_Get(self->integer); +} + +inline PyObject * +AtomicIntHandle_Get_callable(AtomicIntHandle *self) +{ + return AtomicInt_Get_callable(self->integer); +} + +__attribute__((unused)) inline void +AtomicIntHandle_Set(AtomicIntHandle *self, int64_t updated) +{ + return AtomicInt_Set(self->integer, updated); +} + +inline PyObject * +AtomicIntHandle_Set_callable(AtomicIntHandle *self, PyObject *updated) +{ + return AtomicInt_Set_callable(self->integer, updated); +} + +__attribute__((unused)) inline int +AtomicIntHandle_CompareAndSet(AtomicIntHandle *self, int64_t expected, int64_t updated) +{ + return AtomicInt_CompareAndSet(self->integer, expected, updated); +} + +inline PyObject * +AtomicIntHandle_CompareAndSet_callable(AtomicIntHandle *self, PyObject *args, PyObject *kwargs) +{ + return AtomicInt_CompareAndSet_callable(self->integer, args, kwargs); +} + +__attribute__((unused)) inline int64_t +AtomicIntHandle_GetAndSet(AtomicIntHandle *self, int64_t updated) +{ + return AtomicInt_GetAndSet(self->integer, updated); +} + +inline PyObject * +AtomicIntHandle_GetAndSet_callable(AtomicIntHandle *self, PyObject *args, PyObject *kwargs) +{ + return AtomicInt_GetAndSet_callable(self->integer, args, kwargs); +} + +__attribute__((unused)) inline int64_t +AtomicIntHandle_IncrementAndGet(AtomicIntHandle *self, int64_t other, int *overflow) +{ + return AtomicInt_IncrementAndGet(self->integer, other, overflow); +} + +inline PyObject * +AtomicIntHandle_IncrementAndGet_callable(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_IncrementAndGet_callable(self->integer, other); +} + +__attribute__((unused)) inline int64_t +AtomicIntHandle_GetAndIncrement(AtomicIntHandle *self, int64_t other, int *overflow) +{ + return AtomicInt_GetAndIncrement(self->integer, other, overflow); +} + +inline PyObject * +AtomicIntHandle_GetAndIncrement_callable(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_GetAndIncrement_callable(self->integer, other); +} + +__attribute__((unused)) inline int64_t +AtomicIntHandle_DecrementAndGet(AtomicIntHandle *self, int64_t other, int *overflow) +{ + return AtomicInt_DecrementAndGet(self->integer, other, overflow); +} + +inline PyObject * +AtomicIntHandle_DecrementAndGet_callable(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_DecrementAndGet_callable(self->integer, other); +} + +__attribute__((unused)) inline int64_t +AtomicIntHandle_GetAndDecrement(AtomicIntHandle *self, int64_t other, int *overflow) +{ + return AtomicInt_GetAndDecrement(self->integer, other, overflow); +} + +inline PyObject * +AtomicIntHandle_GetAndDecrement_callable(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_GetAndDecrement_callable(self->integer, other); +} + +__attribute__((unused)) inline int64_t +AtomicIntHandle_GetAndUpdate(AtomicIntHandle *self, PyObject *callable, int *overflow) +{ + return AtomicInt_GetAndUpdate(self->integer, callable, overflow); +} + +inline PyObject * +AtomicIntHandle_GetAndUpdate_callable(AtomicIntHandle *self, PyObject *callable) +{ + return AtomicInt_GetAndUpdate_callable(self->integer, callable); +} + +__attribute__((unused)) inline int64_t +AtomicIntHandle_UpdateAndGet(AtomicIntHandle *self, PyObject *callable, int *overflow) +{ + return AtomicInt_UpdateAndGet(self->integer, callable, overflow); +} + +inline PyObject * +AtomicIntHandle_UpdateAndGet_callable(AtomicIntHandle *self, PyObject *callable) +{ + return AtomicInt_UpdateAndGet_callable(self->integer, callable); +} + + +inline PyObject * +AtomicIntHandle_GetHandle(AtomicIntHandle *self) +{ + return AtomicInt_GetHandle(self->integer); +} + +inline Py_hash_t +AtomicIntHandle_Hash(AtomicIntHandle *self) +{ + return AtomicInt_Hash(self->integer); +} + +inline PyObject * +AtomicIntHandle_Add(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Add(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Subtract(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Subtract(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Multiply(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Multiply(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Remainder(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Remainder(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Divmod(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Divmod(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Power(AtomicIntHandle *self, PyObject *other, PyObject *mod) +{ + return AtomicInt_Power(self->integer, other, mod); +} + +inline PyObject * +AtomicIntHandle_Negative(AtomicIntHandle *self) +{ + return AtomicInt_Negative(self->integer); +} + +inline PyObject * +AtomicIntHandle_Positive(AtomicIntHandle *self) +{ + return AtomicInt_Positive(self->integer); +} + +inline PyObject * +AtomicIntHandle_Absolute(AtomicIntHandle *self) +{ + return AtomicInt_Absolute(self->integer); +} + +inline int +AtomicIntHandle_Bool(AtomicIntHandle *self) +{ + return AtomicInt_Bool(self->integer); +} + +inline PyObject * +AtomicIntHandle_Invert(AtomicIntHandle *self) +{ + return AtomicInt_Invert(self->integer); +} + +inline PyObject * +AtomicIntHandle_Lshift(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Lshift(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Rshift(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Rshift(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_And(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_And(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Xor(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Xor(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Or(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_Or(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Int(AtomicIntHandle *self) +{ + return AtomicInt_Int(self->integer); +} + +inline PyObject * +AtomicIntHandle_Float(AtomicIntHandle *self) +{ + return AtomicInt_Float(self->integer); +} + +inline PyObject * +AtomicIntHandle_FloorDivide(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_FloorDivide(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_TrueDivide(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_TrueDivide(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_Index(AtomicIntHandle *self) +{ + return AtomicInt_Index(self->integer); +} + + +inline PyObject * +AtomicIntHandle_InplaceAdd(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceAdd_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceSubtract(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceSubtract_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceMultiply(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceMultiply_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceRemainder(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceRemainder_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplacePower(AtomicIntHandle *self, PyObject *other, PyObject *mod) +{ + return AtomicInt_InplacePower_internal(self->integer, other, mod, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceLshift(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceLshift_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceRshift(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceRshift_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceAnd(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceAnd_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceXor(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceXor_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceOr(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceOr_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceFloorDivide(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceFloorDivide_internal(self->integer, other, 0); +} + +inline PyObject * +AtomicIntHandle_InplaceTrueDivide(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceTrueDivide(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_MatrixMultiply(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_MatrixMultiply(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_InplaceMatrixMultiply(AtomicIntHandle *self, PyObject *other) +{ + return AtomicInt_InplaceMatrixMultiply(self->integer, other); +} + +inline PyObject * +AtomicIntHandle_RichCompare(AtomicIntHandle *self, PyObject *other, int op) +{ + return AtomicInt_RichCompare(self->integer, other, op); +} + +inline PyObject * +AtomicIntHandle_AsIntegerRatio(AtomicIntHandle *self) +{ + return AtomicInt_AsIntegerRatio(self->integer); +} + +inline PyObject * +AtomicIntHandle_BitLength(AtomicIntHandle *self) +{ + return AtomicInt_BitLength(self->integer); +} + +inline PyObject * +AtomicIntHandle_Conjugate(AtomicIntHandle *self) +{ + return AtomicInt_Conjugate(self->integer); +} + +inline PyObject * +AtomicIntHandle_FromBytes(AtomicIntHandle *self, PyObject *args, PyObject *kwargs) +{ + return AtomicInt_FromBytes(self->integer, args, kwargs); +} + +inline PyObject * +AtomicIntHandle_ToBytes(AtomicIntHandle *self, PyObject *args, PyObject *kwargs) +{ + return AtomicInt_ToBytes(self->integer, args, kwargs); +} + +inline PyObject * +AtomicIntHandle_Denominator_Get(AtomicIntHandle *self, void *closure) +{ + return AtomicInt_Denominator_Get(self->integer, closure); +} + +inline PyObject * +AtomicIntHandle_Denominator_Set(AtomicIntHandle *self, PyObject *value, void *closure) +{ + return AtomicInt_Denominator_Set(self->integer, value, closure); +} + +inline PyObject * +AtomicIntHandle_Numerator_Get(AtomicIntHandle *self, void *closure) +{ + return AtomicInt_Numerator_Get(self->integer, closure); +} + +inline PyObject * +AtomicIntHandle_Numerator_Set(AtomicIntHandle *self, PyObject *value, void *closure) +{ + return AtomicInt_Numerator_Set(self->integer, value, closure); +} + +inline PyObject * +AtomicIntHandle_Imag_Get(AtomicIntHandle *self, void *closure) +{ + return AtomicInt_Imag_Get(self->integer, closure); +} + +inline PyObject * +AtomicIntHandle_Imag_Set(AtomicIntHandle *self, PyObject *value, void *closure) +{ + return AtomicInt_Imag_Set(self->integer, value, closure); +} + +inline PyObject * +AtomicIntHandle_Real_Get(AtomicIntHandle *self, void *closure) +{ + return AtomicInt_Real_Get(self->integer, closure); +} + +inline PyObject * +AtomicIntHandle_Real_Set(AtomicIntHandle *self, PyObject *value, void *closure) +{ + return AtomicInt_Real_Set(self->integer, value, closure); +} diff --git a/src/cereggii/cereggii.c b/src/cereggii/cereggii.c index fcc02bea..959e9b85 100644 --- a/src/cereggii/cereggii.c +++ b/src/cereggii/cereggii.c @@ -4,10 +4,188 @@ #define PY_SSIZE_T_CLEAN +#include "atomic_int.h" #include "atomic_ref.h" #include "atomic_dict.h" +static PyMethodDef AtomicInt_methods[] = { + {"get", (PyCFunction) AtomicInt_Get_callable, METH_NOARGS, NULL}, + {"set", (PyCFunction) AtomicInt_Set_callable, METH_O, NULL}, + {"compare_and_set", (PyCFunction) AtomicInt_CompareAndSet_callable, METH_VARARGS | METH_KEYWORDS, NULL}, + {"get_and_set", (PyCFunction) AtomicInt_GetAndSet_callable, METH_VARARGS | METH_KEYWORDS, NULL}, + {"increment_and_get", (PyCFunction) AtomicInt_IncrementAndGet_callable, METH_VARARGS, NULL}, + {"get_and_increment", (PyCFunction) AtomicInt_GetAndIncrement_callable, METH_VARARGS, NULL}, + {"decrement_and_get", (PyCFunction) AtomicInt_DecrementAndGet_callable, METH_VARARGS, NULL}, + {"get_and_decrement", (PyCFunction) AtomicInt_GetAndDecrement_callable, METH_VARARGS, NULL}, + {"update_and_get", (PyCFunction) AtomicInt_UpdateAndGet_callable, METH_O, NULL}, + {"get_and_update", (PyCFunction) AtomicInt_GetAndUpdate_callable, METH_O, NULL}, + {"get_handle", (PyCFunction) AtomicInt_GetHandle, METH_NOARGS, NULL}, + {"as_integer_ratio", (PyCFunction) AtomicInt_AsIntegerRatio, METH_NOARGS, NULL}, + {"bit_length", (PyCFunction) AtomicInt_BitLength, METH_NOARGS, NULL}, + {"conjugate", (PyCFunction) AtomicInt_Conjugate, METH_NOARGS, NULL}, + {"from_bytes", (PyCFunction) AtomicInt_FromBytes, METH_VARARGS | METH_KEYWORDS | + METH_CLASS, NULL}, + {"to_bytes", (PyCFunction) AtomicInt_ToBytes, METH_NOARGS, NULL}, + {NULL} +}; + +static PyGetSetDef AtomicInt_properties[] = { + {"denominator", (getter) AtomicInt_Denominator_Get, (setter) AtomicInt_Denominator_Set, NULL, NULL}, + {"numerator", (getter) AtomicInt_Numerator_Get, (setter) AtomicInt_Numerator_Set, NULL, NULL}, + {"imag", (getter) AtomicInt_Imag_Get, (setter) AtomicInt_Imag_Set, NULL, NULL}, + {"real", (getter) AtomicInt_Real_Get, (setter) AtomicInt_Real_Set, NULL, NULL}, + {NULL}, +}; + +static PyNumberMethods AtomicInt_as_number = { + .nb_add = (binaryfunc) AtomicInt_Add, + .nb_subtract = (binaryfunc) AtomicInt_Subtract, + .nb_multiply = (binaryfunc) AtomicInt_Multiply, + .nb_remainder = (binaryfunc) AtomicInt_Remainder, + .nb_divmod = (binaryfunc) AtomicInt_Divmod, + .nb_power = (ternaryfunc) AtomicInt_Power, + .nb_negative = (unaryfunc) AtomicInt_Negative, + .nb_positive = (unaryfunc) AtomicInt_Positive, + .nb_absolute = (unaryfunc) AtomicInt_Absolute, + .nb_bool = (inquiry) AtomicInt_Bool, + .nb_invert = (unaryfunc) AtomicInt_Invert, + .nb_lshift = (binaryfunc) AtomicInt_Lshift, + .nb_rshift = (binaryfunc) AtomicInt_Rshift, + .nb_and = (binaryfunc) AtomicInt_And, + .nb_xor = (binaryfunc) AtomicInt_Xor, + .nb_or = (binaryfunc) AtomicInt_Or, + .nb_int = (unaryfunc) AtomicInt_Int, + .nb_float = (unaryfunc) AtomicInt_Float, + + .nb_inplace_add = (binaryfunc) AtomicInt_InplaceAdd, + .nb_inplace_subtract = (binaryfunc) AtomicInt_InplaceSubtract, + .nb_inplace_multiply = (binaryfunc) AtomicInt_InplaceMultiply, + .nb_inplace_remainder = (binaryfunc) AtomicInt_InplaceRemainder, + .nb_inplace_power = (ternaryfunc) AtomicInt_InplacePower, + .nb_inplace_lshift = (binaryfunc) AtomicInt_InplaceLshift, + .nb_inplace_rshift = (binaryfunc) AtomicInt_InplaceRshift, + .nb_inplace_and = (binaryfunc) AtomicInt_InplaceAnd, + .nb_inplace_xor = (binaryfunc) AtomicInt_InplaceXor, + .nb_inplace_or = (binaryfunc) AtomicInt_InplaceOr, + + .nb_floor_divide = (binaryfunc) AtomicInt_FloorDivide, + .nb_true_divide = (binaryfunc) AtomicInt_TrueDivide, + .nb_inplace_floor_divide = (binaryfunc) AtomicInt_InplaceFloorDivide, + .nb_inplace_true_divide = (binaryfunc) AtomicInt_InplaceTrueDivide, + + .nb_index = (unaryfunc) AtomicInt_Index, + + .nb_matrix_multiply = (binaryfunc) AtomicInt_MatrixMultiply, + .nb_inplace_matrix_multiply = (binaryfunc) AtomicInt_InplaceMatrixMultiply, +}; + +PyTypeObject AtomicInt_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "cereggii.AtomicInt", + .tp_doc = PyDoc_STR("An int that may be updated atomically."), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_basicsize = sizeof(AtomicInt), + .tp_itemsize = 0, + .tp_new = AtomicInt_new, + .tp_init = (initproc) AtomicInt_init, + .tp_dealloc = (destructor) AtomicInt_dealloc, + .tp_methods = AtomicInt_methods, + .tp_as_number = &AtomicInt_as_number, + .tp_richcompare = (richcmpfunc) AtomicInt_RichCompare, + .tp_hash = (hashfunc) AtomicInt_Hash, + .tp_getset = AtomicInt_properties, +}; + +static PyMethodDef AtomicIntHandle_methods[] = { + {"get", (PyCFunction) AtomicIntHandle_Get_callable, METH_NOARGS, NULL}, + {"set", (PyCFunction) AtomicIntHandle_Set_callable, METH_O, NULL}, + {"compare_and_set", (PyCFunction) AtomicIntHandle_CompareAndSet_callable, METH_VARARGS | METH_KEYWORDS, NULL}, + {"get_and_set", (PyCFunction) AtomicIntHandle_GetAndSet_callable, METH_VARARGS | METH_KEYWORDS, NULL}, + {"increment_and_get", (PyCFunction) AtomicIntHandle_IncrementAndGet_callable, METH_VARARGS, NULL}, + {"get_and_increment", (PyCFunction) AtomicIntHandle_GetAndIncrement_callable, METH_VARARGS, NULL}, + {"decrement_and_get", (PyCFunction) AtomicIntHandle_DecrementAndGet_callable, METH_VARARGS, NULL}, + {"get_and_decrement", (PyCFunction) AtomicIntHandle_GetAndDecrement_callable, METH_VARARGS, NULL}, + {"update_and_get", (PyCFunction) AtomicIntHandle_UpdateAndGet_callable, METH_O, NULL}, + {"get_and_update", (PyCFunction) AtomicIntHandle_GetAndUpdate_callable, METH_O, NULL}, + {"get_handle", (PyCFunction) AtomicIntHandle_GetHandle, METH_NOARGS, NULL}, + {"as_integer_ratio", (PyCFunction) AtomicIntHandle_AsIntegerRatio, METH_NOARGS, NULL}, + {"bit_length", (PyCFunction) AtomicIntHandle_BitLength, METH_NOARGS, NULL}, + {"conjugate", (PyCFunction) AtomicIntHandle_Conjugate, METH_NOARGS, NULL}, + {"from_bytes", (PyCFunction) AtomicIntHandle_FromBytes, METH_VARARGS | METH_KEYWORDS | + METH_CLASS, NULL}, + {"to_bytes", (PyCFunction) AtomicIntHandle_ToBytes, METH_NOARGS, NULL}, + {NULL} +}; + +static PyGetSetDef AtomicIntHandle_properties[] = { + {"denominator", (getter) AtomicIntHandle_Denominator_Get, (setter) AtomicIntHandle_Denominator_Set, NULL, NULL}, + {"numerator", (getter) AtomicIntHandle_Numerator_Get, (setter) AtomicIntHandle_Numerator_Set, NULL, NULL}, + {"imag", (getter) AtomicIntHandle_Imag_Get, (setter) AtomicIntHandle_Imag_Set, NULL, NULL}, + {"real", (getter) AtomicIntHandle_Real_Get, (setter) AtomicIntHandle_Real_Set, NULL, NULL}, + {NULL}, +}; + +static PyNumberMethods AtomicIntHandle_as_number = { + .nb_add = (binaryfunc) AtomicIntHandle_Add, + .nb_subtract = (binaryfunc) AtomicIntHandle_Subtract, + .nb_multiply = (binaryfunc) AtomicIntHandle_Multiply, + .nb_remainder = (binaryfunc) AtomicIntHandle_Remainder, + .nb_divmod = (binaryfunc) AtomicIntHandle_Divmod, + .nb_power = (ternaryfunc) AtomicIntHandle_Power, + .nb_negative = (unaryfunc) AtomicIntHandle_Negative, + .nb_positive = (unaryfunc) AtomicIntHandle_Positive, + .nb_absolute = (unaryfunc) AtomicIntHandle_Absolute, + .nb_bool = (inquiry) AtomicIntHandle_Bool, + .nb_invert = (unaryfunc) AtomicIntHandle_Invert, + .nb_lshift = (binaryfunc) AtomicIntHandle_Lshift, + .nb_rshift = (binaryfunc) AtomicIntHandle_Rshift, + .nb_and = (binaryfunc) AtomicIntHandle_And, + .nb_xor = (binaryfunc) AtomicIntHandle_Xor, + .nb_or = (binaryfunc) AtomicIntHandle_Or, + .nb_int = (unaryfunc) AtomicIntHandle_Int, + .nb_float = (unaryfunc) AtomicIntHandle_Float, + + .nb_inplace_add = (binaryfunc) AtomicIntHandle_InplaceAdd, + .nb_inplace_subtract = (binaryfunc) AtomicIntHandle_InplaceSubtract, + .nb_inplace_multiply = (binaryfunc) AtomicIntHandle_InplaceMultiply, + .nb_inplace_remainder = (binaryfunc) AtomicIntHandle_InplaceRemainder, + .nb_inplace_power = (ternaryfunc) AtomicIntHandle_InplacePower, + .nb_inplace_lshift = (binaryfunc) AtomicIntHandle_InplaceLshift, + .nb_inplace_rshift = (binaryfunc) AtomicIntHandle_InplaceRshift, + .nb_inplace_and = (binaryfunc) AtomicIntHandle_InplaceAnd, + .nb_inplace_xor = (binaryfunc) AtomicIntHandle_InplaceXor, + .nb_inplace_or = (binaryfunc) AtomicIntHandle_InplaceOr, + + .nb_floor_divide = (binaryfunc) AtomicIntHandle_FloorDivide, + .nb_true_divide = (binaryfunc) AtomicIntHandle_TrueDivide, + .nb_inplace_floor_divide = (binaryfunc) AtomicIntHandle_InplaceFloorDivide, + .nb_inplace_true_divide = (binaryfunc) AtomicIntHandle_InplaceTrueDivide, + + .nb_index = (unaryfunc) AtomicIntHandle_Index, + + .nb_matrix_multiply = (binaryfunc) AtomicIntHandle_MatrixMultiply, + .nb_inplace_matrix_multiply = (binaryfunc) AtomicIntHandle_InplaceMatrixMultiply, +}; + +PyTypeObject AtomicIntHandle_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "cereggii.AtomicIntHandle", + .tp_doc = PyDoc_STR("An immutable handle for referencing an AtomicInt."), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_basicsize = sizeof(AtomicIntHandle), + .tp_itemsize = 0, + .tp_new = AtomicIntHandle_new, + .tp_init = (initproc) AtomicIntHandle_init, + .tp_dealloc = (destructor) AtomicIntHandle_dealloc, + .tp_methods = AtomicIntHandle_methods, + .tp_as_number = &AtomicIntHandle_as_number, + .tp_richcompare = (richcmpfunc) AtomicIntHandle_RichCompare, + .tp_hash = (hashfunc) AtomicIntHandle_Hash, + .tp_getset = AtomicIntHandle_properties, +}; + + static PyMethodDef AtomicRef_methods[] = { {"get", (PyCFunction) AtomicRef_Get, METH_NOARGS, NULL}, {"set", (PyCFunction) AtomicRef_Set, METH_O, NULL}, @@ -32,8 +210,8 @@ PyTypeObject AtomicRef_Type = { static PyMethodDef AtomicDict_methods[] = { - {"debug", (PyCFunction) AtomicDict_Debug, METH_NOARGS, NULL}, - {"get", (PyCFunction) AtomicDict_GetItemOrDefaultVarargs, METH_VARARGS | METH_KEYWORDS, NULL}, + {"debug", (PyCFunction) AtomicDict_Debug, METH_NOARGS, NULL}, + {"get", (PyCFunction) AtomicDict_GetItemOrDefaultVarargs, METH_VARARGS | METH_KEYWORDS, NULL}, {NULL} }; @@ -75,6 +253,10 @@ PyInit__cereggii(void) return NULL; if (PyType_Ready(&AtomicRef_Type) < 0) return NULL; + if (PyType_Ready(&AtomicInt_Type) < 0) + return NULL; + if (PyType_Ready(&AtomicIntHandle_Type) < 0) + return NULL; m = PyModule_Create(&cereggii_module); if (m == NULL) @@ -92,6 +274,18 @@ PyInit__cereggii(void) goto fail; } + Py_INCREF(&AtomicInt_Type); + if (PyModule_AddObject(m, "AtomicInt", (PyObject *) &AtomicInt_Type) < 0) { + Py_DECREF(&AtomicInt_Type); + goto fail; + } + + Py_INCREF(&AtomicIntHandle_Type); + if (PyModule_AddObject(m, "AtomicIntHandle", (PyObject *) &AtomicIntHandle_Type) < 0) { + Py_DECREF(&AtomicIntHandle_Type); + goto fail; + } + return m; fail: Py_DECREF(m); diff --git a/src/include/atomic_int.h b/src/include/atomic_int.h new file mode 100644 index 00000000..29197b03 --- /dev/null +++ b/src/include/atomic_int.h @@ -0,0 +1,354 @@ +// SPDX-FileCopyrightText: 2023-present dpdani +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef CEREGGII_ATOMIC_INT_H +#define CEREGGII_ATOMIC_INT_H + +#define PY_SSIZE_T_CLEAN + +#include + + +typedef struct atomic_int { + PyObject_HEAD + + int64_t integer; +} AtomicInt; + +typedef struct atomic_int_handle { + PyObject_HEAD + + AtomicInt *integer; +} AtomicIntHandle; + + +/// basic methods + +int64_t AtomicInt_Get(AtomicInt *self); + +PyObject *AtomicInt_Get_callable(AtomicInt *self); + +void AtomicInt_Set(AtomicInt *self, int64_t updated); + +PyObject *AtomicInt_Set_callable(AtomicInt *self, PyObject *updated); + +int AtomicInt_CompareAndSet(AtomicInt *self, int64_t expected, int64_t updated); + +PyObject *AtomicInt_CompareAndSet_callable(AtomicInt *self, PyObject *args, PyObject *kwargs); + +int64_t AtomicInt_GetAndSet(AtomicInt *self, int64_t updated); + +PyObject *AtomicInt_GetAndSet_callable(AtomicInt *self, PyObject *args, PyObject *kwargs); + + +/// handle + +PyObject *AtomicInt_GetHandle(AtomicInt *self); + + +/// java-esque methods +// see https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html + +int64_t AtomicInt_IncrementAndGet(AtomicInt *self, int64_t other, int *overflow); + +PyObject *AtomicInt_IncrementAndGet_callable(AtomicInt *self, PyObject *py_other); + +int64_t AtomicInt_GetAndIncrement(AtomicInt *self, int64_t other, int *overflow); + +PyObject *AtomicInt_GetAndIncrement_callable(AtomicInt *self, PyObject *py_other); + + +int64_t AtomicInt_DecrementAndGet(AtomicInt *self, int64_t other, int *overflow); + +PyObject *AtomicInt_DecrementAndGet_callable(AtomicInt *self, PyObject *other); + +int64_t AtomicInt_GetAndDecrement(AtomicInt *self, int64_t other, int *overflow); + +PyObject *AtomicInt_GetAndDecrement_callable(AtomicInt *self, PyObject *other); + + +int64_t AtomicInt_GetAndUpdate(AtomicInt *self, PyObject *callable, int *error); + +PyObject *AtomicInt_GetAndUpdate_callable(AtomicInt *self, PyObject *callable); + +int64_t AtomicInt_UpdateAndGet(AtomicInt *self, PyObject *callable, int *error); + +PyObject *AtomicInt_UpdateAndGet_callable(AtomicInt *self, PyObject *callable); + + +/// number methods + +Py_hash_t AtomicInt_Hash(AtomicInt *self); + +PyObject *AtomicInt_Add(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Subtract(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Multiply(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Remainder(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Divmod(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Power(AtomicInt *self, PyObject *other, PyObject *mod); + +PyObject *AtomicInt_Negative(AtomicInt *self); + +PyObject *AtomicInt_Positive(AtomicInt *self); + +PyObject *AtomicInt_Absolute(AtomicInt *self); + +int AtomicInt_Bool(AtomicInt *self); + +PyObject *AtomicInt_Invert(AtomicInt *self); + +PyObject *AtomicInt_Lshift(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Rshift(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_And(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Xor(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Or(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_Int(AtomicInt *self); + +PyObject *AtomicInt_Float(AtomicInt *self); + + +PyObject *AtomicInt_InplaceAdd(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceSubtract(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceMultiply(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceRemainder(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplacePower(AtomicInt *self, PyObject *other, PyObject *mod); + +PyObject *AtomicInt_InplaceLshift(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceRshift(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceAnd(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceXor(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceOr(AtomicInt *self, PyObject *other); + + +PyObject *AtomicInt_FloorDivide(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_TrueDivide(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceFloorDivide(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceTrueDivide(AtomicInt *self, PyObject *other); + + +PyObject *AtomicInt_Index(AtomicInt *self); + + +PyObject *AtomicInt_MatrixMultiply(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_InplaceMatrixMultiply(AtomicInt *self, PyObject *other); + +PyObject *AtomicInt_RichCompare(AtomicInt *self, PyObject *other, int op); + + +/// int-specific methods +// these are explicitly not supported + +PyObject *AtomicInt_AsIntegerRatio(AtomicInt *self); + +PyObject *AtomicInt_BitLength(AtomicInt *self); + +PyObject *AtomicInt_Conjugate(AtomicInt *self); + +PyObject *AtomicInt_FromBytes(AtomicInt *self, PyObject *args, PyObject *kwargs); + +PyObject *AtomicInt_ToBytes(AtomicInt *self, PyObject *args, PyObject *kwargs); + +PyObject *AtomicInt_Denominator_Get(AtomicInt *self, void *closure); + +PyObject *AtomicInt_Denominator_Set(AtomicInt *self, PyObject *value, void *closure); + +PyObject *AtomicInt_Numerator_Get(AtomicInt *self, void *closure); + +PyObject *AtomicInt_Numerator_Set(AtomicInt *self, PyObject *value, void *closure); + +PyObject *AtomicInt_Imag_Get(AtomicInt *self, void *closure); + +PyObject *AtomicInt_Imag_Set(AtomicInt *self, PyObject *value, void *closure); + +PyObject *AtomicInt_Real_Get(AtomicInt *self, void *closure); + +PyObject *AtomicInt_Real_Set(AtomicInt *self, PyObject *value, void *closure); + + +/// handle + +__attribute__((unused)) int64_t AtomicIntHandle_Get(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_Get_callable(AtomicIntHandle *self); + +__attribute__((unused)) void AtomicIntHandle_Set(AtomicIntHandle *self, int64_t updated); + +PyObject *AtomicIntHandle_Set_callable(AtomicIntHandle *self, PyObject *updated); + +__attribute__((unused)) int AtomicIntHandle_CompareAndSet(AtomicIntHandle *self, int64_t expected, int64_t updated); + +PyObject *AtomicIntHandle_CompareAndSet_callable(AtomicIntHandle *self, PyObject *args, PyObject *kwargs); + +__attribute__((unused)) int64_t AtomicIntHandle_GetAndSet(AtomicIntHandle *self, int64_t updated); + +PyObject *AtomicIntHandle_GetAndSet_callable(AtomicIntHandle *self, PyObject *args, PyObject *kwargs); + +PyObject *AtomicIntHandle_GetHandle(AtomicIntHandle *self); + +__attribute__((unused)) int64_t AtomicIntHandle_IncrementAndGet(AtomicIntHandle *self, int64_t other, int *overflow); + +PyObject *AtomicIntHandle_IncrementAndGet_callable(AtomicIntHandle *self, PyObject *other); + +__attribute__((unused)) int64_t AtomicIntHandle_GetAndIncrement(AtomicIntHandle *self, int64_t other, int *overflow); + +PyObject *AtomicIntHandle_GetAndIncrement_callable(AtomicIntHandle *self, PyObject *other); + +__attribute__((unused)) int64_t AtomicIntHandle_DecrementAndGet(AtomicIntHandle *self, int64_t other, int *overflow); + +PyObject *AtomicIntHandle_DecrementAndGet_callable(AtomicIntHandle *self, PyObject *other); + +__attribute__((unused)) int64_t AtomicIntHandle_GetAndDecrement(AtomicIntHandle *self, int64_t other, int *overflow); + +PyObject *AtomicIntHandle_GetAndDecrement_callable(AtomicIntHandle *self, PyObject *other); + +__attribute__((unused)) int64_t AtomicIntHandle_GetAndUpdate(AtomicIntHandle *self, PyObject *callable, int *overflow); + +PyObject *AtomicIntHandle_GetAndUpdate_callable(AtomicIntHandle *self, PyObject *callable); + +__attribute__((unused)) int64_t AtomicIntHandle_UpdateAndGet(AtomicIntHandle *self, PyObject *callable, int *overflow); + +PyObject *AtomicIntHandle_UpdateAndGet_callable(AtomicIntHandle *self, PyObject *callable); + +Py_hash_t AtomicIntHandle_Hash(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_Add(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Subtract(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Multiply(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Remainder(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Divmod(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Power(AtomicIntHandle *self, PyObject *other, PyObject *mod); + +PyObject *AtomicIntHandle_Negative(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_Positive(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_Absolute(AtomicIntHandle *self); + +int AtomicIntHandle_Bool(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_Invert(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_Lshift(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Rshift(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_And(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Xor(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Or(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Int(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_Float(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_InplaceAdd(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceSubtract(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceMultiply(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceRemainder(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplacePower(AtomicIntHandle *self, PyObject *other, PyObject *mod); + +PyObject *AtomicIntHandle_InplaceLshift(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceRshift(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceAnd(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceXor(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceOr(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_FloorDivide(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_TrueDivide(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceFloorDivide(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceTrueDivide(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_Index(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_MatrixMultiply(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_InplaceMatrixMultiply(AtomicIntHandle *self, PyObject *other); + +PyObject *AtomicIntHandle_RichCompare(AtomicIntHandle *self, PyObject *other, int op); + +PyObject *AtomicIntHandle_AsIntegerRatio(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_BitLength(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_Conjugate(AtomicIntHandle *self); + +PyObject *AtomicIntHandle_FromBytes(AtomicIntHandle *self, PyObject *args, PyObject *kwargs); + +PyObject *AtomicIntHandle_ToBytes(AtomicIntHandle *self, PyObject *args, PyObject *kwargs); + +PyObject *AtomicIntHandle_Denominator_Get(AtomicIntHandle *self, void *closure); + +PyObject *AtomicIntHandle_Denominator_Set(AtomicIntHandle *self, PyObject *value, void *closure); + +PyObject *AtomicIntHandle_Numerator_Get(AtomicIntHandle *self, void *closure); + +PyObject *AtomicIntHandle_Numerator_Set(AtomicIntHandle *self, PyObject *value, void *closure); + +PyObject *AtomicIntHandle_Imag_Get(AtomicIntHandle *self, void *closure); + +PyObject *AtomicIntHandle_Imag_Set(AtomicIntHandle *self, PyObject *value, void *closure); + +PyObject *AtomicIntHandle_Real_Get(AtomicIntHandle *self, void *closure); + +PyObject *AtomicIntHandle_Real_Set(AtomicIntHandle *self, PyObject *value, void *closure); + + +/// type methods + +PyObject *AtomicInt_new(PyTypeObject *type, PyObject *args, PyObject *kwargs); + +PyObject *AtomicIntHandle_new(PyTypeObject *type, PyObject *args, PyObject *kwargs); + +int AtomicInt_init(AtomicInt *self, PyObject *args, PyObject *kwargs); + +int AtomicIntHandle_init(AtomicIntHandle *self, PyObject *args, PyObject *kwargs); + +void AtomicInt_dealloc(AtomicInt *self); + +void AtomicIntHandle_dealloc(AtomicIntHandle *self); + +extern PyTypeObject AtomicInt_Type; +extern PyTypeObject AtomicIntHandle_Type; + + +#endif // CEREGGII_ATOMIC_INT_H diff --git a/src/include/atomic_int_internal.h b/src/include/atomic_int_internal.h new file mode 100644 index 00000000..287edf63 --- /dev/null +++ b/src/include/atomic_int_internal.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2023-present dpdani +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef CEREGGII_ATOMIC_INT_INTERNAL_H +#define CEREGGII_ATOMIC_INT_INTERNAL_H + +#define PY_SSIZE_T_CLEAN + +#include "atomic_int.h" + + +int AtomicInt_ConvertToCLongOrSetException(PyObject *py_integer, int64_t *integer); + +int AtomicInt_AddOrSetOverflow(int64_t current, int64_t to_add, int64_t *result); + +int AtomicInt_SubOrSetOverflow(int64_t current, int64_t to_add, int64_t *result); + +int AtomicInt_MulOrSetOverflow(int64_t current, int64_t to_add, int64_t *result); + +int AtomicInt_DivOrSetException(int64_t current, int64_t to_div, int64_t *result); + +PyObject *AtomicInt_InplaceAdd_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplaceSubtract_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplaceMultiply_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplaceRemainder_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplacePower_internal(AtomicInt *self, PyObject *other, PyObject *mod, int do_refcount); + +PyObject *AtomicInt_InplaceLshift_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplaceRshift_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplaceAnd_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplaceXor_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplaceOr_internal(AtomicInt *self, PyObject *other, int do_refcount); + +PyObject *AtomicInt_InplaceFloorDivide_internal(AtomicInt *self, PyObject *other, int do_refcount); + +#endif //CEREGGII_ATOMIC_INT_INTERNAL_H diff --git a/tests/test_atomic_dict.py b/tests/test_atomic_dict.py index 837ebc41..38d5b595 100644 --- a/tests/test_atomic_dict.py +++ b/tests/test_atomic_dict.py @@ -35,7 +35,9 @@ def test_weird_init(): with raises(KeyError): assert d[1] == 0 d.debug() - gc.collect() + previous = None + while (this := gc.collect()) != previous: + previous = this assert d["iterable"] == {1: 0} with raises(TypeError): @@ -50,9 +52,13 @@ def test_debug(): assert "index" in dbg assert "blocks" in dbg del dbg - gc.collect() + previous = None + while (this := gc.collect()) != previous: + previous = this d.debug() - gc.collect() + previous = None + while (this := gc.collect()) != previous: + previous = this def test_log_size_bumped(): @@ -220,7 +226,9 @@ def test_node_size_64_unavailable(): def test_dealloc(): d = AtomicDict({"spam": 42}) del d - gc.collect() + previous = None + while (this := gc.collect()) != previous: + previous = this def test_concurrent_insert(): diff --git a/tests/test_atomic_int.py b/tests/test_atomic_int.py new file mode 100644 index 00000000..90f2ceaf --- /dev/null +++ b/tests/test_atomic_int.py @@ -0,0 +1,474 @@ +# SPDX-FileCopyrightText: 2023-present dpdani +# +# SPDX-License-Identifier: Apache-2.0 + +import gc +import threading +from fractions import Fraction +from typing import Union + +import cereggii +from cereggii import AtomicInt +from pytest import raises + + +def test_init(): + i = AtomicInt() + assert i.get() == 0 + i = AtomicInt(100) + assert i.get() == 100 + with raises(OverflowError): + AtomicInt(2**63) + + +def test_set(): + i = AtomicInt() + i.set(200) + assert i.get() == 200 + with raises(OverflowError): + i.set(2**63) + + +class Result: + def __init__(self): + self.result: Union[int, object, None] = 0 + + +def test_cas(): + r = AtomicInt() + result_0 = Result() + result_1 = Result() + + def thread(result, updated): + result.result = r.compare_and_set(0, updated) + + obj_0 = 300 + id_0 = id(obj_0) + t0 = threading.Thread(target=thread, args=(result_0, obj_0)) + obj_1 = 400 + id_1 = id(obj_1) + t1 = threading.Thread(target=thread, args=(result_1, obj_1)) + t0.start() + t1.start() + t0.join() + t1.join() + + assert sorted([result_0.result, result_1.result]) == [False, True] + assert id(obj_0) == id_0 and id(obj_1) == id_1 + + +def test_swap(): + i = AtomicInt(400) + result_0 = Result() + result_1 = Result() + + def thread(result, updated): + result.result = i.get_and_set(updated) + + obj_0 = 500 + id_0 = id(obj_0) + obj_1 = 600 + id_1 = id(obj_1) + t0 = threading.Thread( + target=thread, + args=( + result_0, + obj_0, + ), + ) + t1 = threading.Thread( + target=thread, + args=( + result_1, + obj_1, + ), + ) + t0.start() + t1.start() + t0.join() + t1.join() + results = [result_0.result, result_1.result] + + assert 400 in results + results.remove(400) + assert results[0] in [obj_0, obj_1] + assert id(obj_0) == id_0 and id(obj_1) == id_1 + + +def test_increment_and_get(): + assert AtomicInt(0).increment_and_get(1) == 1 + assert AtomicInt(0).increment_and_get(None) == 1 + assert AtomicInt(0).increment_and_get() == 1 + + +def test_get_and_increment(): + i = AtomicInt(0) + assert i.get_and_increment(1) == 0 + assert i.get() == 1 + i = AtomicInt(0) + assert i.get_and_increment(None) == 0 + assert i.get() == 1 + i = AtomicInt(0) + assert i.get_and_increment() == 0 + assert i.get() == 1 + + +def test_decrement_and_get(): + assert AtomicInt(0).decrement_and_get(1) == -1 + assert AtomicInt(0).decrement_and_get(None) == -1 + assert AtomicInt(0).decrement_and_get() == -1 + + +def test_get_and_decrement(): + i = AtomicInt(0) + assert i.get_and_decrement(1) == 0 + assert i.get() == -1 + i = AtomicInt(0) + assert i.get_and_decrement(None) == 0 + assert i.get() == -1 + i = AtomicInt(0) + assert i.get_and_decrement() == 0 + assert i.get() == -1 + + +def test_get_and_update(): + i = AtomicInt(0) + assert i.get_and_update(lambda _: (_ + 1) % 3) == 0 + assert i.get_and_update(lambda _: (_ + 1) % 3) == 1 + assert i.get_and_update(lambda _: (_ + 1) % 3) == 2 + assert i.get_and_update(lambda _: (_ + 1) % 3) == 0 + + with raises(TypeError): + i.get_and_update(lambda _, mmm: _ + mmm) + + +def test_update_and_get(): + i = AtomicInt(0) + assert i.update_and_get(lambda _: (_ + 1) % 3) == 1 + assert i.update_and_get(lambda _: (_ + 1) % 3) == 2 + assert i.update_and_get(lambda _: (_ + 1) % 3) == 0 + assert i.update_and_get(lambda _: (_ + 1) % 3) == 1 + + with raises(TypeError): + i.update_and_get(lambda _, mmm: _ + mmm) + + +def test_get_handle(): + i = AtomicInt() + h = i.get_handle() + assert isinstance(h, cereggii.AtomicIntHandle) + assert isinstance(h.get_handle(), cereggii.AtomicIntHandle) + + +def test_dealloc(): + AtomicInt() + previous = None + while (this := gc.collect()) != previous: + previous = this + + +def test_hash(): + i = AtomicInt(0) + assert type(hash(i)) == int # noqa: E721 + assert hash(i) != hash(0) + h = i.get_handle() + hh = h.get_handle() + assert hash(i) == hash(h) == hash(hh) + + +def test_eq(): + zero = AtomicInt(0) + assert zero == 0 + assert zero == 0.0 + assert zero == Fraction(0, 1) + assert zero == (0 + 0j) + + +def test_add(): + identity = AtomicInt(0) + assert identity + 1 == 1 + assert identity + 1.1 == 1.1 + assert identity + Fraction(-3, 2) == Fraction(-3, 2) + assert identity + (1 + 1j) == (1 + 1j) + + with raises(TypeError): + identity + None + with raises(TypeError): + identity + "spam" + with raises(TypeError): + identity + list() + with raises(TypeError): + identity + tuple() + with raises(TypeError): + identity + dict() + with raises(TypeError): + identity + set() + + +def test_sub(): + identity = AtomicInt(0) + assert identity - 1 == -1 + assert identity - 1.1 == -1.1 + assert identity - Fraction(-3, 2) == -Fraction(-3, 2) + assert identity - (1 + 1j) == -(1 + 1j) + + with raises(TypeError): + identity - None + with raises(TypeError): + identity - "spam" + with raises(TypeError): + identity - list() + with raises(TypeError): + identity - tuple() + with raises(TypeError): + identity - dict() + with raises(TypeError): + identity - set() + + +def test_mul(): + identity = AtomicInt(1) + assert identity * 0 == 0 + assert identity * 1.1 == 1.1 + assert identity * Fraction(-3, 2) == Fraction(-3, 2) + assert identity * (1 + 1j) == (1 + 1j) + assert identity * "spam" == "spam" + assert identity * list() == list() + assert identity * tuple() == tuple() + + with raises(TypeError): + identity * None + with raises(TypeError): + identity * dict() + with raises(TypeError): + identity * set() + + +def test_remainder(): + assert AtomicInt(100) % 9 == 1 + + +def test_divmod(): + assert divmod(AtomicInt(100), 9) == (11, 1) + + +def test_power(): + assert AtomicInt(2) ** 2 == 4 + + +def test_negative(): + assert -AtomicInt(1) == -1 + assert -AtomicInt(-1) == 1 + assert -AtomicInt(0) == 0 + + +def test_positive(): + assert +AtomicInt(1) == 1 + assert +AtomicInt(-1) == -1 + assert +AtomicInt(0) == 0 + + +def test_absolute(): + assert abs(AtomicInt(-1)) == 1 + assert abs(AtomicInt(1)) == 1 + assert abs(AtomicInt(0)) == 0 + + +def test_bool(): + i = bool(AtomicInt(100)) + assert i + assert type(i) == bool # noqa: E721 + + i = bool(AtomicInt(0)) + assert not i + assert type(i) == bool # noqa: E721 + + +def test_invert(): + assert -AtomicInt(1) == -1 + + +def test_lshift(): + assert AtomicInt(1) << 64 == 1 << 64 + + +def test_rshift(): + assert AtomicInt(1) >> 1 == 0 + + +def test_and(): + assert AtomicInt(128) & 128 == 128 + assert AtomicInt(128) & 64 == 0 + + +def test_xor(): + assert AtomicInt(128) ^ 128 == 0 + assert AtomicInt(128) ^ 64 == 128 + 64 + + +def test_or(): + assert AtomicInt(128) | 128 == 128 + assert AtomicInt(128) | 64 == 128 + 64 + + +def test_int(): + i = int(AtomicInt(100)) + assert i == 100 + assert type(i) == int # noqa: E721 + + +def test_float(): + f = float(AtomicInt(100)) + assert f == 100.0 + assert type(f) == float # noqa: E721 + + +def test_iadd(): + i = AtomicInt(0) + i += 2**62 + assert i.get() == 2**62 + + with raises(OverflowError): + i += 2**62 + + +def test_isub(): + i = AtomicInt(0) + i -= 2**62 + assert i.get() == -(2**62) + + with raises(OverflowError): + i -= 2**62 + 1 + + +def test_imul(): + i = AtomicInt(1) + i *= 2**62 + assert i.get() == 2**62 + + with raises(OverflowError): + i *= 2 + + +def test_iremainder(): + i = AtomicInt(100) + i %= 9 + assert i == 1 + + +def test_ipower(): + i = AtomicInt(100) + i **= 2 + assert i == 10000 + + +def test_ilshift(): + i = AtomicInt(100) + i <<= 9 + assert i == 51200 + + +def test_irshift(): + i = AtomicInt(100) + i >>= 9 + assert i == 0 + + +def test_iand(): + i = AtomicInt(100) + i &= 9 + assert i == 0 + + +def test_ixor(): + i = AtomicInt(100) + i ^= 9 + assert i == 109 + + +def test_ior(): + i = AtomicInt(100) + i |= 9 + assert i == 109 + + +def test_inplace_floor_divide(): + i = AtomicInt(500) + i //= 10 + assert i == 50 + + +def test_inplace_true_divide(): + i = AtomicInt() + with raises(NotImplementedError): + i /= 1 + + +def test_floor_divide(): + result = AtomicInt(500) // 10 + assert result == 50 + assert type(result) == int # noqa: E721 + + +def test_true_divide(): + result = AtomicInt(500) / 10 + assert result == 50 + assert type(result) == float # noqa: E721 + + +def test_index(): + i = AtomicInt(0) + assert i.__index__() == 0 + + +def test_as_integer_ratio(): + with raises(NotImplementedError): + AtomicInt().as_integer_ratio() + + +def test_bit_length(): + with raises(NotImplementedError): + AtomicInt().bit_length() + + +def test_conjugate(): + with raises(NotImplementedError): + AtomicInt().conjugate() + + +def test_from_bytes(): + with raises(NotImplementedError): + AtomicInt().from_bytes() + + +def test_to_bytes(): + with raises(NotImplementedError): + AtomicInt().to_bytes() + + +def test_denominator(): + with raises(NotImplementedError): + AtomicInt().denominator # noqa: B018 + with raises(NotImplementedError): + AtomicInt().denominator.__set__(0) + + +def test_numerator(): + with raises(NotImplementedError): + AtomicInt().numerator # noqa: B018 + with raises(NotImplementedError): + AtomicInt().numerator.__set__(0) + + +def test_imag(): + with raises(NotImplementedError): + AtomicInt().imag # noqa: B018 + with raises(NotImplementedError): + AtomicInt().imag.__set__(0) + + +def test_real(): + with raises(NotImplementedError): + AtomicInt().real # noqa: B018 + with raises(NotImplementedError): + AtomicInt().real.__set__(0) diff --git a/tests/test_atomic_ref.py b/tests/test_atomic_ref.py index c0b61c57..bf213e55 100644 --- a/tests/test_atomic_ref.py +++ b/tests/test_atomic_ref.py @@ -99,5 +99,7 @@ def test_dealloc(): r = AtomicRef(obj) assert r.get() is obj del r - gc.collect() + previous = None + while (this := gc.collect()) != previous: + previous = this assert id(obj) == id_obj