Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Add a switch to destroy runtime only on shutdown #4482

Merged
merged 15 commits into from
Nov 13, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,14 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
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
}
})
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
)
var overrideKonanProperties: Array<String>? = null

@Argument(value="-Xdestroy-runtime-mode", valueDescription = "<mode>", 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<AnalysisFlag<*>, Any> =
super.configureAnalysisFlags(collector).also {
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) ?:
Expand Down Expand Up @@ -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.LEGACY) {
configuration.report(CompilerMessageSeverity.STRONG_WARNING,
"Experimental memory model is incompatible with 'legacy' destroy runtime mode. Used strict memory model.")
MemoryModel.STRICT
} else {
MemoryModel.EXPERIMENTAL
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create("perform compiler caches pre-link")
val OVERRIDE_KONAN_PROPERTIES: CompilerConfigurationKey<Map<String, String>>
= CompilerConfigurationKey.create("override konan.properties values")
val DESTROY_RUNTIME_MODE: CompilerConfigurationKey<DestroyRuntimeMode>
= CompilerConfigurationKey.create("when to destroy runtime")
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE

context.coverage.writeRegionInfo()
appendDebugSelector()
overrideRuntimeGlobals()
appendLlvmUsed("llvm.used", context.llvm.usedFunctions + context.llvm.usedGlobals)
appendLlvmUsed("llvm.compiler.used", context.llvm.compilerUsedGlobals)
if (context.isNativeLibrary) {
Expand Down Expand Up @@ -2380,6 +2381,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
LLVMSetSection(llvmUsedGlobal.llvmGlobal, "llvm.metadata")
}

// TODO: Consider migrating `KonanNeedDebugInfo` to the `overrideRuntimeGlobal` mechanism from below.
private fun appendDebugSelector() {
if (!context.producedLlvmModuleContainsStdlib) return
val llvmDebugSelector =
Expand All @@ -2389,6 +2391,39 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
llvmDebugSelector.setLinkage(LLVMLinkage.LLVMExternalLinkage)
}

private fun overrideRuntimeGlobal(name: String, value: ConstValue) {
// TODO: A similar mechanism is used in `ObjCExportCodeGenerator`. Consider merging them.
if (context.llvmModuleSpecification.importsKotlinDeclarationsFromOtherSharedLibraries()) {
// When some dynamic caches are used, we consider that stdlib is in the dynamic cache as well.
// Runtime is linked into stdlib module only, so import runtime global from it.
val global = codegen.importGlobal(name, value.llvmType, context.standardLlvmSymbolsOrigin)
val initializer = generateFunction(codegen, functionType(voidType, false), "") {
store(value.llvm, global)
ret(null)
}

LLVMSetLinkage(initializer, LLVMLinkage.LLVMPrivateLinkage)

context.llvm.otherStaticInitializers += initializer
} else {
context.llvmImports.add(context.standardLlvmSymbolsOrigin)
// Define a strong runtime global. It'll overrule a weak global defined in a statically linked runtime.
val global = context.llvm.staticData.placeGlobal(name, value, true)

if (context.llvmModuleSpecification.importsKotlinDeclarationsFromOtherObjectFiles()) {
context.llvm.usedGlobals += global.llvmGlobal
LLVMSetVisibility(global.llvmGlobal, LLVMVisibility.LLVMHiddenVisibility)
}
}
}

private fun overrideRuntimeGlobals() {
if (!context.config.produce.isFinalBinary)
return

overrideRuntimeGlobal("Kotlin_destroyRuntimeMode", Int32(context.config.destroyRuntimeMode.value))
}

//-------------------------------------------------------------------------//
// Create type { i32, void ()*, i8* }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ private fun ObjCExportCodeGenerator.replaceExternalWeakOrCommonGlobal(
value: ConstValue,
origin: CompiledKlibModuleOrigin
) {
// TODO: A similar mechanism is used in `IrToBitcode.overrideRuntimeGlobal`. Consider merging them.
if (context.llvmModuleSpecification.importsKotlinDeclarationsFromOtherSharedLibraries()) {
val global = codegen.importGlobal(name, value.llvmType, origin)
externalGlobalInitializers[global] = value
Expand Down
12 changes: 11 additions & 1 deletion backend.native/tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4361,9 +4361,19 @@ dynamicTest("interop_cleaners_leak") {
flags = ['-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
}

dynamicTest("interop_migrating_main_thread_legacy") {
disabled = (project.target.name != project.hostName)
source = "interop/migrating_main_thread/lib.kt"
flags = ['-Xdestroy-runtime-mode=legacy']
clangFlags = ['-DIS_LEGACY']
cSource = "$projectDir/interop/migrating_main_thread/main.cpp"
clangTool = "clang++"
}

dynamicTest("interop_migrating_main_thread") {
disabled = (project.target.name != project.hostName)
source = "interop/migrating_main_thread/lib.kt"
flags = ['-Xdestroy-runtime-mode=on-shutdown']
cSource = "$projectDir/interop/migrating_main_thread/main.cpp"
clangTool = "clang++"
}
Expand All @@ -4374,7 +4384,7 @@ dynamicTest("interop_memory_leaks") {
source = "interop/memory_leaks/lib.kt"
cSource = "$projectDir/interop/memory_leaks/main.cpp"
clangTool = "clang++"
flags = ['-g']
flags = ['-g', '-Xdestroy-runtime-mode=legacy'] // Runtime cannot be destroyed with interop with on-shutdown.
expectedExitStatusChecker = { it != 0 }
outputChecker = { s -> s.contains("Memory leaks detected, 1 objects leaked!") }
}
Expand Down
8 changes: 7 additions & 1 deletion backend.native/tests/interop/migrating_main_thread/lib.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ fun writeToA(i: Int) {
globalA.i = i
}

fun readFromA() = globalA.i
fun tryReadFromA(default: Int): Int {
return try {
globalA.i
} catch (e: IncorrectDereferenceException) {
default
}
}
17 changes: 13 additions & 4 deletions backend.native/tests/interop/migrating_main_thread/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,26 @@
#include <cassert>
#include <thread>

constexpr int kInitialValue = 0;
constexpr int kNewValue = 1;
constexpr int kErrorValue = 2;

int main() {
std::thread main1([]() {
assert(testlib_symbols()->kotlin.root.readFromA() == 0);
testlib_symbols()->kotlin.root.writeToA(1);
assert(testlib_symbols()->kotlin.root.readFromA() == 1);
assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kInitialValue);
testlib_symbols()->kotlin.root.writeToA(kNewValue);
assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kNewValue);
});
main1.join();

std::thread main2([]() {
#if defined(IS_LEGACY)
// Globals were reinitialized.
assert(testlib_symbols()->kotlin.root.readFromA() == 0);
assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kInitialValue);
#else
// Globals are not accessible.
assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kErrorValue);
#endif
});
main2.join();

Expand Down
2 changes: 2 additions & 0 deletions backend.native/tests/interop/objc/smoke.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import kotlin.native.ref.*
import kotlin.test.*

fun main(args: Array<String>) {
// Test relies on full deinitialization at shutdown.
kotlin.native.internal.Debugging.forceCheckedShutdown = true
autoreleasepool {
run()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,12 @@ open class KonanDynamicTest : KonanStandaloneTest() {
@Input
lateinit var cSource: String

@Input
var clangTool = "clang"

@Input
var clangFlags: List<String> = listOf()

// Replace testlib_api.h and all occurrences of the testlib with the actual name of the test
private fun processCSource(): String {
val sourceFile = File(cSource)
Expand Down Expand Up @@ -453,7 +457,7 @@ open class KonanDynamicTest : KonanStandaloneTest() {
"-c",
"-o", "$executable.o",
"-I", artifactsDir
)
) + clangFlags
it.standardOutput = log
it.errorOutput = log
it.isIgnoreExitValue = true
Expand Down Expand Up @@ -485,4 +489,4 @@ open class KonanDynamicTest : KonanStandaloneTest() {
it.execute()
}
}
}
}
40 changes: 28 additions & 12 deletions runtime/src/legacymm/cpp/Memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ class ForeignRefManager {
if (atomicGet(&aliveMemoryStatesCount) == 0)
return;

memoryState = InitMemory(); // Required by ReleaseHeapRef.
memoryState = InitMemory(false); // Required by ReleaseHeapRef.
}

processEnqueuedReleaseRefsWith([](ObjHeader* obj) {
Expand All @@ -585,7 +585,7 @@ class ForeignRefManager {

if (hadNoStateInitialized) {
// Discard the memory state.
DeinitMemory(memoryState);
DeinitMemory(memoryState, false);
}
}
}
Expand Down Expand Up @@ -1978,7 +1978,7 @@ void deinitForeignRef(ObjHeader* object, ForeignRefManager* manager) {
}
}

MemoryState* initMemory() {
MemoryState* initMemory(bool firstRuntime) {
RuntimeAssert(offsetof(ArrayHeader, typeInfoOrMeta_)
==
offsetof(ObjHeader, typeInfoOrMeta_),
Expand All @@ -2005,7 +2005,15 @@ MemoryState* initMemory() {
memoryState->tlsMap = konanConstructInstance<KThreadLocalStorageMap>();
memoryState->foreignRefManager = ForeignRefManager::create();
bool firstMemoryState = atomicAdd(&aliveMemoryStatesCount, 1) == 1;
if (firstMemoryState) {
switch (Kotlin_getDestroyRuntimeMode()) {
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
Expand All @@ -2014,13 +2022,21 @@ MemoryState* initMemory() {
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_getDestroyRuntimeMode()) {
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.
Expand Down Expand Up @@ -2051,7 +2067,7 @@ void deinitMemory(MemoryState* memoryState) {
atomicAdd(&pendingDeinit, -1);

#if TRACE_MEMORY
if (IsStrictMemoryModel && lastMemoryState && allocCount > 0) {
if (IsStrictMemoryModel && destroyRuntime && allocCount > 0) {
MEMORY_LOG("*** Memory leaks, leaked %d containers ***\n", allocCount);
dumpReachable("", memoryState->containers);
}
Expand Down Expand Up @@ -3231,12 +3247,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) {
Expand Down
4 changes: 2 additions & 2 deletions runtime/src/main/cpp/Memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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*);

//
Expand Down
Loading