diff --git a/kotlin-native/runtime/src/main/cpp/Memory.h b/kotlin-native/runtime/src/main/cpp/Memory.h index 2b86caf9735..db684b7ffa8 100644 --- a/kotlin-native/runtime/src/main/cpp/Memory.h +++ b/kotlin-native/runtime/src/main/cpp/Memory.h @@ -605,4 +605,11 @@ RUNTIME_NOTHROW ALWAYS_INLINE extern "C" void Kotlin_processObjectInMark(void* s RUNTIME_NOTHROW ALWAYS_INLINE extern "C" void Kotlin_processArrayInMark(void* state, ObjHeader* object); RUNTIME_NOTHROW ALWAYS_INLINE extern "C" void Kotlin_processEmptyObjectInMark(void* state, ObjHeader* object); +RUNTIME_NOTHROW extern "C" OBJ_GETTER(Kotlin_Interop_derefSpecialRef, kotlin::mm::RawSpecialRef *ref); +RUNTIME_NOTHROW extern "C" kotlin::mm::RawSpecialRef *Kotlin_Interop_createSpecialRef(ObjHeader *object); +RUNTIME_NOTHROW extern "C" void Kotlin_Interop_disposeSpecialRef(kotlin::mm::RawSpecialRef *ref); +RUNTIME_NOTHROW extern "C" void Kotlin_Interop_retainSpecialRef(kotlin::mm::RawSpecialRef *ref); +RUNTIME_NOTHROW extern "C" bool Kotlin_Interop_tryRetainSpecialRef(kotlin::mm::RawSpecialRef *ref); +RUNTIME_NOTHROW extern "C" void Kotlin_Interop_releaseSpecialRef(kotlin::mm::RawSpecialRef *ref); + #endif // RUNTIME_MEMORY_H diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt index 3c57d8d9080..b378ae9320e 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt @@ -12,6 +12,31 @@ import kotlin.internal.getProgressionLastElement import kotlin.reflect.KClass import kotlin.concurrent.AtomicReference import kotlinx.cinterop.* +import kotlinx.cinterop.NativePtr + +@ExperimentalNativeApi +@GCUnsafeCall("Kotlin_Interop_derefSpecialRef") +public external fun dereferenceSpecialRef(ref: COpaquePointer?): Any? + +@ExperimentalNativeApi +@GCUnsafeCall("Kotlin_Interop_createSpecialRef") +public external fun createSpecialRef(ref: Any?): COpaquePointer? + +@ExperimentalNativeApi +@GCUnsafeCall("Kotlin_Interop_disposeSpecialRef") +public external fun disposeSpecialRef(ref: COpaquePointer?) + +@ExperimentalNativeApi +@GCUnsafeCall("Kotlin_Interop_retainSpecialRef") +public external fun retainSpecialRef(ref: COpaquePointer?) + +@ExperimentalNativeApi +@GCUnsafeCall("Kotlin_Interop_tryRetainSpecialRef") +public external fun tryRetainSpecialRef(ref: COpaquePointer?): Boolean + +@ExperimentalNativeApi +@GCUnsafeCall("Kotlin_Interop_releaseSpecialRef") +public external fun releaseSpecialRef(ref: COpaquePointer?) @ExportForCppRuntime @PublishedApi diff --git a/kotlin-native/runtime/src/mm/cpp/Memory.cpp b/kotlin-native/runtime/src/mm/cpp/Memory.cpp index c4828da733a..9aeabb40b24 100644 --- a/kotlin-native/runtime/src/mm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/mm/cpp/Memory.cpp @@ -14,11 +14,13 @@ #include "GlobalsRegistry.hpp" #include "KAssert.h" #include "Natives.h" +#include "ObjCBackRef.hpp" #include "ObjectOps.hpp" #include "Porting.h" #include "ReferenceOps.hpp" #include "Runtime.h" #include "SafePoint.hpp" +#include "SpecialRefRegistry.hpp" #include "StableRef.hpp" #include "ThreadData.hpp" #include "ThreadRegistry.hpp" @@ -664,3 +666,33 @@ void kotlin::initObjectPool() noexcept { void kotlin::compactObjectPoolInCurrentThread() noexcept { alloc::compactObjectPoolInCurrentThread(); } + +RUNTIME_NOTHROW extern "C" OBJ_GETTER(Kotlin_Interop_derefSpecialRef, mm::RawSpecialRef *ref) { + RETURN_OBJ(ref ? *mm::ObjCBackRef(ref) : nullptr); +} + +RUNTIME_NOTHROW extern "C" mm::RawSpecialRef *Kotlin_Interop_createSpecialRef(ObjHeader *object) { + return object ? static_cast(mm::ObjCBackRef::create(object)) : nullptr; +} + +RUNTIME_NOTHROW extern "C" void Kotlin_Interop_disposeSpecialRef(mm::RawSpecialRef *ref) { + if (ref) { + mm::ObjCBackRef(ref).dispose(); + } +} + +RUNTIME_NOTHROW extern "C" void Kotlin_Interop_retainSpecialRef(mm::RawSpecialRef *ref) { + if (ref) { + mm::ObjCBackRef(ref).retain(); + } +} + +RUNTIME_NOTHROW extern "C" bool Kotlin_Interop_tryRetainSpecialRef(mm::RawSpecialRef *ref) { + return ref ? mm::ObjCBackRef(ref).tryRetain() : false; +} + +RUNTIME_NOTHROW extern "C" void Kotlin_Interop_releaseSpecialRef(mm::RawSpecialRef *ref) { + if (ref) { + mm::ObjCBackRef(ref).release(); + } +} diff --git a/native/native.tests/testData/CExport/InterfaceNone/nativeRefs/main.c b/native/native.tests/testData/CExport/InterfaceNone/nativeRefs/main.c new file mode 100644 index 00000000000..56f8d14fcaf --- /dev/null +++ b/native/native.tests/testData/CExport/InterfaceNone/nativeRefs/main.c @@ -0,0 +1,38 @@ +#include +#include + +uintptr_t get_null(); +uintptr_t get_singleton_object(); +uintptr_t get_static_object(); +uintptr_t get_array(); +uintptr_t get_local_object(); +uintptr_t dispose_object(uintptr_t); + +_Bool compare_identities(uintptr_t, uintptr_t); +_Bool compare_objects(uintptr_t, uintptr_t); +_Bool compare_arrays(uintptr_t, uintptr_t); + +void retain_object(uintptr_t); +void release_object(uintptr_t); + +void test(uintptr_t obj1, uintptr_t obj2, _Bool (*comparator)(uintptr_t, uintptr_t), int code) { + if (!comparator(obj1, obj2)) { + exit(code); + } + + release_object(obj1); + dispose_object(obj2); + + release_object(obj1); + dispose_object(obj2); +} + +int main() { + test(get_singleton_object(), get_singleton_object(), compare_identities, -1); + test(get_static_object(), get_static_object(), compare_identities, -2); + test(get_local_object(), get_local_object(), compare_objects, -3); + test(get_null(), get_null(), compare_identities, -4); + test(get_array(), get_array(), compare_arrays, -5); + + return 0; +} \ No newline at end of file diff --git a/native/native.tests/testData/CExport/InterfaceNone/nativeRefs/main.kt b/native/native.tests/testData/CExport/InterfaceNone/nativeRefs/main.kt new file mode 100644 index 00000000000..9ab7d0e692a --- /dev/null +++ b/native/native.tests/testData/CExport/InterfaceNone/nativeRefs/main.kt @@ -0,0 +1,52 @@ +import kotlin.native.internal.ExportedBridge +import kotlin.native.internal.* +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.COpaquePointer + +data object MyObject +const val string = "Hello, world!" +data class Data(var data: Unit = Unit) + +@ExportedBridge("get_null") +@OptIn(ExperimentalForeignApi::class) +fun getNull(): COpaquePointer? = createSpecialRef(null) + +@ExportedBridge("get_singleton_object") +@OptIn(ExperimentalForeignApi::class) +fun getSingletonObject(): COpaquePointer? = createSpecialRef(MyObject) + +@ExportedBridge("get_static_object") +@OptIn(ExperimentalForeignApi::class) +fun getStaticObject(): COpaquePointer? = createSpecialRef(string) + +@ExportedBridge("get_local_object") +@OptIn(ExperimentalForeignApi::class) +fun getLocalObject(): COpaquePointer? = createSpecialRef(Data()) + +@ExportedBridge("get_array") +@OptIn(ExperimentalForeignApi::class) +fun getArray(): COpaquePointer? = createSpecialRef(arrayOf(Data(), Data())) + +@ExportedBridge("compare_identities") +@OptIn(ExperimentalForeignApi::class) +fun compareIdentities(obj1: COpaquePointer?, obj2: COpaquePointer?): Boolean = dereferenceSpecialRef(obj1) === dereferenceSpecialRef(obj2) + +@ExportedBridge("compare_objects") +@OptIn(ExperimentalForeignApi::class) +fun compareObjects(obj1: COpaquePointer?, obj2: COpaquePointer?): Boolean = dereferenceSpecialRef(obj1) == dereferenceSpecialRef(obj2) + +@ExportedBridge("compare_arrays") +@OptIn(ExperimentalForeignApi::class) +fun compareArrays(obj1: COpaquePointer?, obj2: COpaquePointer?): Boolean = (dereferenceSpecialRef(obj1) as? Array) contentEquals (dereferenceSpecialRef(obj2) as? Array) + +@ExportedBridge("dispose_object") +@OptIn(ExperimentalForeignApi::class) +fun disposeObject(obj: COpaquePointer?): Unit = disposeSpecialRef(obj) + +@ExportedBridge("retain_object") +@OptIn(ExperimentalForeignApi::class) +fun retainObject(ref: COpaquePointer?) = retainSpecialRef(ref) + +@ExportedBridge("release_object") +@OptIn(ExperimentalForeignApi::class) +fun releaseObject(ref: COpaquePointer?) = releaseSpecialRef(ref) \ No newline at end of file diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CExportDynamicInterfaceNoneTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CExportDynamicInterfaceNoneTestGenerated.java index 708f7bd6b5e..b65aa916d6e 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CExportDynamicInterfaceNoneTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CExportDynamicInterfaceNoneTestGenerated.java @@ -27,6 +27,12 @@ public class CExportDynamicInterfaceNoneTestGenerated extends AbstractNativeCExp KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/CExport/InterfaceNone"), Pattern.compile("^([^_](.+))$"), null, false); } + @Test + @TestMetadata("nativeRefs") + public void testNativeRefs() { + runTest("native/native.tests/testData/CExport/InterfaceNone/nativeRefs/"); + } + @Test @TestMetadata("primitiveTypes") public void testPrimitiveTypes() { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CExportStaticInterfaceNoneTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CExportStaticInterfaceNoneTestGenerated.java index 41ca160381b..299e03467c6 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CExportStaticInterfaceNoneTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CExportStaticInterfaceNoneTestGenerated.java @@ -27,6 +27,12 @@ public class CExportStaticInterfaceNoneTestGenerated extends AbstractNativeCExpo KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/CExport/InterfaceNone"), Pattern.compile("^([^_](.+))$"), null, false); } + @Test + @TestMetadata("nativeRefs") + public void testNativeRefs() { + runTest("native/native.tests/testData/CExport/InterfaceNone/nativeRefs/"); + } + @Test @TestMetadata("primitiveTypes") public void testPrimitiveTypes() { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirCExportDynamicInterfaceNoneTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirCExportDynamicInterfaceNoneTestGenerated.java index ca6a2f88c70..ef19a187465 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirCExportDynamicInterfaceNoneTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirCExportDynamicInterfaceNoneTestGenerated.java @@ -31,6 +31,12 @@ public class FirCExportDynamicInterfaceNoneTestGenerated extends AbstractNativeC KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/CExport/InterfaceNone"), Pattern.compile("^([^_](.+))$"), null, false); } + @Test + @TestMetadata("nativeRefs") + public void testNativeRefs() { + runTest("native/native.tests/testData/CExport/InterfaceNone/nativeRefs/"); + } + @Test @TestMetadata("primitiveTypes") public void testPrimitiveTypes() { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirCExportStaticInterfaceNoneTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirCExportStaticInterfaceNoneTestGenerated.java index cee4ae1d029..7ecd14f1fed 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirCExportStaticInterfaceNoneTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirCExportStaticInterfaceNoneTestGenerated.java @@ -31,6 +31,12 @@ public class FirCExportStaticInterfaceNoneTestGenerated extends AbstractNativeCE KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/CExport/InterfaceNone"), Pattern.compile("^([^_](.+))$"), null, false); } + @Test + @TestMetadata("nativeRefs") + public void testNativeRefs() { + runTest("native/native.tests/testData/CExport/InterfaceNone/nativeRefs/"); + } + @Test @TestMetadata("primitiveTypes") public void testPrimitiveTypes() {