/** * @file Atomic.h * @author apio (cloudapio.eu) * @brief Atomic value operations. * * @copyright Copyright (c) 2022-2023, the Luna authors. * */ #pragma once /** * @brief An enum representing the different C++11 memory orders. */ enum class MemoryOrder { Relaxed = __ATOMIC_RELAXED, Consume = __ATOMIC_CONSUME, Acquire = __ATOMIC_ACQUIRE, Release = __ATOMIC_RELEASE, AcqRel = __ATOMIC_ACQ_REL, SeqCst = __ATOMIC_SEQ_CST, }; /** * @brief An atomic wrapper around a simple integer value. * * @tparam T The type of the value. */ template class Atomic { public: /** * @brief Construct a new Atomic object. */ Atomic() : m_value() { } /** * @brief Construct a new Atomic object. * * @param value The value to use. */ Atomic(T value) : m_value(value) { } /** * @brief Store a new value inside this object. * * @param other The value to store. * @return T The updated value. */ T operator=(T other) { store(other); return other; } /** * @brief Perform an atomic load of the stored value. * * @param order The memory order to use (keep it to the default if you don't know what you're doing). * @return T The loaded value. */ T load(MemoryOrder order = MemoryOrder::SeqCst) const { return __atomic_load_n(&m_value, (int)order); } /** * @brief Return the stored value atomically. * * @return T The stored value. */ operator T() const { return load(); } /** * @brief Store a new value atomically. * * @param value The value to store. * @param order The memory order to use (keep it to the default if you don't know what you're doing). */ void store(T value, MemoryOrder order = MemoryOrder::SeqCst) { return __atomic_store_n(&m_value, value, (int)order); } /** * @brief Store a new value atomically, and return the old value. * * @param value The value to store. * @param order The memory order to use (keep it to the default if you don't know what you're doing). * @return T The old value. */ T exchange(T value, MemoryOrder order = MemoryOrder::SeqCst) { return __atomic_exchange_n(&m_value, value, (int)order); } /** * @brief Compare the current value against an expected value and exchange it with a desired value only if they * match, all in a single atomic step. * * @param expected The expected value. After compare_exchange_strong returns, it will be set to the value that used * to be held. * @param desired The value to store if the two values match. * @param success The memory order to use in case of success. * @param failure The memory order to use in case of failure. * @return true The values match and the current value was updated to match desired. * @return false The values did not match and the current value stays the same. */ bool compare_exchange_strong(T& expected, T desired, MemoryOrder success, MemoryOrder failure) { return __atomic_compare_exchange_n(&m_value, &expected, desired, false, (int)success, (int)failure); } /** * @brief Compare the current value against an expected value and exchange it with a desired value only if they * match, all in a single atomic step. * * @param expected The expected value. After compare_exchange_strong returns, it will be set to the value that used * to be held. * @param desired The value to store if the two values match. * @param order The memory order to use (keep it to the default if you don't know what you're doing). * @return true The values match and the current value was updated to match desired. * @return false The values did not match and the current value stays the same. */ bool compare_exchange_strong(T& expected, T desired, MemoryOrder order = MemoryOrder::SeqCst) { MemoryOrder failure = (order == MemoryOrder::AcqRel) ? MemoryOrder::Acquire : (order == MemoryOrder::Release) ? MemoryOrder::Relaxed : order; return __atomic_compare_exchange_n(&m_value, &expected, desired, false, (int)order, (int)failure); } /** * @brief Compare the current value against an expected value and exchange it with a desired value only if they * match, all in a single atomic step. * * This variant may be more performant than compare_exchange_strong, but is allowed to sometimes spuriously fail * (fail even if the two values did match). * * @param expected The expected value. After compare_exchange_weak returns, it will be set to the value that used * to be held. * @param desired The value to store if the two values match. * @param success The memory order to use in case of success. * @param failure The memory order to use in case of failure. * @return true The values match and the current value was updated to match desired. * @return false The values did not match (or there was a spurious failure) and the current value stays the same. */ bool compare_exchange_weak(T& expected, T desired, MemoryOrder success, MemoryOrder failure) { return __atomic_compare_exchange_n(&m_value, &expected, desired, true, (int)success, (int)failure); } /** * @brief Compare the current value against an expected value and exchange it with a desired value only if they * match, all in a single atomic step. * * This variant may be more performant than compare_exchange_strong, but is allowed to sometimes spuriously fail * (fail even if the two values did match). * * @param expected The expected value. After compare_exchange_weak returns, it will be set to the value that used * to be held. * @param desired The value to store if the two values match. * @param order The memory order to use (keep it to the default if you don't know what you're doing). * @return true The values match and the current value was updated to match desired. * @return false The values did not match (or there was a spurious failure) and the current value stays the same. */ bool compare_exchange_weak(T& expected, T desired, MemoryOrder order = MemoryOrder::SeqCst) { MemoryOrder failure = (order == MemoryOrder::AcqRel) ? MemoryOrder::Acquire : (order == MemoryOrder::Release) ? MemoryOrder::Relaxed : order; return __atomic_compare_exchange_n(&m_value, &expected, desired, true, (int)order, (int)failure); } /** * @brief Atomically add a value to the stored value and return the previous value. * * @param other The value to add. * @param order The memory order to use (keep it to the default if you don't know what you're doing). * @return T The original stored value. */ T fetch_add(T other, MemoryOrder order = MemoryOrder::SeqCst) { return __atomic_fetch_add(&m_value, other, (int)order); } /** * @brief Atomically subtract a value from the stored value and return the previous value. * * @param other The value to add. * @param order The memory order to use (keep it to the default if you don't know what you're doing). * @return T The original stored value. */ T fetch_sub(T other, MemoryOrder order = MemoryOrder::SeqCst) { return __atomic_fetch_sub(&m_value, other, (int)order); } /** * @brief Increment the stored value and return it. * * @return T The new value. */ T operator++() { return fetch_add(1) + 1; } /** * @brief Fetch the stored value, increment it and return the original value. * * @return T The original value. */ T operator++(int) { return fetch_add(1); } /** * @brief Decrement the stored value and return it. * * @return T The new value. */ T operator--() { return fetch_sub(1) - 1; } /** * @brief Fetch the stored value, decrement it and return the original value. * * @return T The original value. */ T operator--(int) { return fetch_sub(1); } /** * @brief Add a value to the stored value and return the result. * * @param other The value to add. * @return T The resulting value. */ T operator+=(const T& other) { return fetch_add(other) + other; } /** * @brief Subtract a value from the stored value and return the result. * * @param other The value to subtract. * @return T The resulting value. */ T operator-=(const T& other) { return fetch_sub(other) - other; } private: T m_value; };