blob: 5d346c1a13f695574222e5be730a82e55d216067 [file]
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "GlobalData.hpp"
#include <condition_variable>
#include <mutex>
#include "CompilerConstants.hpp"
#include "Porting.h"
#if KONAN_WINDOWS
#include "concurrent/Mutex.hpp"
#endif
using namespace kotlin;
namespace {
enum class InitState {
kUninitialized,
kInitializing,
kInitialized,
};
const char* initStateToString(InitState state) noexcept {
switch (state) {
case InitState::kUninitialized: return "uninitialized";
case InitState::kInitializing: return "initializing";
case InitState::kInitialized: return "initialized";
}
}
std::atomic<InitState> globalDataInitState = InitState::kUninitialized;
std::atomic<std::thread::id> globalDataInitializingThread{};
ManuallyScoped<mm::GlobalData> globalDataInstance{};
#if KONAN_WINDOWS
// On winpthreads, there's a weird bug if this is a regular `std::mutex`:
// even though `constructGlobalDataInstance()` has already started (and so,
// has already successfully tried locking this mutex), `waitGlobalDataInitialized`
// may crash trying to lock it too.
SpinLock globalDataInitMutex;
std::condition_variable_any globalDataInitCV;
#else
std::mutex globalDataInitMutex;
std::condition_variable globalDataInitCV;
#endif
void constructGlobalDataInstance() noexcept {
std::unique_lock guard{globalDataInitMutex};
auto initialState = InitState::kUninitialized;
globalDataInitState.compare_exchange_strong(initialState, InitState::kInitializing, std::memory_order_acq_rel);
RuntimeAssert(initialState == InitState::kUninitialized, "Expected state %s, but was %s", initStateToString(InitState::kUninitialized), initStateToString(initialState));
globalDataInitializingThread.store(std::this_thread::get_id(), std::memory_order_relaxed);
globalDataInstance.construct();
auto initializingState = InitState::kInitializing;
globalDataInitState.compare_exchange_strong(initializingState, InitState::kInitialized, std::memory_order_acq_rel);
RuntimeAssert(initializingState == InitState::kInitializing, "Expected state %s, but was %s", initStateToString(InitState::kInitializing), initStateToString(initializingState));
guard.unlock();
globalDataInitCV.notify_all();
}
[[maybe_unused]] struct GlobalDataEagerInit {
GlobalDataEagerInit() noexcept {
if (!compiler::globalDataLazyInit()) {
constructGlobalDataInstance();
}
}
} globalDataEagerInit;
}
// static
mm::GlobalData& mm::GlobalData::Instance() noexcept {
if (compiler::runtimeAssertsEnabled()) {
auto s = globalDataInitState.load(std::memory_order_relaxed);
auto initializingThread = globalDataInitializingThread.load(std::memory_order_relaxed);
RuntimeAssert(s == InitState::kInitialized || initializingThread == std::this_thread::get_id(), "Expected state %s, but was %s.", initStateToString(InitState::kInitialized), initStateToString(s));
}
return *globalDataInstance;
}
// static
void mm::GlobalData::init() noexcept {
if (compiler::globalDataLazyInit()) {
constructGlobalDataInstance();
}
}
void mm::waitGlobalDataInitialized() noexcept {
if (globalDataInitState.load(std::memory_order_acquire) == InitState::kInitialized) {
return;
}
if (compiler::runtimeAssertsEnabled()) {
auto initializingThread = globalDataInitializingThread.load(std::memory_order_relaxed);
RuntimeAssert(initializingThread != std::this_thread::get_id(), "A thread that initialized global data cannot be waiting for its initialization");
}
std::unique_lock guard{globalDataInitMutex};
globalDataInitCV.wait(guard, []() noexcept {
return globalDataInitState.load(std::memory_order_relaxed) == InitState::kInitialized;
});
}
mm::GlobalData::GlobalData() noexcept = default;
// static