diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterGenerator.kt index 7207eb66ea4..f9acbc46677 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterGenerator.kt @@ -10,6 +10,8 @@ import org.jetbrains.kotlin.backend.common.pop import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.driver.phases.PsiToIrContext +import org.jetbrains.kotlin.config.CommonConfigurationKeys.USE_FIR +import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.ir.util.referenceFunction @@ -394,6 +396,7 @@ private fun ModuleDescriptor.getPackageFragments(): List { private val scopes = mutableListOf() @@ -481,6 +484,20 @@ internal class CAdapterGenerator( val fragments = descriptor.module.getPackage(FqName.ROOT).fragments.filter { it.module in moduleDescriptors } visitChildren(fragments) + + if (configuration.get(USE_FIR) == true) { + // K2 does not serialize empty package fragments, thus breaking the scope chain. + // The following traverse definitely reaches every subpackage fragment. + scopes.push(getPackageScope(FqName.ROOT)) + val subfragments = descriptor.module.getSubPackagesOf(FqName.ROOT) { true } + .flatMap { + descriptor.module.getPackage(it).fragments.filter { + it.module in moduleDescriptors + } + } + visitChildren(subfragments) + scopes.pop() + } return true } @@ -508,25 +525,28 @@ internal class CAdapterGenerator( override fun visitPackageFragmentDescriptor(descriptor: PackageFragmentDescriptor, ignored: Void?): Boolean { val fqName = descriptor.fqName - val packageScope = packageScopes.getOrPut(fqName) { - val name = if (fqName.isRoot) "root" else translateName(fqName.shortName()) - val scope = ExportedElementScope(ScopeKind.PACKAGE, name) - scopes.last().scopes += scope - scope - } + val packageScope = getPackageScope(fqName) scopes.push(packageScope) - visitChildren(DescriptorUtils.getAllDescriptors(descriptor.getMemberScope())) + if (!seenPackageFragments.contains(descriptor)) + visitChildren(DescriptorUtils.getAllDescriptors(descriptor.getMemberScope())) for (currentPackageFragment in currentPackageFragments) { if (!seenPackageFragments.contains(currentPackageFragment) && currentPackageFragment.fqName.isChildOf(descriptor.fqName)) { - seenPackageFragments += currentPackageFragment visitChildren(currentPackageFragment) + seenPackageFragments += currentPackageFragment } } scopes.pop() return true } + private fun getPackageScope(fqName: FqName) = packageScopes.getOrPut(fqName) { + val name = if (fqName.isRoot) "root" else translateName(fqName.shortName()) + val scope = ExportedElementScope(ScopeKind.PACKAGE, name) + scopes.last().scopes += scope + scope + } + private val moduleDescriptors = mutableSetOf() diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/CExport.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/CExport.kt index 9826f223f18..86cb3d2a949 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/CExport.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/CExport.kt @@ -19,7 +19,7 @@ internal val BuildCExports = createSimpleNamedCompilerPhase val prefix = context.config.fullExportedNamePrefix.replace("-|\\.".toRegex(), "_") val typeTranslator = CAdapterTypeTranslator(prefix, context.builtIns) - CAdapterGenerator(context, typeTranslator).buildExports(input.moduleDescriptor) + CAdapterGenerator(context, input.environment.configuration, typeTranslator).buildExports(input.moduleDescriptor) } internal data class CExportGenerateApiInput( diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 9308195658c..246b4cb332b 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -5418,8 +5418,7 @@ dynamicTest("kt41904") { for (i in 0..2) { dynamicTest("kt42796_$i") { clangTool = "clang++" - disabled = (project.testTarget == 'wasm32') || // wasm doesn't support -produce dynamic - (i==1 && isK2(project)) // KT-56182 + disabled = (project.testTarget == 'wasm32') // wasm doesn't support -produce dynamic source = "produce_dynamic/kt-42796/main-${i}.kt" cSource = "$projectDir/produce_dynamic/kt-42796/main.cpp" useGoldenData = true @@ -5455,12 +5454,16 @@ dynamicTest("interop_concurrentRuntime") { expectedExitStatus = 99 } -dynamicTest("interop_kt42397") { - disabled = (project.testTarget == 'wasm32') || // wasm doesn't support -produce dynamic - isK2(project) // KT-56182 - source = "interop/kt42397/knlibrary.kt" - cSource = "$projectDir/interop/kt42397/test.cpp" - clangTool = "clang++" +for (i in ["kt42397", "kt56182_root", "kt56182_package1lvl", "kt56182_subpackage2lvl", "kt56182_root_package1lvl", "kt56182_root_subpackage2lvl"]) { + // note: kt56182_package1lvl is same as kt42397 + // note: kt56182_root_package1lvl is similar as kt41904 + // note: kt56182_subpackage2lvl is similar as kt42796_1 + dynamicTest("interop_$i") { + disabled = (project.testTarget == 'wasm32') // wasm doesn't support -produce dynamic + source = "interop/$i/knlibrary.kt" + cSource = "$projectDir/interop/$i/test.cpp" + clangTool = "clang++" + } } dynamicTest("interop_cleaners_main_thread") { diff --git a/kotlin-native/backend.native/tests/interop/kt56182_package1lvl/knlibrary.kt b/kotlin-native/backend.native/tests/interop/kt56182_package1lvl/knlibrary.kt new file mode 100644 index 00000000000..3a5bda9ae83 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_package1lvl/knlibrary.kt @@ -0,0 +1,18 @@ +// This test is similar to kt42397, just everything is doubled: in "package knlibrary" and the root package + +package knlibrary + +import kotlin.native.Platform + +// 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 {} +} + +fun enableMemoryChecker() { + Platform.isMemoryLeakCheckerActive = true +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_package1lvl/test.cpp b/kotlin-native/backend.native/tests/interop/kt56182_package1lvl/test.cpp new file mode 100644 index 00000000000..983f6684961 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_package1lvl/test.cpp @@ -0,0 +1,28 @@ +#include "testlib_api.h" + +#include + +int main() { + auto t = std::thread([] { + auto lib = testlib_symbols(); + + lib->kotlin.root.knlibrary.enableMemoryChecker(); + + // 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; +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_root/knlibrary.kt b/kotlin-native/backend.native/tests/interop/kt56182_root/knlibrary.kt new file mode 100644 index 00000000000..eda4a1dc2af --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_root/knlibrary.kt @@ -0,0 +1,16 @@ +// This test is similar to kt42397, just everything is in root package instead of "package knlibrary" + +import kotlin.native.Platform + +// 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 {} +} + +fun enableMemoryChecker() { + Platform.isMemoryLeakCheckerActive = true +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_root/test.cpp b/kotlin-native/backend.native/tests/interop/kt56182_root/test.cpp new file mode 100644 index 00000000000..f371d2a3a26 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_root/test.cpp @@ -0,0 +1,28 @@ +#include "testlib_api.h" + +#include + +int main() { + auto t = std::thread([] { + auto lib = testlib_symbols(); + + lib->kotlin.root.enableMemoryChecker(); + + // Initialize A and B.Companion and get their stable pointers. + auto a = lib->kotlin.root.A._instance(); + auto bCompanion = lib->kotlin.root.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; +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_root_package1lvl/knlibrary.kt b/kotlin-native/backend.native/tests/interop/kt56182_root_package1lvl/knlibrary.kt new file mode 100644 index 00000000000..f196b2167f1 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_root_package1lvl/knlibrary.kt @@ -0,0 +1,30 @@ +// This test is similar to kt42397, just everything is doubled: in "package knlibrary.subpackage" and the root package + +// FILE: FirstLevelPackage.kt +package knlibrary + +import kotlin.native.Platform + +// 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 {} +} + +fun enableMemoryChecker() { + Platform.isMemoryLeakCheckerActive = true +} + +// FILE: rootPackage.kt +object A {} + +class B { + companion object {} +} + +fun enableMemoryChecker() { + Platform.isMemoryLeakCheckerActive = true +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_root_package1lvl/test.cpp b/kotlin-native/backend.native/tests/interop/kt56182_root_package1lvl/test.cpp new file mode 100644 index 00000000000..5ff9795a47b --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_root_package1lvl/test.cpp @@ -0,0 +1,39 @@ +#include "testlib_api.h" + +#include + +int main() { + auto t = std::thread([] { + auto lib = testlib_symbols(); + + lib->kotlin.root.knlibrary.enableMemoryChecker(); + + // 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. + + // Now same actions for objects in the root package + lib->kotlin.root.enableMemoryChecker(); + + // Initialize A and B.Companion and get their stable pointers. + auto a2 = lib->kotlin.root.A._instance(); + auto bCompanion2 = lib->kotlin.root.B.Companion._instance(); + + // Now, dispose of the stable pointers. + lib->DisposeStablePointer(bCompanion2.pinned); + lib->DisposeStablePointer(a2.pinned); + }); + + // 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; +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_root_subpackage2lvl/knlibrary.kt b/kotlin-native/backend.native/tests/interop/kt56182_root_subpackage2lvl/knlibrary.kt new file mode 100644 index 00000000000..6847aeaacad --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_root_subpackage2lvl/knlibrary.kt @@ -0,0 +1,30 @@ +// This test is similar to kt42397, just everything is doubled: in "package knlibrary.subpackage" and the root package + +// FILE: SecondLevelPackage.kt +package knlibrary.subpackage + +import kotlin.native.Platform + +// 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 {} +} + +fun enableMemoryChecker() { + Platform.isMemoryLeakCheckerActive = true +} + +// FILE: rootPackage.kt +object A {} + +class B { + companion object {} +} + +fun enableMemoryChecker() { + Platform.isMemoryLeakCheckerActive = true +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_root_subpackage2lvl/test.cpp b/kotlin-native/backend.native/tests/interop/kt56182_root_subpackage2lvl/test.cpp new file mode 100644 index 00000000000..af09182a6c5 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_root_subpackage2lvl/test.cpp @@ -0,0 +1,39 @@ +#include "testlib_api.h" + +#include + +int main() { + auto t = std::thread([] { + auto lib = testlib_symbols(); + + lib->kotlin.root.knlibrary.subpackage.enableMemoryChecker(); + + // Initialize A and B.Companion and get their stable pointers. + auto a = lib->kotlin.root.knlibrary.subpackage.A._instance(); + auto bCompanion = lib->kotlin.root.knlibrary.subpackage.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. + + // Now same actions for objects in the root package + lib->kotlin.root.enableMemoryChecker(); + + // Initialize A and B.Companion and get their stable pointers. + auto a2 = lib->kotlin.root.A._instance(); + auto bCompanion2 = lib->kotlin.root.B.Companion._instance(); + + // Now, dispose of the stable pointers. + lib->DisposeStablePointer(bCompanion2.pinned); + lib->DisposeStablePointer(a2.pinned); + }); + + // 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; +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_subpackage2lvl/knlibrary.kt b/kotlin-native/backend.native/tests/interop/kt56182_subpackage2lvl/knlibrary.kt new file mode 100644 index 00000000000..88b182a214e --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_subpackage2lvl/knlibrary.kt @@ -0,0 +1,17 @@ +// This test is similar to kt42397, just everything is in "package knlibrary.subpackage" instead of "package knlibrary" +package knlibrary.subpackage + +import kotlin.native.Platform + +// 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 {} +} + +fun enableMemoryChecker() { + Platform.isMemoryLeakCheckerActive = true +} diff --git a/kotlin-native/backend.native/tests/interop/kt56182_subpackage2lvl/test.cpp b/kotlin-native/backend.native/tests/interop/kt56182_subpackage2lvl/test.cpp new file mode 100644 index 00000000000..5a45f346357 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/kt56182_subpackage2lvl/test.cpp @@ -0,0 +1,28 @@ +#include "testlib_api.h" + +#include + +int main() { + auto t = std::thread([] { + auto lib = testlib_symbols(); + + lib->kotlin.root.knlibrary.subpackage.enableMemoryChecker(); + + // Initialize A and B.Companion and get their stable pointers. + auto a = lib->kotlin.root.knlibrary.subpackage.A._instance(); + auto bCompanion = lib->kotlin.root.knlibrary.subpackage.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; +}