Files
MentOS/mentos/inc/klib/stdatomic.h
T
2023-01-18 14:20:13 -05:00

185 lines
6.7 KiB
C

/// @file stdatomic.h
/// @brief
/// @copyright (c) 2014-2022 This file is distributed under the MIT License.
/// See LICENSE.md for details.
#pragma once
#include "klib/compiler.h"
/// @brief Standard structure for atomic operations (see below
/// for volatile explanation).
typedef volatile unsigned atomic_t;
/// @brief The prefix used to lock.
#define LOCK_PREFIX "\n\tlock; "
/// @brief Compile read-write barrier.
#define barrier() __asm__ __volatile__("" \
: \
: \
: "memory")
/// @brief Pause instruction to prevent excess processor bus usage.
#define cpu_relax() __asm__ __volatile__("pause\n" \
: \
: \
: "memory")
/// @brief Atomically sets `value` at `ptr`.
/// @param ptr the pointer we are working with.
/// @param value the value to set.
/// @return The final value of the atomic variable.
inline static int atomic_set_and_test(atomic_t *ptr, int value)
{
// The + in "+r" and "+m" denotes a read-modify-write operand.
__asm__ __volatile__(LOCK_PREFIX // Lock
"xchgl %0, %1" // Instruction
: "+r"(value), "+m"(*ptr) // Input + Output
: // No input-only
: "memory"); // Side effects
return value;
}
/// @brief Atomically set the value pointed by `ptr` to `value`.
/// @param ptr the pointer we are working with.
/// @param value the value we need to set.
inline static void atomic_set(atomic_t *ptr, int value)
{
atomic_set_and_test(ptr, value);
}
/// @brief Atomically read the value pointed by `ptr`.
/// @param ptr the pointer we are working with.
/// @return the value we read.
inline static int atomic_read(const atomic_t *ptr)
{
return READ_ONCE(*ptr);
}
/// @brief Atomically add `value` to the value pointed by `ptr`.
/// @param ptr the pointer we are working with.
/// @param value the value we need to add.
/// @return the result of the operation.
inline static int atomic_add(atomic_t *ptr, int value)
{
// The + in "+r" and "+m" denotes a read-modify-write operand.
__asm__ __volatile__(LOCK_PREFIX // Lock
"xaddl %0, %1" // Instruction
: "+r"(value), "+m"(*ptr) // Input + Output
: // No input-only
: "memory"); // Side effects
return value;
}
/// @brief Atomically subtract `value` from the value pointed by `ptr`.
/// @param ptr the pointer we are working with.
/// @param value the value we need to subtract.
/// @return the result of the operation.
inline static int atomic_sub(atomic_t *ptr, int value)
{
return atomic_add(ptr, -value);
}
/// @brief Atomically increment the value at `ptr`.
/// @param ptr the pointer we are working with.
/// @return the result of the operation.
inline static int atomic_inc(atomic_t *ptr)
{
return atomic_add(ptr, 1);
}
/// @brief Atomically decrement the value at `ptr`.
/// @param ptr the pointer we are working with.
/// @return the result of the operation.
inline static int atomic_dec(atomic_t *ptr)
{
return atomic_add(ptr, -1);
}
/// @brief Atomically add `value` to `ptr` and checks if the result is negative.
/// @param ptr the pointer we are working with.
/// @param value the value we need to add.
/// @return true if the result is negative, false otherwise.
inline static int atomic_add_negative(atomic_t *ptr, int value)
{
return atomic_add(ptr, value) < 0;
}
/// @brief Atomically subtract `value` from `ptr` and checks if the result is zero.
/// @param ptr the pointer we are working with.
/// @param value the value we need to subtract.
/// @return true if the result is zero, false otherwise.
inline static int atomic_sub_and_test(atomic_t *ptr, int value)
{
return atomic_sub(ptr, value) == 0;
}
/// @brief Atomically increment `ptr` and checks if the result is zero.
/// @param ptr the pointer we are working with.
/// @return true if the result is zero, false otherwise.
inline static int atomic_inc_and_test(atomic_t *ptr)
{
return atomic_inc(ptr) == 0;
}
/// @brief Atomically decrement `ptr` and checks if the result is zero.
/// @param ptr the pointer we are working with.
/// @return true if the result is zero, false otherwise.
inline static int atomic_dec_and_test(atomic_t *ptr)
{
return atomic_dec(ptr) == 0;
}
/// @brief Atomically sets a bit in memory, using Bit Test And Set (bts).
/// @param offset The offset to the bit.
/// @param base The base address.
static inline void set_bit(int offset, volatile unsigned long *base)
{
__asm__ __volatile__("btsl %[offset], %[base]"
: [base] "=m"(*(volatile long *)base)
: [offset] "Ir"(offset));
}
/// @brief Atomically clears a bit in memory.
/// @param offset The offset to the bit.
/// @param base The base address.
static inline void clear_bit(int offset, volatile unsigned long *base)
{
__asm__ __volatile__("btrl %[offset],%[base]"
: [base] "=m"(*(volatile long *)base)
: [offset] "Ir"(offset));
}
/// @brief Atomically tests a bit in memory.
/// @param offset The offset to the bit.
/// @param base The base address.
/// @return 1 if the bit is set, 0 otherwise.
static inline int test_bit(int offset, volatile unsigned long *base)
{
int old = 0;
__asm__ __volatile__("btl %[offset],%[base]\n" // Bit Test
"sbbl %[old],%[old]\n" // Return the previous value.
: [old] "=r"(old)
: [base] "m"(*(volatile long *)base),
[offset] "Ir"(offset));
return old;
}
// == Volatile Variable =======================================================
// In C, and consequently C++, the volatile keyword was intended to:
// - allow access to memory-mapped I/O devices
// - allow uses of variables between setjmp and longjmp
// - allow uses of sig_atomic_t variables in signal handlers.
//
// Operations on volatile variables are not atomic, nor do they establish
// a proper happens-before relationship for threading like with the
// `__asm__` inline blocks.
// This is specified in the relevant standards (C, C++, POSIX, WIN32), and
// volatile variables are not thread-safe in the vast majority of current
// implementations.
// Thus, the usage of volatile keyword as a portable synchronization mechanism
// is discouraged by many C/C++ groups.
// == xchg/xchgl ==============================================================