blob: f4879c89d1056855fa1481142783d524326ff15b [file]
/*
* Copyright 2010-2022 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 "std_support/Atomic.hpp"
#include "CompilerConstants.hpp"
#include "Exceptions.h"
#include "KAssert.h"
#include "Memory.h"
#include "Logging.hpp"
#include "ObjCExportInit.h"
#include "Porting.h"
#include "Runtime.h"
#include "RuntimePrivate.hpp"
#include "Worker.h"
#include "KString.h"
#include "CrashHandler.hpp"
#include <atomic>
#include <cstdint>
#include <cstdlib>
#include <thread>
using namespace kotlin;
using kotlin::internal::FILE_NOT_INITIALIZED;
using kotlin::internal::FILE_BEING_INITIALIZED;
using kotlin::internal::FILE_INITIALIZED;
using kotlin::internal::FILE_FAILED_TO_INITIALIZE;
namespace {
InitNode* initHeadNode = nullptr;
InitNode* initTailNode = nullptr;
enum class RuntimeStatus {
kUninitialized,
kRunning,
kDestroying,
};
struct RuntimeState {
MemoryState* memoryState;
Worker* worker;
RuntimeStatus status = RuntimeStatus::kUninitialized;
};
// Must be synchronized with IrToBitcode.kt
enum {
ALLOC_THREAD_LOCAL_GLOBALS = 0,
INIT_GLOBALS = 1,
INIT_THREAD_LOCAL_GLOBALS = 2,
};
void InitOrDeinitGlobalVariables(int initialize, MemoryState* memory) {
InitNode* currentNode = initHeadNode;
while (currentNode != nullptr) {
currentNode->init(initialize, memory);
currentNode = currentNode->next;
}
}
constexpr RuntimeState* kInvalidRuntime = nullptr;
THREAD_LOCAL_VARIABLE RuntimeState* runtimeState = kInvalidRuntime;
PERFORMANCE_INLINE inline bool isValidRuntime() {
return ::runtimeState != kInvalidRuntime;
}
std::atomic<int> aliveRuntimesCount = 0;
enum GlobalRuntimeStatus {
kGlobalRuntimeUninitialized = 0,
kGlobalRuntimeRunning,
kGlobalRuntimeShutdown,
};
std::atomic<GlobalRuntimeStatus> globalRuntimeStatus = kGlobalRuntimeUninitialized;
void Kotlin_deinitRuntimeCallback(void* argument);
NO_INLINE RuntimeState* initRuntime() {
SetKonanTerminateHandler();
initObjectPool();
RuntimeState* result = new RuntimeState();
if (!result) return kInvalidRuntime;
RuntimeCheck(!isValidRuntime(), "No active runtimes allowed");
::runtimeState = result;
// First update `aliveRuntimesCount` and then update `globalRuntimeStatus`, for synchronization with
// runtime shutdown, which does it the other way around.
++aliveRuntimesCount;
bool firstRuntime = initializeGlobalRuntimeIfNeeded();
result->memoryState = InitMemory();
// Switch thread state because worker and globals inits require the runnable state.
// This call may block if GC requested suspending threads.
ThreadStateGuard stateGuard(result->memoryState, kotlin::ThreadState::kRunnable);
result->worker = WorkerInit(result->memoryState);
InitOrDeinitGlobalVariables(ALLOC_THREAD_LOCAL_GLOBALS, result->memoryState);
CommitTLSStorage(result->memoryState);
// Keep global variables in state as well.
if (firstRuntime) {
InitOrDeinitGlobalVariables(INIT_GLOBALS, result->memoryState);
}
InitOrDeinitGlobalVariables(INIT_THREAD_LOCAL_GLOBALS, result->memoryState);
RuntimeAssert(result->status == RuntimeStatus::kUninitialized, "Runtime must still be in the uninitialized state");
result->status = RuntimeStatus::kRunning;
// Register runtime deinit function at thread cleanup.
konan::onThreadExit(Kotlin_deinitRuntimeCallback, runtimeState);
return result;
}
void deinitRuntime(RuntimeState* state) {
AssertThreadState(state->memoryState, kotlin::ThreadState::kRunnable);
RuntimeAssert(state->status == RuntimeStatus::kRunning, "Runtime must be in the running state");
state->status = RuntimeStatus::kDestroying;
// This may be called after TLS is zeroed out, so ::runtimeState and ::memoryState in Memory cannot be trusted.
// TODO: This may in fact reallocate TLS without guarantees that it'll be deallocated again.
::runtimeState = state;
--aliveRuntimesCount;
ClearTLS(state->memoryState);
// Do not use ThreadStateGuard because memoryState will be destroyed during DeinitMemory.
kotlin::SwitchThreadState(state->memoryState, kotlin::ThreadState::kNative);
auto workerId = GetWorkerId(state->worker);
WorkerDeinit(state->worker);
DeinitMemory(state->memoryState);
delete state;
WorkerDestroyThreadDataIfNeeded(workerId);
::runtimeState = kInvalidRuntime;
}
void Kotlin_deinitRuntimeCallback(void* argument) {
auto* state = reinterpret_cast<RuntimeState*>(argument);
// This callback may be called from any state, make sure it runs in the runnable state.
kotlin::SwitchThreadState(state->memoryState, kotlin::ThreadState::kRunnable, /* reentrant = */ true);
deinitRuntime(state);
}
} // namespace
bool kotlin::initializeGlobalRuntimeIfNeeded() noexcept {
auto lastStatus = std_support::atomic_compare_swap_strong(globalRuntimeStatus, kGlobalRuntimeUninitialized, kGlobalRuntimeRunning);
if (lastStatus != kGlobalRuntimeUninitialized)
return false;
konan::consoleInit();
logging::OnRuntimeInit();
crashHandlerInit();
initGlobalMemory();
#if KONAN_OBJC_INTEROP
Kotlin_ObjCExport_initialize();
#endif
return true;
}
const char* kotlin::programName = nullptr;
extern "C" {
RUNTIME_NOTHROW void AppendToInitializersTail(InitNode *next) {
// TODO: use RuntimeState.
if (initHeadNode == nullptr) {
initHeadNode = next;
} else {
initTailNode->next = next;
}
initTailNode = next;
}
PERFORMANCE_INLINE RUNTIME_NOTHROW void Kotlin_initRuntimeIfNeeded() {
if (!isValidRuntime()) {
initRuntime();
}
}
void deinitRuntimeIfNeeded() {
if (isValidRuntime()) {
deinitRuntime(::runtimeState);
}
}
// TODO: Consider exporting it to interop API.
void Kotlin_shutdownRuntime() {
auto* runtime = ::runtimeState;
RuntimeAssert(runtime != kInvalidRuntime, "Current thread must have Kotlin runtime initialized on it");
auto lastStatus = std_support::atomic_compare_swap_strong(globalRuntimeStatus, kGlobalRuntimeRunning, kGlobalRuntimeShutdown);
RuntimeAssert(lastStatus == kGlobalRuntimeRunning, "Invalid runtime status for shutdown");
// The main thread is not doing anything Kotlin anymore, but will stick around to cleanup C++ globals and the like.
// Mark the thread native, and don't make the GC thread wait on it.
kotlin::SwitchThreadState(runtime->memoryState, kotlin::ThreadState::kNative);
return;
}
KInt Konan_Platform_canAccessUnaligned() {
#if KONAN_NO_UNALIGNED_ACCESS
return 0;
#else
return 1;
#endif
}
KInt Konan_Platform_isLittleEndian() {
#ifdef __BIG_ENDIAN__
return 0;
#else
return 1;
#endif
}
KInt Konan_Platform_getOsFamily() {
#if KONAN_MACOSX
return 1;
#elif KONAN_IOS
return 2;
#elif KONAN_LINUX
return 3;
#elif KONAN_WINDOWS
return 4;
#elif KONAN_ANDROID
return 5;
#elif KONAN_TVOS
return 7;
#elif KONAN_WATCHOS
return 8;
#else
#warning "Unknown platform"
return 0;
#endif
}
KInt Konan_Platform_getCpuArchitecture() {
#if KONAN_ARM32
return 1;
#elif KONAN_ARM64
return 2;
#elif KONAN_X86
return 3;
#elif KONAN_X64
return 4;
#else
#warning "Unknown CPU"
return 0;
#endif
}
KBoolean Konan_Platform_isDebugBinary() {
return kotlin::compiler::shouldContainDebugInfo();
}
KInt Konan_Platform_getAllocator() {
if (!compiler::pagedAllocator()) {
// Allocator.PER_OBJECT
return 1;
}
// Allocator.PAGED
return 0;
}
OBJ_GETTER0(Konan_Platform_getProgramName) {
if (kotlin::programName == nullptr) {
// null in case Platform.getProgramName is called from within a library and the main function of the binary is not built with Kotlin
RETURN_OBJ(nullptr)
} else {
RETURN_RESULT_OF(CreateStringFromCString, kotlin::programName)
}
}
KInt Konan_Platform_getAvailableProcessors() {
auto res = std::thread::hardware_concurrency();
// C++ standard says that if this function can return 0 if value is not "well defined or not computable"
// In current libstdc++ implementation, seems it can happen only on unsupported targets.
// We consider such systems as single-threaded
if (res == 0) {
res = 1;
}
// Probably it can't happen, but let's not allow overflows
if (res > std::numeric_limits<int>::max()) {
res = std::numeric_limits<int>::max();
}
return static_cast<KInt>(res);
}
OBJ_GETTER0(Konan_Platform_getAvailableProcessorsEnv) {
char* env = getenv("KOTLIN_NATIVE_AVAILABLE_PROCESSORS");
if (env == nullptr) {
RETURN_OBJ(nullptr)
}
RETURN_RESULT_OF(CreateStringFromCString, env)
}
KBoolean Kotlin_Debugging_isThreadStateRunnable() {
return kotlin::GetThreadState() == kotlin::ThreadState::kRunnable;
}
KBoolean Kotlin_Debugging_isThreadStateNative() {
return kotlin::GetThreadState() == kotlin::ThreadState::kNative;
}
KBoolean Kotlin_Debugging_isPermanent(KRef obj) {
return obj->permanent();
}
RUNTIME_NOTHROW KBoolean Kotlin_Debugging_isStack(KRef obj) {
return obj->stack();
}
static void CallInitGlobalAwaitInitialized(uintptr_t* state) {
uintptr_t localState;
// Switch to the native state to avoid dead-locks.
{
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
do {
localState = std_support::atomic_ref{*state}.load(std::memory_order_acquire);
} while (localState != FILE_INITIALIZED && localState != FILE_FAILED_TO_INITIALIZE);
}
if (localState == FILE_FAILED_TO_INITIALIZE) ThrowFileFailedToInitializeException(nullptr);
}
NO_INLINE void CallInitGlobalPossiblyLock(uintptr_t* state, void (*init)()) {
uintptr_t localState = std_support::atomic_ref{*state}.load(std::memory_order_acquire);
if (localState == FILE_INITIALIZED) return;
if (localState == FILE_FAILED_TO_INITIALIZE)
ThrowFileFailedToInitializeException(nullptr);
uintptr_t threadId = konan::currentThreadId();
if ((localState & 3) == FILE_BEING_INITIALIZED) {
if ((localState & ~3) != (threadId << 2)) {
CallInitGlobalAwaitInitialized(state);
}
return;
}
if (std_support::atomic_compare_swap_strong(std_support::atomic_ref{*state}, FILE_NOT_INITIALIZED, FILE_BEING_INITIALIZED | (threadId << 2)) == FILE_NOT_INITIALIZED) {
// actual initialization
try {
CurrentFrameGuard guard;
init();
} catch (ExceptionObjHolder& e) {
ObjHolder holder;
auto *exception = Kotlin_getExceptionObject(&e, holder.slot());
std_support::atomic_ref{*state}.store(FILE_FAILED_TO_INITIALIZE, std::memory_order_release);
ThrowFileFailedToInitializeException(exception);
}
std_support::atomic_ref{*state}.store(FILE_INITIALIZED, std::memory_order_release);
} else {
CallInitGlobalAwaitInitialized(state);
}
}
void CallInitThreadLocal(uintptr_t volatile* globalState, uintptr_t* localState, void (*init)()) {
if (*localState == FILE_FAILED_TO_INITIALIZE || (globalState != nullptr && *globalState == FILE_FAILED_TO_INITIALIZE))
ThrowFileFailedToInitializeException(nullptr);
*localState = FILE_INITIALIZED;
try {
CurrentFrameGuard guard;
init();
} catch(ExceptionObjHolder& e) {
ObjHolder holder;
auto *exception = Kotlin_getExceptionObject(&e, holder.slot());
*localState = FILE_FAILED_TO_INITIALIZE;
ThrowFileFailedToInitializeException(exception);
}
}
} // extern "C"