diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt index c0182e46008..dc9e06c239f 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt @@ -253,6 +253,14 @@ class K2Native : CLICompiler() { put(DISABLE_FAKE_OVERRIDE_VALIDATOR, arguments.disableFakeOverrideValidator) putIfNotNull(PRE_LINK_CACHES, parsePreLinkCachesValue(configuration, arguments.preLinkCaches)) putIfNotNull(OVERRIDE_KONAN_PROPERTIES, parseOverrideKonanProperties(arguments, configuration)) + put(DESTROY_RUNTIME_MODE, when (arguments.destroyRuntimeMode) { + "legacy" -> DestroyRuntimeMode.LEGACY + "on-shutdown" -> DestroyRuntimeMode.ON_SHUTDOWN + else -> { + configuration.report(ERROR, "Unsupported destroy runtime mode ${arguments.destroyRuntimeMode}") + DestroyRuntimeMode.ON_SHUTDOWN + } + }) } } } diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt index 0b596f5269b..b71cdd7ab83 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt @@ -288,6 +288,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() { ) var overrideKonanProperties: Array? = null + @Argument(value="-Xdestroy-runtime-mode", valueDescription = "", description = "When to destroy runtime. 'legacy' and 'on-shutdown' are currently supported. NOTE: 'legacy' mode is deprecated and will be removed.") + var destroyRuntimeMode: String? = "on-shutdown" + override fun configureAnalysisFlags(collector: MessageCollector): MutableMap, Any> = super.configureAnalysisFlags(collector).also { val useExperimental = it[AnalysisFlags.useExperimental] as List<*> diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DestroyRuntimeMode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DestroyRuntimeMode.kt new file mode 100644 index 00000000000..ad4822e5f11 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DestroyRuntimeMode.kt @@ -0,0 +1,11 @@ +/* + * 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. + */ +package org.jetbrains.kotlin.backend.konan + +// Must match DestroyRuntimeMode in Runtime.h +enum class DestroyRuntimeMode(val value: Int) { + LEGACY(0), + ON_SHUTDOWN(1) +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index ccfc2ccc813..612e3a4526e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -45,6 +45,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration ?: target.family.isAppleFamily // Default is true for Apple targets. val memoryModel: MemoryModel get() = configuration.get(KonanConfigKeys.MEMORY_MODEL)!! + val destroyRuntimeMode: DestroyRuntimeMode get() = configuration.get(KonanConfigKeys.DESTROY_RUNTIME_MODE)!! val needCompilerVerification: Boolean get() = configuration.get(KonanConfigKeys.VERIFY_COMPILER) ?: @@ -128,6 +129,10 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration configuration.report(CompilerMessageSeverity.STRONG_WARNING, "Experimental memory model requires threads, which are not supported on target ${target.name}. Used strict memory model.") MemoryModel.STRICT + } else if (destroyRuntimeMode != DestroyRuntimeMode.ON_SHUTDOWN) { + configuration.report(CompilerMessageSeverity.STRONG_WARNING, + "Experimental memory model requires destroy runtime mode 'on-shutdown'. Used strict memory model.") + MemoryModel.STRICT } else { MemoryModel.EXPERIMENTAL } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt index 8a25ab08e25..02f2658cbe0 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt @@ -146,6 +146,8 @@ class KonanConfigKeys { = CompilerConfigurationKey.create("perform compiler caches pre-link") val OVERRIDE_KONAN_PROPERTIES: CompilerConfigurationKey> = CompilerConfigurationKey.create("override konan.properties values") + val DESTROY_RUNTIME_MODE: CompilerConfigurationKey + = CompilerConfigurationKey.create("when to destroy runtime") } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 0832299a239..156bc5d4157 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -362,7 +362,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: MaptlsMap = konanConstructInstance(); memoryState->foreignRefManager = ForeignRefManager::create(); bool firstMemoryState = atomicAdd(&aliveMemoryStatesCount, 1) == 1; - if (firstMemoryState) { + switch (Kotlin_destroyRuntimeMode) { + case DESTROY_RUNTIME_LEGACY: + firstRuntime = firstMemoryState; + break; + case DESTROY_RUNTIME_ON_SHUTDOWN: + // Nothing to do. + break; + } + if (firstRuntime) { #if USE_CYCLIC_GC cyclicInit(); #endif // USE_CYCLIC_GC memoryState->isMainThread = true; } + if (!firstRuntime && firstMemoryState) { + memoryState->isMainThread = true; + // This thread is now the main thread. And there was a previous main thread, because this is not the first runtime. + // Make sure this thread sees all the updates to Kotlin globals from the previous main thread. + synchronize(); + } return memoryState; } -void deinitMemory(MemoryState* memoryState) { +void deinitMemory(MemoryState* memoryState, bool destroyRuntime) { static int pendingDeinit = 0; atomicAdd(&pendingDeinit, 1); #if USE_GC bool lastMemoryState = atomicAdd(&aliveMemoryStatesCount, -1) == 0; - bool checkLeaks = Kotlin_memoryLeakCheckerEnabled() && lastMemoryState; - if (lastMemoryState) { + switch (Kotlin_destroyRuntimeMode) { + case DESTROY_RUNTIME_LEGACY: + destroyRuntime = lastMemoryState; + break; + case DESTROY_RUNTIME_ON_SHUTDOWN: + // Nothing to do. + break; + } + bool checkLeaks = Kotlin_memoryLeakCheckerEnabled() && destroyRuntime; + if (destroyRuntime) { garbageCollect(memoryState, true); #if USE_CYCLIC_GC // If there are other pending deinits (rare situation) - just skip the leak checker. @@ -2032,6 +2054,10 @@ void deinitMemory(MemoryState* memoryState) { cyclicDeinit(g_hasCyclicCollector); #endif // USE_CYCLIC_GC } + if (!destroyRuntime && memoryState->isMainThread) { + // If we are not destroying the runtime but we were the main thread, publish all changes to Kotlin globals. + synchronize(); + } // Actual GC only implemented in strict memory model at the moment. do { GC_LOG("Calling garbageCollect from DeinitMemory()\n") @@ -3231,12 +3257,12 @@ void AdoptReferenceFromSharedVariable(ObjHeader* object) { } // Public memory interface. -MemoryState* InitMemory() { - return initMemory(); +MemoryState* InitMemory(bool firstRuntime) { + return initMemory(firstRuntime); } -void DeinitMemory(MemoryState* memoryState) { - deinitMemory(memoryState); +void DeinitMemory(MemoryState* memoryState, bool destroyRuntime) { + deinitMemory(memoryState, destroyRuntime); } void RestoreMemory(MemoryState* memoryState) { diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index 46b45c36b05..be5ab005796 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -122,8 +122,8 @@ extern "C" { struct MemoryState; -MemoryState* InitMemory(); -void DeinitMemory(MemoryState*); +MemoryState* InitMemory(bool firstRuntime); +void DeinitMemory(MemoryState*, bool destroyRuntime); void RestoreMemory(MemoryState*); // diff --git a/runtime/src/main/cpp/Runtime.cpp b/runtime/src/main/cpp/Runtime.cpp index 4cd392fe246..518bc493153 100644 --- a/runtime/src/main/cpp/Runtime.cpp +++ b/runtime/src/main/cpp/Runtime.cpp @@ -119,9 +119,17 @@ RuntimeState* initRuntime() { if (!result) return kInvalidRuntime; RuntimeCheck(!isValidRuntime(), "No active runtimes allowed"); ::runtimeState = result; - result->memoryState = InitMemory(); + bool firstRuntime = lastStatus == kGlobalRuntimeUninitialized; + result->memoryState = InitMemory(firstRuntime); // The argument will be ignored for legacy DestroyRuntimeMode result->worker = WorkerInit(true); - bool firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1; + switch (Kotlin_destroyRuntimeMode) { + case DESTROY_RUNTIME_LEGACY: + firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1; + break; + case DESTROY_RUNTIME_ON_SHUTDOWN: + atomicAdd(&aliveRuntimesCount, 1) + break; + } // Keep global variables in state as well. if (firstRuntime) { konan::consoleInit(); @@ -136,25 +144,33 @@ RuntimeState* initRuntime() { return result; } -void deinitRuntime(RuntimeState* state) { +void deinitRuntime(RuntimeState* state, bool destroyRuntime) { 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 ::memoryState in Memory cannot be trusted. RestoreMemory(state->memoryState); bool lastRuntime = atomicAdd(&aliveRuntimesCount, -1) == 0; + switch (Kotlin_destroyRuntimeMode) { + case DESTROY_RUNTIME_LEGACY: + destroyRuntime = lastRuntime; + break; + case DESTROY_RUNTIME_ON_SHUTDOWN: + // Nothing to do. + break; + } InitOrDeinitGlobalVariables(DEINIT_THREAD_LOCAL_GLOBALS, state->memoryState); - if (lastRuntime) + if (destroyRuntime) InitOrDeinitGlobalVariables(DEINIT_GLOBALS, state->memoryState); auto workerId = GetWorkerId(state->worker); WorkerDeinit(state->worker); - DeinitMemory(state->memoryState); + DeinitMemory(state->memoryState, destroyRuntime); konanDestructInstance(state); WorkerDestroyThreadDataIfNeeded(workerId); } void Kotlin_deinitRuntimeCallback(void* argument) { auto* state = reinterpret_cast(argument); - deinitRuntime(state); + deinitRuntime(state, false); } } // namespace @@ -181,7 +197,7 @@ void Kotlin_initRuntimeIfNeeded() { void Kotlin_deinitRuntimeIfNeeded() { if (isValidRuntime()) { - deinitRuntime(::runtimeState); + deinitRuntime(::runtimeState, false); ::runtimeState = kInvalidRuntime; } } @@ -239,7 +255,7 @@ void Kotlin_shutdownRuntime() { } } - deinitRuntime(runtime); + deinitRuntime(runtime, true); ::runtimeState = kInvalidRuntime; } diff --git a/runtime/src/main/cpp/Runtime.h b/runtime/src/main/cpp/Runtime.h index 2b4e6d152ed..ddc43e5aa9f 100644 --- a/runtime/src/main/cpp/Runtime.h +++ b/runtime/src/main/cpp/Runtime.h @@ -25,6 +25,14 @@ struct InitNode; extern "C" { #endif +// Must match DestroyRuntimeMode in DestroyRuntimeMode.kt +enum DestroyRuntimeMode { + DESTROY_RUNTIME_LEGACY = 0, + DESTROY_RUNTIME_ON_SHUTDOWN = 1, +}; + +extern const DestroyRuntimeMode Kotlin_destroyRuntimeMode; + void Kotlin_initRuntimeIfNeeded(); void Kotlin_deinitRuntimeIfNeeded(); diff --git a/runtime/src/mm/cpp/Stubs.cpp b/runtime/src/mm/cpp/Stubs.cpp index cfc6582cd1d..c0b6f7b2019 100644 --- a/runtime/src/mm/cpp/Stubs.cpp +++ b/runtime/src/mm/cpp/Stubs.cpp @@ -47,11 +47,11 @@ static void destroyMetaObject(TypeInfo** location) { extern "C" { -MemoryState* InitMemory() { +MemoryState* InitMemory(bool firstRuntime) { RuntimeCheck(false, "Unimplemented"); } -void DeinitMemory(MemoryState*) { +void DeinitMemory(MemoryState*, bool destroyRuntime) { RuntimeCheck(false, "Unimplemented"); }