Skip to content

Commit

Permalink
iox-#2329 Add iox::Atomic
Browse files Browse the repository at this point in the history
  • Loading branch information
elBoberido committed Aug 20, 2024
1 parent 912931a commit 05ab522
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 1 deletion.
1 change: 1 addition & 0 deletions iceoryx_platform/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ cc_library(
],
linkopts = select({
":linux": [
"-latomic",
"-lpthread",
"-lrt",
],
Expand Down
2 changes: 1 addition & 1 deletion iceoryx_platform/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
276 changes: 276 additions & 0 deletions iceoryx_platform/generic/include/iceoryx_platform/atomic.hpp
Original file line number Diff line number Diff line change
@@ -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 <atomic>

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 <typename T>
class Atomic
{
public:
static_assert(std::atomic<T>::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<T>::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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_pointer_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_pointer_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U> || std::is_floating_point_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U> || std::is_floating_point_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_pointer_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_pointer_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U> || std::is_pointer_v<U>>>
T operator++() noexcept
{
return m_value.operator++();
}

/// @brief Atomic post-increment operator, equivalent to 'return fetch_add(1)'
template <typename U = T, typename = std::enable_if_t<std::is_integral_v<U> || std::is_pointer_v<U>>>
T operator++(int) noexcept
{
return m_value.operator++(0);
}

/// @brief Atomic pre-decrement operator, equivalent to 'return fetch_sub(1) - 1'
template <typename U = T, typename = std::enable_if_t<std::is_integral_v<U> || std::is_pointer_v<U>>>
T operator--() noexcept
{
return m_value.operator--();
}

/// @brief Atomic post-decrement operator, equivalent to 'return fetch_sub(1)'
template <typename U = T, typename = std::enable_if_t<std::is_integral_v<U> || std::is_pointer_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U>>>
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 <typename U = T, typename = std::enable_if_t<std::is_integral_v<U>>>
T operator^=(T value) noexcept
{
return m_value.operator^=(value);
}

private:
std::atomic<T> m_value;
};
} // namespace iox

#endif // IOX_PLATFORM_ATOMIC_HPP

0 comments on commit 05ab522

Please sign in to comment.