blob: a60f690dd96403a3e2b4f21e904929d4f0ce148c [file] [log] [blame]
// 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;
}