Ensure deinitialization of singletons for native library generation (#4422)
This commit is contained in:
committed by
GitHub
parent
77949abcdc
commit
86ae02eb25
+2
-1
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.library.resolver.TopologicalLibraryOrder
|
|||||||
import org.jetbrains.kotlin.backend.konan.Context
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
import org.jetbrains.kotlin.backend.konan.hash.GlobalHash
|
import org.jetbrains.kotlin.backend.konan.hash.GlobalHash
|
||||||
import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin
|
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.CompiledKlibModuleOrigin
|
||||||
import org.jetbrains.kotlin.descriptors.konan.CurrentKlibModuleOrigin
|
import org.jetbrains.kotlin.descriptors.konan.CurrentKlibModuleOrigin
|
||||||
import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
|
import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
|
||||||
@@ -638,4 +639,4 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
|||||||
val llvmVector128 = vector128Type
|
val llvmVector128 = vector128Type
|
||||||
}
|
}
|
||||||
|
|
||||||
class IrStaticInitializer(val file: IrFile, val initializer: LLVMValueRef)
|
class IrStaticInitializer(val module: ModuleDescriptor, val initializer: LLVMValueRef)
|
||||||
|
|||||||
+35
-24
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.backend.konan.ir.*
|
|||||||
import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation
|
import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation
|
||||||
import org.jetbrains.kotlin.builtins.UnsignedType
|
import org.jetbrains.kotlin.builtins.UnsignedType
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.IrStatement
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
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)
|
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) {
|
override fun visitElement(element: IrElement) {
|
||||||
@@ -337,19 +355,22 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
initializeCachedBoxes(context)
|
initializeCachedBoxes(context)
|
||||||
declaration.acceptChildrenVoid(this)
|
declaration.acceptChildrenVoid(this)
|
||||||
|
|
||||||
// Note: it is here because it also generates some bitcode.
|
runAndProcessInitializers(declaration.descriptor) {
|
||||||
context.objCExport.generate(codegen)
|
// Note: it is here because it also generates some bitcode.
|
||||||
|
context.objCExport.generate(codegen)
|
||||||
|
|
||||||
codegen.objCDataGenerator?.finishModule()
|
codegen.objCDataGenerator?.finishModule()
|
||||||
|
|
||||||
context.coverage.writeRegionInfo()
|
context.coverage.writeRegionInfo()
|
||||||
appendDebugSelector()
|
appendDebugSelector()
|
||||||
appendLlvmUsed("llvm.used", context.llvm.usedFunctions + context.llvm.usedGlobals)
|
appendLlvmUsed("llvm.used", context.llvm.usedFunctions + context.llvm.usedGlobals)
|
||||||
appendLlvmUsed("llvm.compiler.used", context.llvm.compilerUsedGlobals)
|
appendLlvmUsed("llvm.compiler.used", context.llvm.compilerUsedGlobals)
|
||||||
appendStaticInitializers()
|
if (context.isNativeLibrary) {
|
||||||
if (context.isNativeLibrary) {
|
appendCAdapters()
|
||||||
appendCAdapters()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appendStaticInitializers()
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------//
|
//-------------------------------------------------------------------------//
|
||||||
@@ -487,21 +508,11 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
//-------------------------------------------------------------------------//
|
//-------------------------------------------------------------------------//
|
||||||
|
|
||||||
override fun visitFile(declaration: IrFile) {
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
using(FileScope(declaration)) {
|
using(FileScope(declaration)) {
|
||||||
declaration.acceptChildrenVoid(this)
|
runAndProcessInitializers(declaration.packageFragmentDescriptor.module) {
|
||||||
|
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)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2413,7 +2424,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.llvm.irStaticInitializers.forEach {
|
context.llvm.irStaticInitializers.forEach {
|
||||||
val library = it.file.packageFragmentDescriptor.module.konanLibrary
|
val library = it.module.konanLibrary
|
||||||
val initializers = libraryToInitializers[library]
|
val initializers = libraryToInitializers[library]
|
||||||
?: error("initializer for not included library ${library?.libraryFile}")
|
?: error("initializer for not included library ${library?.libraryFile}")
|
||||||
|
|
||||||
|
|||||||
@@ -4208,6 +4208,15 @@ dynamicTest("interop_concurrentRuntime") {
|
|||||||
expectedExitStatus = 99
|
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) {
|
task library_mismatch(type: KonanDriverTest) {
|
||||||
// Does not work for cross targets yet.
|
// Does not work for cross targets yet.
|
||||||
enabled = !(project.testTarget != null && project.target.name != project.hostName)
|
enabled = !(project.testTarget != null && project.target.name != project.hostName)
|
||||||
@@ -4467,6 +4476,15 @@ if (isAppleTarget(project)) {
|
|||||||
}
|
}
|
||||||
swiftSources = ['framework/gh3343/']
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user