Ensure deinitialization of singletons for native library generation (#4422)

This commit is contained in:
Alexander Shabalin
2020-10-10 03:04:55 +03:00
committed by GitHub
parent 77949abcdc
commit 86ae02eb25
7 changed files with 142 additions and 25 deletions
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.hash.GlobalHash
import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.konan.CompiledKlibModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.CurrentKlibModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
@@ -638,4 +639,4 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val llvmVector128 = vector128Type
}
class IrStaticInitializer(val file: IrFile, val initializer: LLVMValueRef)
class IrStaticInitializer(val module: ModuleDescriptor, val initializer: LLVMValueRef)
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation
import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
@@ -322,6 +323,23 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
context.cAdapterGenerator.generateBindings(codegen)
}
private fun runAndProcessInitializers(module: ModuleDescriptor, f: () -> Unit) {
// TODO: collect those two in one place.
context.llvm.fileInitializers.clear()
context.llvm.fileUsesThreadLocalObjects = false
context.llvm.globalSharedObjects.clear()
f()
if (context.llvm.fileInitializers.isEmpty() && !context.llvm.fileUsesThreadLocalObjects && context.llvm.globalSharedObjects.isEmpty()) {
return
}
// Create global initialization records.
val initNode = createInitNode(createInitBody())
context.llvm.irStaticInitializers.add(IrStaticInitializer(module, createInitCtor(initNode)))
}
//-------------------------------------------------------------------------//
override fun visitElement(element: IrElement) {
@@ -337,19 +355,22 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
initializeCachedBoxes(context)
declaration.acceptChildrenVoid(this)
// Note: it is here because it also generates some bitcode.
context.objCExport.generate(codegen)
runAndProcessInitializers(declaration.descriptor) {
// Note: it is here because it also generates some bitcode.
context.objCExport.generate(codegen)
codegen.objCDataGenerator?.finishModule()
codegen.objCDataGenerator?.finishModule()
context.coverage.writeRegionInfo()
appendDebugSelector()
appendLlvmUsed("llvm.used", context.llvm.usedFunctions + context.llvm.usedGlobals)
appendLlvmUsed("llvm.compiler.used", context.llvm.compilerUsedGlobals)
appendStaticInitializers()
if (context.isNativeLibrary) {
appendCAdapters()
context.coverage.writeRegionInfo()
appendDebugSelector()
appendLlvmUsed("llvm.used", context.llvm.usedFunctions + context.llvm.usedGlobals)
appendLlvmUsed("llvm.compiler.used", context.llvm.compilerUsedGlobals)
if (context.isNativeLibrary) {
appendCAdapters()
}
}
appendStaticInitializers()
}
//-------------------------------------------------------------------------//
@@ -487,21 +508,11 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
//-------------------------------------------------------------------------//
override fun visitFile(declaration: IrFile) {
// TODO: collect those two in one place.
context.llvm.fileInitializers.clear()
context.llvm.fileUsesThreadLocalObjects = false
context.llvm.globalSharedObjects.clear()
@Suppress("UNCHECKED_CAST")
using(FileScope(declaration)) {
declaration.acceptChildrenVoid(this)
if (context.llvm.fileInitializers.isEmpty() && !context.llvm.fileUsesThreadLocalObjects && context.llvm.globalSharedObjects.isEmpty())
return
// Create global initialization records.
val initNode = createInitNode(createInitBody())
context.llvm.irStaticInitializers.add(IrStaticInitializer(declaration, createInitCtor(initNode)))
runAndProcessInitializers(declaration.packageFragmentDescriptor.module) {
declaration.acceptChildrenVoid(this)
}
}
}
@@ -2413,7 +2424,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
context.llvm.irStaticInitializers.forEach {
val library = it.file.packageFragmentDescriptor.module.konanLibrary
val library = it.module.konanLibrary
val initializers = libraryToInitializers[library]
?: error("initializer for not included library ${library?.libraryFile}")
+18
View File
@@ -4208,6 +4208,15 @@ dynamicTest("interop_concurrentRuntime") {
expectedExitStatus = 99
}
dynamicTest("interop_kt42397") {
disabled = (project.testTarget != null && project.testTarget != project.hostName) ||
project.globalTestArgs.contains('-opt')
source = "interop/kt42397/knlibrary.kt"
cSource = "$projectDir/interop/kt42397/test.cpp"
clangTool = "clang++"
flags = ['-g']
}
task library_mismatch(type: KonanDriverTest) {
// Does not work for cross targets yet.
enabled = !(project.testTarget != null && project.target.name != project.hostName)
@@ -4467,6 +4476,15 @@ if (isAppleTarget(project)) {
}
swiftSources = ['framework/gh3343/']
}
frameworkTest("testKt42397Framework") {
enabled = !project.globalTestArgs.contains('-opt')
framework("Kt42397") {
sources = ['framework/kt42397']
opts = ['-g']
}
swiftSources = ['framework/kt42397']
}
}
/**
@@ -0,0 +1,12 @@
// The following 2 singletons are unused. However, since we are generating ObjC bindings for them,
// they should be marked as used, so that the code generator emits their deinitialization.
object A {
fun foo() = 1
}
class B {
companion object {
fun foo() = 2
}
}
@@ -0,0 +1,39 @@
import Kt42397
class Results {
var aFoo: Int32 = 0
var bFoo: Int32 = 0
}
func runTestKt42397(pointer: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? {
autoreleasepool {
let results = pointer.bindMemory(to: Results.self, capacity: 1).pointee
results.aFoo = A().foo()
results.bFoo = B.Companion().foo()
}
return nil
}
func testKt42397() throws {
let results = Results()
let resultsPtr = UnsafeMutablePointer<Results>.allocate(capacity: 1)
resultsPtr.initialize(to: results)
var thread: pthread_t? = nil
let result = pthread_create(&thread, nil, runTestKt42397, resultsPtr)
try assertEquals(actual: result, expected: 0)
pthread_join(thread!, nil)
try assertEquals(actual: results.aFoo, expected: 1)
try assertEquals(actual: results.bFoo, expected: 2)
}
// -------- Execution of the test --------
class TestTests : SimpleTestProvider {
override init() {
super.init()
test("Kt42397", testKt42397)
}
}
@@ -0,0 +1,10 @@
package knlibrary
// The following 2 singletons are unused. However, since we are generating C bindings for them,
// they should be marked as used, so that the code generator emits their deinitialization.
object A {}
class B {
companion object {}
}
@@ -0,0 +1,26 @@
#include "testlib_api.h"
#include <thread>
int main() {
auto t = std::thread([] {
auto lib = testlib_symbols();
// Initialize A and B.Companion and get their stable pointers.
auto a = lib->kotlin.root.knlibrary.A._instance();
auto bCompanion = lib->kotlin.root.knlibrary.B.Companion._instance();
// Now, dispose of the stable pointers.
lib->DisposeStablePointer(bCompanion.pinned);
lib->DisposeStablePointer(a.pinned);
// A and B.Companion now are owned by the global references only.
});
// This causes Kotlin runtime full deinitialization, because `t` is the only thread
// with the Kotlin runtime. So, all the globals will get deinitialized and memory
// leak checker will get executed (because .kt code is compiled with -g).
t.join();
return 0;
}