#pragma once #include "util/types.hpp" #include "util/atomic.hpp" #include "util/StrFmt.h" // Pessimistic mutex for slow operation, does not spin wait, occupies only one byte class slow_mutex { atomic_t m_value{0}; public: constexpr slow_mutex() noexcept = default; void lock() noexcept { // Two-stage locking: increment, then wait while (true) { const u8 prev = m_value.fetch_op([](u8& val) { if ((val & 0x7f) == 0x7f) [[unlikely]] return; val++; }); if ((prev & 0x7f) == 0x7f) [[unlikely]] { // Keep trying until counter can be incremented m_value.wait(0x7f, 0x7f); } else if (prev == 0) { // Locked return; } else { // Normal waiting break; } } // Wait for signal bit m_value.wait(0, 0x80); m_value &= ~0x80; } bool try_lock() noexcept { return m_value.compare_and_swap_test(0, 1); } void unlock() noexcept { const u8 prev = m_value.fetch_op([](u8& val) { if (val) [[likely]] val--; }); if (prev == 0) [[unlikely]] { fmt::throw_exception("I tried to unlock unlocked mutex."); } // Set signal and notify if (prev & 0x7f) { m_value |= 0x80; m_value.notify_one(0x80); } if ((prev & 0x7f) == 0x7f) [[unlikely]] { // Overflow notify: value can be incremented m_value.notify_one(0x7f); } } bool is_free() const noexcept { return !m_value; } void lock_unlock() noexcept { if (m_value) { lock(); unlock(); } } };