//
// Copyright 2023 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// GlobalMutex.cpp: Defines Global Mutex and utilities.

#include "libANGLE/GlobalMutex.h"

#include <atomic>

#include "common/debug.h"
#include "common/system_utils.h"

namespace egl
{
namespace priv
{
using GlobalMutexType = std::mutex;

#if !defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
// Default version.
class GlobalMutex final : angle::NonCopyable
{
  public:
    ANGLE_INLINE void lock() { mMutex.lock(); }
    ANGLE_INLINE void unlock() { mMutex.unlock(); }

  protected:
    GlobalMutexType mMutex;
};
#endif

#if defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
// Debug version.
class GlobalMutex final : angle::NonCopyable
{
  public:
    ANGLE_INLINE void lock()
    {
        const angle::ThreadId threadId = angle::GetCurrentThreadId();
        ASSERT(getOwnerThreadId() != threadId);
        mMutex.lock();
        ASSERT(getOwnerThreadId() == angle::InvalidThreadId());
        mOwnerThreadId.store(threadId, std::memory_order_relaxed);
    }

    ANGLE_INLINE void unlock()
    {
        ASSERT(getOwnerThreadId() == angle::GetCurrentThreadId());
        mOwnerThreadId.store(angle::InvalidThreadId(), std::memory_order_relaxed);
        mMutex.unlock();
    }

  private:
    ANGLE_INLINE angle::ThreadId getOwnerThreadId() const
    {
        return mOwnerThreadId.load(std::memory_order_relaxed);
    }

    GlobalMutexType mMutex;
    std::atomic<angle::ThreadId> mOwnerThreadId{angle::InvalidThreadId()};
};
#endif  // defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)

#if defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
// Recursive version.
class GlobalMutex final : angle::NonCopyable
{
  public:
    ANGLE_INLINE void lock()
    {
        const angle::ThreadId threadId = angle::GetCurrentThreadId();
        if (ANGLE_UNLIKELY(!mMutex.try_lock()))
        {
            if (ANGLE_UNLIKELY(getOwnerThreadId() == threadId))
            {
                ASSERT(mLockLevel > 0);
                ++mLockLevel;
                return;
            }
            mMutex.lock();
        }
        ASSERT(getOwnerThreadId() == angle::InvalidThreadId());
        ASSERT(mLockLevel == 0);
        mOwnerThreadId.store(threadId, std::memory_order_relaxed);
        mLockLevel = 1;
    }

    ANGLE_INLINE void unlock()
    {
        ASSERT(getOwnerThreadId() == angle::GetCurrentThreadId());
        ASSERT(mLockLevel > 0);
        if (ANGLE_LIKELY(--mLockLevel == 0))
        {
            mOwnerThreadId.store(angle::InvalidThreadId(), std::memory_order_relaxed);
            mMutex.unlock();
        }
    }

  private:
    ANGLE_INLINE angle::ThreadId getOwnerThreadId() const
    {
        return mOwnerThreadId.load(std::memory_order_relaxed);
    }

    GlobalMutexType mMutex;
    std::atomic<angle::ThreadId> mOwnerThreadId{angle::InvalidThreadId()};
    uint32_t mLockLevel = 0;
};
#endif  // defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION)
namespace
{
#if defined(ANGLE_ENABLE_GLOBAL_MUTEX_LOAD_TIME_ALLOCATE)
#    if !ANGLE_HAS_ATTRIBUTE_CONSTRUCTOR || !ANGLE_HAS_ATTRIBUTE_DESTRUCTOR
#        error \
            "'angle_enable_global_mutex_load_time_allocate' " \
               "requires constructor/destructor compiler atributes."
#    endif
GlobalMutex *g_MutexPtr        = nullptr;
GlobalMutex *g_EGLSyncMutexPtr = nullptr;

void ANGLE_CONSTRUCTOR AllocateGlobalMutex()
{
    ASSERT(g_MutexPtr == nullptr);
    g_MutexPtr = new GlobalMutex();
    ASSERT(g_EGLSyncMutexPtr == nullptr);
    g_EGLSyncMutexPtr = new GlobalMutex();
}

void ANGLE_DESTRUCTOR DeallocateGlobalMutex()
{
    SafeDelete(g_MutexPtr);
    SafeDelete(g_EGLSyncMutexPtr);
}
#else
ANGLE_REQUIRE_CONSTANT_INIT std::atomic<GlobalMutex *> g_Mutex(nullptr);
ANGLE_REQUIRE_CONSTANT_INIT std::atomic<GlobalMutex *> g_EGLSyncMutex(nullptr);
static_assert(std::is_trivially_destructible<decltype(g_Mutex)>::value,
              "global mutex is not trivially destructible");
static_assert(std::is_trivially_destructible<decltype(g_EGLSyncMutex)>::value,
              "global EGL Sync mutex is not trivially destructible");

GlobalMutex *AllocateGlobalMutexImpl(std::atomic<GlobalMutex *> *globalMutex)
{
    GlobalMutex *currentMutex = nullptr;
    std::unique_ptr<GlobalMutex> newMutex(new GlobalMutex());
    do
    {
        if (globalMutex->compare_exchange_weak(currentMutex, newMutex.get()))
        {
            return newMutex.release();
        }
    } while (currentMutex == nullptr);
    return currentMutex;
}

GlobalMutex *GetGlobalMutex()
{
    GlobalMutex *mutex = g_Mutex.load();
    return mutex != nullptr ? mutex : AllocateGlobalMutexImpl(&g_Mutex);
}

GlobalMutex *GetGlobalEGLSyncObjectMutex()
{
    GlobalMutex *mutex = g_EGLSyncMutex.load();
    return mutex != nullptr ? mutex : AllocateGlobalMutexImpl(&g_EGLSyncMutex);
}
#endif
}  // anonymous namespace

// ScopedGlobalMutexLock implementation.
#if defined(ANGLE_ENABLE_GLOBAL_MUTEX_LOAD_TIME_ALLOCATE)
template <GlobalMutexChoice mutexChoice>
ScopedGlobalMutexLock<mutexChoice>::ScopedGlobalMutexLock()
{
    switch (mutexChoice)
    {
        case GlobalMutexChoice::EGL:
            g_MutexPtr->lock();
            break;
        case GlobalMutexChoice::Sync:
            g_EGLSyncMutexPtr->lock();
            break;
        default:
            UNREACHABLE();
            break;
    }
}

template <GlobalMutexChoice mutexChoice>
ScopedGlobalMutexLock<mutexChoice>::~ScopedGlobalMutexLock()
{
    switch (mutexChoice)
    {
        case GlobalMutexChoice::EGL:
            g_MutexPtr->unlock();
            break;
        case GlobalMutexChoice::Sync:
            g_EGLSyncMutexPtr->unlock();
            break;
        default:
            UNREACHABLE();
            break;
    }
}
#else
template <GlobalMutexChoice mutexChoice>
ScopedGlobalMutexLock<mutexChoice>::ScopedGlobalMutexLock()
{
    switch (mutexChoice)
    {
        case GlobalMutexChoice::EGL:
            mMutex = GetGlobalMutex();
            break;
        case GlobalMutexChoice::Sync:
            mMutex = GetGlobalEGLSyncObjectMutex();
            break;
        default:
            UNREACHABLE();
            break;
    }

    mMutex->lock();
}

template <GlobalMutexChoice mutexChoice>
ScopedGlobalMutexLock<mutexChoice>::~ScopedGlobalMutexLock()
{
    mMutex->unlock();
}
#endif

template class ScopedGlobalMutexLock<GlobalMutexChoice::EGL>;
template class ScopedGlobalMutexLock<GlobalMutexChoice::Sync>;
}  // namespace priv

// ScopedOptionalGlobalMutexLock implementation.
ScopedOptionalGlobalMutexLock::ScopedOptionalGlobalMutexLock(bool enabled)
{
    if (enabled)
    {
#if defined(ANGLE_ENABLE_GLOBAL_MUTEX_LOAD_TIME_ALLOCATE)
        mMutex = priv::g_MutexPtr;
#else
        mMutex = priv::GetGlobalMutex();
#endif
        mMutex->lock();
    }
    else
    {
        mMutex = nullptr;
    }
}

ScopedOptionalGlobalMutexLock::~ScopedOptionalGlobalMutexLock()
{
    if (mMutex != nullptr)
    {
        mMutex->unlock();
    }
}

// Global functions.
#if defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_STATIC)
#    if defined(ANGLE_ENABLE_GLOBAL_MUTEX_LOAD_TIME_ALLOCATE)
#        error "'angle_enable_global_mutex_load_time_allocate' is not supported in Windows DLL."
#    endif

void AllocateGlobalMutex()
{
    (void)priv::AllocateGlobalMutexImpl(&priv::g_Mutex);
    (void)priv::AllocateGlobalMutexImpl(&priv::g_EGLSyncMutex);
}

void DeallocateGlobalMutexImpl(std::atomic<priv::GlobalMutex *> *globalMutex)
{
    priv::GlobalMutex *mutex = globalMutex->exchange(nullptr);
    if (mutex != nullptr)
    {
        {
            // Wait for the mutex to become released by other threads before deleting.
            std::lock_guard<priv::GlobalMutex> lock(*mutex);
        }
        delete mutex;
    }
}

void DeallocateGlobalMutex()
{
    DeallocateGlobalMutexImpl(&priv::g_Mutex);
    DeallocateGlobalMutexImpl(&priv::g_EGLSyncMutex);
}
#endif

}  // namespace egl
