| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #include <cassert> | |
| #include <cdefs.h> | |
| #include <futex.h> | |
| #include <limits> | |
| #include <stdint.h> | |
| /** | |
| * The helper functions need to expose an unmangled name because the compiler | |
| * inserts calls to them. Declare them using the asm label extension. | |
| */ | |
| #define DECLARE_ATOMIC_LIBCALL(name, ret, ...) \ | |
| [[cheri::interrupt_state(disabled)]] __cheri_libcall ret name( \ | |
| __VA_ARGS__) asm(#name); | |
| DECLARE_ATOMIC_LIBCALL(__cxa_guard_acquire, int, uint64_t *) | |
| DECLARE_ATOMIC_LIBCALL(__cxa_guard_release, void, uint64_t *) | |
| DECLARE_ATOMIC_LIBCALL(__cxa_atexit, int, void (*)(void *), void *, void *) | |
| namespace | |
| { | |
| /** | |
| * Helper for operating on the guard word. The guard word is a 64-bit value | |
| * where the low bit indicates that the variable is initialised and the | |
| * high bit indicates that it's locked. | |
| */ | |
| class GuardWord | |
| { | |
| /// The low half (first on a little-endian system). | |
| uint32_t low; | |
| /// The high half (second on a little-endian system). | |
| uint32_t high; | |
| /// The bit used for the lock (the high bit on a little-endian system) | |
| static constexpr uint32_t LockBit = uint32_t(1) << 31; | |
| public: | |
| /** | |
| * Returns true if this guard is initialised. | |
| */ | |
| bool is_initialised() | |
| { | |
| return low & 1; | |
| } | |
| /** | |
| * Sets that this guard is initialised. | |
| */ | |
| void set_initialised() | |
| { | |
| low = 1; | |
| } | |
| /** | |
| * Acquire the lock. | |
| */ | |
| void lock() | |
| { | |
| // Block until the lock word is 0, then set it. | |
| while (high & LockBit) | |
| { | |
| futex_wait(&high, LockBit); | |
| } | |
| assert(high == 0); | |
| high = LockBit; | |
| } | |
| /** | |
| * Release the lock | |
| */ | |
| void unlock() | |
| { | |
| assert(high == LockBit); | |
| high = 0; | |
| futex_wake(&high, std::numeric_limits<uint32_t>::max()); | |
| } | |
| /** | |
| * Returns true if the lock is held. | |
| */ | |
| bool is_locked() | |
| { | |
| return high == LockBit; | |
| } | |
| }; | |
| } // namespace | |
| /** | |
| * Acquire the lock for a guard word associated with a static. Returns 0 if | |
| * the variable has already been initialised, 1 otherwise. If this returns 1, | |
| * then it has acquired the lock, which must be released with | |
| * `__cxa_guard_release`. | |
| */ | |
| int __cxa_guard_acquire(uint64_t *guard) | |
| { | |
| auto *g = reinterpret_cast<GuardWord *>(guard); | |
| if (g->is_initialised()) | |
| { | |
| return 0; | |
| } | |
| g->lock(); | |
| return 1; | |
| } | |
| /** | |
| * Release a guard word and mark the variable as initialised. | |
| */ | |
| void __cxa_guard_release(uint64_t *guard) | |
| { | |
| auto *g = reinterpret_cast<GuardWord *>(guard); | |
| assert(!g->is_initialised()); | |
| assert(g->is_locked()); | |
| g->set_initialised(); | |
| g->unlock(); | |
| } | |
| /** | |
| * Register a global destructor. We have no notion of program end and so this | |
| * does nothing. | |
| */ | |
| int __cxa_atexit(void (*)(void *), void *, void *) | |
| { | |
| return 0; | |
| } |