diff --git a/libluna/include/luna/Atomic.h b/libluna/include/luna/Atomic.h index f2c9cd79..3838d943 100644 --- a/libluna/include/luna/Atomic.h +++ b/libluna/include/luna/Atomic.h @@ -1,5 +1,17 @@ +/** + * @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, @@ -10,48 +22,114 @@ enum class MemoryOrder 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 @@ -60,11 +138,40 @@ template class Atomic 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 @@ -73,41 +180,87 @@ template class Atomic 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;