diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 7a2210d8bb3..457d13b441f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -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) 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 e58d37903a5..8925ef0adef 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 @@ -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 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 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.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) + } +} diff --git a/backend.native/tests/interop/kt42397/knlibrary.kt b/backend.native/tests/interop/kt42397/knlibrary.kt new file mode 100644 index 00000000000..eaefd5a1ad6 --- /dev/null +++ b/backend.native/tests/interop/kt42397/knlibrary.kt @@ -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 {} +} diff --git a/backend.native/tests/interop/kt42397/test.cpp b/backend.native/tests/interop/kt42397/test.cpp new file mode 100644 index 00000000000..2bd5cc286db --- /dev/null +++ b/backend.native/tests/interop/kt42397/test.cpp @@ -0,0 +1,26 @@ +#include "testlib_api.h" + +#include + +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; +}