From 05ab5226e73dc51a22dfb541eb2bba908cfcfe2a Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Tue, 20 Aug 2024 18:36:25 +0200 Subject: [PATCH] iox-#2329 Add iox::Atomic --- iceoryx_platform/BUILD.bazel | 1 + iceoryx_platform/CMakeLists.txt | 2 +- .../include/iceoryx_platform/atomic.hpp | 276 ++++++++++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 iceoryx_platform/generic/include/iceoryx_platform/atomic.hpp diff --git a/iceoryx_platform/BUILD.bazel b/iceoryx_platform/BUILD.bazel index ccfc1bf456..12ba0ba17e 100644 --- a/iceoryx_platform/BUILD.bazel +++ b/iceoryx_platform/BUILD.bazel @@ -145,6 +145,7 @@ cc_library( ], linkopts = select({ ":linux": [ + "-latomic", "-lpthread", "-lrt", ], diff --git a/iceoryx_platform/CMakeLists.txt b/iceoryx_platform/CMakeLists.txt index 964ab7ed60..e3a5d70782 100644 --- a/iceoryx_platform/CMakeLists.txt +++ b/iceoryx_platform/CMakeLists.txt @@ -79,7 +79,7 @@ iox_add_library( PROJECT_PREFIX ${PREFIX} EXPORT_INCLUDE_DIRS generic/include/ ${ICEORYX_PLATFORM}/include/ - PUBLIC_LIBS_LINUX rt pthread + PUBLIC_LIBS_LINUX atomic rt pthread PUBLIC_LIBS_UNIX rt pthread PRIVATE_LIBS_QNX socket PUBLIC_LIBS_FREERTOS freertos_sdk::FreeRTOS_POSIX diff --git a/iceoryx_platform/generic/include/iceoryx_platform/atomic.hpp b/iceoryx_platform/generic/include/iceoryx_platform/atomic.hpp new file mode 100644 index 0000000000..46863ec78b --- /dev/null +++ b/iceoryx_platform/generic/include/iceoryx_platform/atomic.hpp @@ -0,0 +1,276 @@ +// Copyright (c) 2024 by ekxide IO GmbH. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef IOX_PLATFORM_ATOMIC_HPP +#define IOX_PLATFORM_ATOMIC_HPP + +#include + +namespace iox +{ + +/// @brief A thin wrapper for a 'std::atomic' which ensures that all atomic operations are always lock-free in order to +/// ensure safe usage across processes in shared memory. For detailed documentation please have a look at +/// https://en.cppreference.com/w/cpp/atomic/atomic +template +class Atomic +{ + public: + static_assert(std::atomic::is_always_lock_free, + "The 'iox::Atomic' must work across process boundaries and must therefore be always lock-free!"); + + /// @brief This is always 'true' for 'iox::Atomic' + static constexpr bool is_always_lock_free = std::atomic::is_always_lock_free; + + /// @brief Constructs a new 'iox::Atomic' with a default value + constexpr Atomic() noexcept + : m_value{T()} + { + } + + /// @brief Constructs a new 'iox::Atomic' with the given value + constexpr Atomic(T value) noexcept + : m_value{value} + { + } + + /// @brief Similar to the std::atomic, the 'iox::Atomic' is not copy constructible + Atomic(const Atomic& other) = delete; + /// @brief Similar to the std::atomic, the 'iox::Atomic' is not copy assignable + Atomic& operator=(const Atomic&) = delete; + + Atomic(Atomic&& rhs) noexcept = default; + Atomic& operator=(Atomic&& rhs) noexcept = default; + + /// @brief Atomically assigns the given value to the 'iox::Atomic' and returns the given value. Equivalent to + /// 'store(value)' + T operator=(T value) noexcept + { + m_value = value; + return value; + } + + /// @brief Atomically loads and returns the stored value. Equivalent to 'load()' + operator T() const noexcept + { + return m_value.operator T(); + } + + /// @brief Return true if all operations on an object of this type are lock-free + bool is_lock_free() const noexcept + { + return m_value.is_lock_free(); + } + + /// @brief Atomically stores the given value with the given memory order + void store(T value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + m_value.store(value, order); + } + + /// @brief Atomically loads and returns the stored value + T load(std::memory_order order = std::memory_order_seq_cst) const noexcept + { + return m_value.load(order); + } + + /// @brief Atomically exchanges the given value with the stored value using the given memory order and returns the + /// previous value + T exchange(T value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.exchange(value, order); + } + + /// @brief Performs an atomic CAS operation on the stored value with the given desired value and the given memory + /// orders for success and failure. If current value is not the expected value, the CAS operation fails and updates + /// the expected value with the current value. Returns true on success and false on failure. The operation can fail + /// spuriously even when the current value equals the expected value. + bool compare_exchange_weak(T& expected, T desired, std::memory_order success, std::memory_order failure) noexcept + { + return m_value.compare_exchange_weak(expected, desired, success, failure); + } + + /// @brief Performs an atomic CAS operation on the stored value with the given desired value and the given memory + /// order for both success and failure. If current value is not the expected value, the CAS operation fails and + /// updates the expected value with the current value. Returns true on success and false on failure. The operation + /// can fail spuriously even when the current value equals the expected value. + bool compare_exchange_weak(T& expected, T desired, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.compare_exchange_weak(expected, desired, order); + } + + /// @brief Performs an atomic CAS operation on the stored value with the given desired value and the given memory + /// orders for success and failure. If current value is not the expected value, the CAS operation fails and updates + /// the expected value with the current value. Returns true on success and false on failure. + bool compare_exchange_strong(T& expected, T desired, std::memory_order success, std::memory_order failure) noexcept + { + return m_value.compare_exchange_strong(expected, desired, success, failure); + } + + /// @brief Performs an atomic CAS operation on the stored value with the given desired value and the given memory + /// order for both success and failure. If current value is not the expected value, the CAS operation fails and + /// updates the expected value with the current value. Returns true on success and false on failure. + bool compare_exchange_strong(T& expected, T desired, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.compare_exchange_strong(expected, desired, order); + } + + /// @brief Atomically adds the given value to the stored value with the given memory order and returns the previous + /// value + template >> + T fetch_add(T value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.fetch_add(value, order); + } + + /// @brie Atomically adds the given difference to the stored pointer value with the given memory order and returns + /// the previous pointer value + template >> + T fetch_add(std::ptrdiff_t value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.fetch_add(value, order); + } + + /// @brief Atomically substracts the given value to the stored value with the given memory order and returns the + /// previous value + template >> + T fetch_sub(T value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.fetch_sub(value, order); + } + + /// @brie Atomically substracts the given difference to the stored pointer value with the given memory order and + /// returns the previous pointer value + template >> + T fetch_sub(std::ptrdiff_t value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.fetch_sub(value, order); + } + + /// @brief Atomically adds the given value to the stored value and returns the resulting value. Equivalent to + /// 'fetch_add(value) + value' + template || std::is_floating_point_v>> + T operator+=(T value) noexcept + { + return m_value.operator+=(value); + } + + /// @brief Atomically substracts the given value to the stored value and returns the resulting value. Equivalent to + /// 'fetch_sub(value) - value' + template || std::is_floating_point_v>> + T operator-=(T value) noexcept + { + return m_value.operator-=(value); + } + + /// @brief Atomically adds the given difference to the stored pointer value and returns the resulting new pointer + /// value. Equivalent to 'fetch_add(value) + value' + template >> + T operator+=(std::ptrdiff_t value) noexcept + { + return m_value.operator+=(value); + } + + /// @brief Atomically substracts the given difference to the stored pointer value and returns the resulting new + /// pointer value. Equivalent to 'fetch_sub(value) - value' + template >> + T operator-=(std::ptrdiff_t value) noexcept + { + return m_value.operator-=(value); + } + + /// @brief Atomic pre-increment operator, equivalent to 'return fetch_add(1) + 1' + template || std::is_pointer_v>> + T operator++() noexcept + { + return m_value.operator++(); + } + + /// @brief Atomic post-increment operator, equivalent to 'return fetch_add(1)' + template || std::is_pointer_v>> + T operator++(int) noexcept + { + return m_value.operator++(0); + } + + /// @brief Atomic pre-decrement operator, equivalent to 'return fetch_sub(1) - 1' + template || std::is_pointer_v>> + T operator--() noexcept + { + return m_value.operator--(); + } + + /// @brief Atomic post-decrement operator, equivalent to 'return fetch_sub(1)' + template || std::is_pointer_v>> + T operator--(int) noexcept + { + return m_value.oprator--(0); + } + + /// @brief Atomically performs a bitwise 'AND' operation to the stored value with the given memory order and returns + /// the previous value + template >> + T fetch_and(T value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.fetch_and(value, order); + } + + /// @brief Atomically performs a bitwise 'OR' operation to the stored value with the given memory order and returns + /// the previous value + template >> + T fetch_or(T value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.fetch_or(value, order); + } + + /// @brief Atomically performs a bitwise 'XOR' operation to the stored value with the given memory order and returns + /// the previous value + template >> + T fetch_xor(T value, std::memory_order order = std::memory_order_seq_cst) noexcept + { + return m_value.fetch_xor(value, order); + } + + /// @brief Atomically performs a bitwise 'AND' operation to the stored value and returns the resulting value, + /// Equivalent to 'return fetch_and(value) & value;' + template >> + T operator&=(T value) noexcept + { + return m_value.operator&=(value); + } + + /// @brief Atomically performs a bitwise 'OR' operation to the stored value and returns the resulting value, + /// Equivalent to 'return fetch_or(value) & value;' + template >> + T operator|=(T value) noexcept + { + return m_value.operator|=(value); + } + + /// @brief Atomically performs a bitwise 'OR' operation to the stored value and returns the resulting value, + /// Equivalent to 'return fetch_xor(value) & value;' + template >> + T operator^=(T value) noexcept + { + return m_value.operator^=(value); + } + + private: + std::atomic m_value; +}; +} // namespace iox + +#endif // IOX_PLATFORM_ATOMIC_HPP