diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt index 0887b1f0330..31886c8f73b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt @@ -101,7 +101,7 @@ private fun PatchBuilder.addObjCPatches() { addProtocolImport("NSCopying") addPrivateSelector("toKotlin:") - addPrivateSelector("releaseAsAssociatedObject") + addPrivateSelector("releaseAsAssociatedObject:") addPrivateClass("KIteratorAsNSEnumerator", "iteratorHolder") addPrivateClass("KListAsNSArray", "listHolder") diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 6d6ecb376b8..d141c161b1f 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -4316,7 +4316,7 @@ if (PlatformInfo.isAppleTarget(project)) { } interopTestMultifile("interop_objc_tests") { - enabled = !isExperimentalMM // Experimental MM does not support Obj-C weaks and thread state switching for ObjC interop yet. + enabled = !isExperimentalMM // Experimental MM does not support thread state switching for ObjC interop yet. source = "interop/objc/tests/" interop = 'objcTests' flags = ['-tr', '-e', 'main'] @@ -4787,7 +4787,7 @@ Task frameworkTest(String name, Closure configurator) { if (isAppleTarget(project)) { frameworkTest('testObjCExport') { - enabled = !isExperimentalMM // Experimental MM does not support Obj-C weaks and thread state switching for ObjC interop yet. + enabled = !isExperimentalMM // Experimental MM does not support thread state switching for ObjC interop yet. final String frameworkName = 'Kt' final String dir = "$testOutputFramework/testObjCExport" final File lazyHeader = file("$dir/$target-lazy.h") @@ -4836,7 +4836,7 @@ if (isAppleTarget(project)) { } frameworkTest('testObjCExportNoGenerics') { - enabled = !isExperimentalMM // Experimental MM does not support Obj-C weaks and thread state switching for ObjC interop yet. + enabled = !isExperimentalMM // Experimental MM does not support thread state switching for ObjC interop yet. final String frameworkName = 'KtNoGenerics' final String frameworkArtifactName = 'Kt' final String dir = "$testOutputFramework/testObjCExportNoGenerics" @@ -4889,7 +4889,7 @@ if (isAppleTarget(project)) { } frameworkTest('testObjCExportStatic') { - enabled = !isExperimentalMM // Experimental MM does not support Obj-C weaks and thread state switching for ObjC interop yet. + enabled = !isExperimentalMM // Experimental MM does not support thread state switching for ObjC interop yet. final String frameworkName = 'KtStatic' final String frameworkArtifactName = 'Kt' final String libraryName = frameworkName + "Library" diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h index a00c0ec344a..2b95f5ecb41 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h @@ -2312,6 +2312,7 @@ __attribute__((swift_name("ValuesKt"))) @property (class) id _Nullable warningVar __attribute__((swift_name("warningVar"))) __attribute__((deprecated("warning"))); @property (class) int32_t gh3525BaseInitCount __attribute__((swift_name("gh3525BaseInitCount"))); @property (class) int32_t gh3525InitCount __attribute__((swift_name("gh3525InitCount"))); +@property (class, readonly) BOOL isExperimentalMM __attribute__((swift_name("isExperimentalMM"))); @end; __attribute__((swift_name("InvariantSuper"))) diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h index a30a2e3d830..8e1b137ecd7 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h @@ -2285,6 +2285,7 @@ __attribute__((swift_name("ValuesKt"))) @property (class) id _Nullable warningVar __attribute__((swift_name("warningVar"))) __attribute__((deprecated("warning"))); @property (class) int32_t gh3525BaseInitCount __attribute__((swift_name("gh3525BaseInitCount"))); @property (class) int32_t gh3525InitCount __attribute__((swift_name("gh3525InitCount"))); +@property (class, readonly) BOOL isExperimentalMM __attribute__((swift_name("isExperimentalMM"))); @end; __attribute__((swift_name("InvariantSuper"))) diff --git a/kotlin-native/backend.native/tests/objcexport/values.kt b/kotlin-native/backend.native/tests/objcexport/values.kt index 6f6d224360c..cdf2ab729f8 100644 --- a/kotlin-native/backend.native/tests/objcexport/values.kt +++ b/kotlin-native/backend.native/tests/objcexport/values.kt @@ -1020,4 +1020,7 @@ interface Bar_FakeOverrideInInterface : Foo_FakeOverrideInInterface fun callFoo_FakeOverrideInInterface(obj: Bar_FakeOverrideInInterface) { obj.foo(null) -} \ No newline at end of file +} + +val isExperimentalMM: Boolean + get() = kotlin.native.Platform.memoryModel == kotlin.native.MemoryModel.EXPERIMENTAL diff --git a/kotlin-native/backend.native/tests/objcexport/values.swift b/kotlin-native/backend.native/tests/objcexport/values.swift index 5d7f7b2cfe4..27566284766 100644 --- a/kotlin-native/backend.native/tests/objcexport/values.swift +++ b/kotlin-native/backend.native/tests/objcexport/values.swift @@ -596,10 +596,17 @@ func testShared() throws { try assertFalse(ValuesKt.isFrozen(obj: obj), "isFrozen(\(obj))") } - try assertFrozen(NSObject()) - try assertFrozen(TestSharedIImpl()) - try assertFrozen(ValuesKt.kotlinLambda(block: { return $0 }) as AnyObject) - try assertNotFrozen(FinalClassExtOpen()) + if ValuesKt.isExperimentalMM { + try assertNotFrozen(NSObject()) + try assertNotFrozen(TestSharedIImpl()) + try assertNotFrozen(ValuesKt.kotlinLambda(block: { return $0 }) as AnyObject) + try assertNotFrozen(FinalClassExtOpen()) + } else { + try assertFrozen(NSObject()) + try assertFrozen(TestSharedIImpl()) + try assertFrozen(ValuesKt.kotlinLambda(block: { return $0 }) as AnyObject) + try assertNotFrozen(FinalClassExtOpen()) + } } class PureSwiftClass { @@ -1411,4 +1418,4 @@ class ValuesTests : SimpleTestProvider { // Stress test, must remain the last one: test("TestGH2931", testGH2931) } -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/gc/common/cpp/MarkAndSweepUtils.hpp b/kotlin-native/runtime/src/gc/common/cpp/MarkAndSweepUtils.hpp index 1eb97eb8048..62fb9fb41a0 100644 --- a/kotlin-native/runtime/src/gc/common/cpp/MarkAndSweepUtils.hpp +++ b/kotlin-native/runtime/src/gc/common/cpp/MarkAndSweepUtils.hpp @@ -62,6 +62,7 @@ typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFact auto* objHeader = it->IsArray() ? it->GetArrayHeader()->obj() : it->GetObjHeader(); if (auto* extraObject = mm::ExtraObjectData::Get(objHeader)) { extraObject->ClearWeakReferenceCounter(); + extraObject->DetachAssociatedObject(); } if (HasFinalizers(objHeader)) { iter.MoveAndAdvance(finalizerQueue, it); diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index 56b299bb3b3..faa01669bc4 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -3137,7 +3137,7 @@ void ObjHeader::destroyMetaObject(ObjHeader* object) { } #ifdef KONAN_OBJC_INTEROP - Kotlin_ObjCExport_releaseAssociatedObject(meta->associatedObject_); + Kotlin_ObjCExport_detachAndReleaseAssociatedObject(meta->associatedObject_); #endif konanFreeMemory(meta); @@ -3286,6 +3286,10 @@ RUNTIME_NOTHROW void ReleaseHeapRefNoCollectRelaxed(const ObjHeader* object) { releaseHeapRef(const_cast(object)); } +RUNTIME_NOTHROW OBJ_GETTER(TryRef, ObjHeader* object) { + RuntimeFail("Only for experimental MM"); +} + ForeignRefContext InitLocalForeignRef(ObjHeader* object) { return initLocalForeignRef(object); } diff --git a/kotlin-native/runtime/src/legacymm/cpp/MemoryPrivate.hpp b/kotlin-native/runtime/src/legacymm/cpp/MemoryPrivate.hpp index 75f523ca6f0..cc2493d5568 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/MemoryPrivate.hpp +++ b/kotlin-native/runtime/src/legacymm/cpp/MemoryPrivate.hpp @@ -327,6 +327,7 @@ MODEL_VARIANTS(void, UpdateHeapRefsInsideOneArray, const ArrayHeader* array, int MODEL_VARIANTS(void, EnterFrame, ObjHeader** start, int parameters, int count); MODEL_VARIANTS(void, LeaveFrame, ObjHeader** start, int parameters, int count); +void ReleaseHeapRef(const ObjHeader* object) RUNTIME_NOTHROW; MODEL_VARIANTS(void, ReleaseHeapRef, const ObjHeader* object); MODEL_VARIANTS(void, ReleaseHeapRefNoCollect, const ObjHeader* object); diff --git a/kotlin-native/runtime/src/main/cpp/Memory.h b/kotlin-native/runtime/src/main/cpp/Memory.h index 9a9179e27c4..b22c6f3f565 100644 --- a/kotlin-native/runtime/src/main/cpp/Memory.h +++ b/kotlin-native/runtime/src/main/cpp/Memory.h @@ -298,11 +298,13 @@ bool Kotlin_Any_isShareable(ObjHeader* thiz); void Kotlin_Any_share(ObjHeader* thiz); void PerformFullGC(MemoryState* memory) RUNTIME_NOTHROW; +// Only for legacy bool TryAddHeapRef(const ObjHeader* object); - -void ReleaseHeapRef(const ObjHeader* object) RUNTIME_NOTHROW; void ReleaseHeapRefNoCollect(const ObjHeader* object) RUNTIME_NOTHROW; +// Only for experimental +OBJ_GETTER(TryRef, ObjHeader* object) RUNTIME_NOTHROW; + ForeignRefContext InitLocalForeignRef(ObjHeader* object); ForeignRefContext InitForeignRef(ObjHeader* object); diff --git a/kotlin-native/runtime/src/main/cpp/MemorySharedRefs.cpp b/kotlin-native/runtime/src/main/cpp/MemorySharedRefs.cpp index a29ffadd060..7205d22b7d9 100644 --- a/kotlin-native/runtime/src/main/cpp/MemorySharedRefs.cpp +++ b/kotlin-native/runtime/src/main/cpp/MemorySharedRefs.cpp @@ -146,19 +146,30 @@ bool BackRefFromAssociatedObject::tryAddRef() { if (obj_ == nullptr) return false; // E.g. after [detach]. - // Suboptimal but simple: - ensureRefAccessible(obj_, context_); + if (CurrentMemoryModel == MemoryModel::kExperimental) { + ObjHolder holder; + ObjHeader* obj = TryRef(obj_, holder.slot()); + // Failed to lock weak reference. + if (obj == nullptr) return false; + RuntimeAssert(obj == obj_, "Mismatched locked weak. obj=%p obj_=%p", obj, obj_); + // TODO: This is a very weird way to ask for "unsafe" addRef. + addRef(); + return true; + } else { + // Suboptimal but simple: + ensureRefAccessible(obj_, context_); - ObjHeader* obj = obj_; + ObjHeader* obj = obj_; - if (!TryAddHeapRef(obj)) return false; - RuntimeAssert(isForeignRefAccessible(obj_, context_), "Cannot be inaccessible because of the check above"); - // TODO: This is a very weird way to ask for "unsafe" addRef. - addRef(); - ReleaseHeapRefNoCollect(obj); // Balance TryAddHeapRef. - // TODO: consider optimizing for non-shared objects. + if (!TryAddHeapRef(obj)) return false; + RuntimeAssert(isForeignRefAccessible(obj_, context_), "Cannot be inaccessible because of the check above"); + // TODO: This is a very weird way to ask for "unsafe" addRef. + addRef(); + ReleaseHeapRefNoCollect(obj); // Balance TryAddHeapRef. + // TODO: consider optimizing for non-shared objects. - return true; + return true; + } } template bool BackRefFromAssociatedObject::tryAddRef(); @@ -183,6 +194,10 @@ void BackRefFromAssociatedObject::detach() { obj_ = nullptr; // Handled in addRef/tryAddRef/releaseRef/ref. } +ALWAYS_INLINE void BackRefFromAssociatedObject::assertDetached() { + RuntimeAssert(obj_ == nullptr, "Expecting this=%p to be detached, but found obj_=%p", this, obj_); +} + template ObjHeader* BackRefFromAssociatedObject::ref() const { RuntimeAssert(obj_ != nullptr, "no valid Kotlin object found"); diff --git a/kotlin-native/runtime/src/main/cpp/MemorySharedRefs.hpp b/kotlin-native/runtime/src/main/cpp/MemorySharedRefs.hpp index ab40b2457f2..16ab41b66ed 100644 --- a/kotlin-native/runtime/src/main/cpp/MemorySharedRefs.hpp +++ b/kotlin-native/runtime/src/main/cpp/MemorySharedRefs.hpp @@ -54,6 +54,7 @@ class BackRefFromAssociatedObject { void releaseRef(); void detach(); + void assertDetached(); // Error if called from the wrong worker with non-frozen obj_. template diff --git a/kotlin-native/runtime/src/main/cpp/ObjCExport.mm b/kotlin-native/runtime/src/main/cpp/ObjCExport.mm index 58f0ecafe44..61181855099 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCExport.mm +++ b/kotlin-native/runtime/src/main/cpp/ObjCExport.mm @@ -123,13 +123,29 @@ static Class getOrCreateClass(const TypeInfo* typeInfo); extern "C" id objc_retainAutoreleaseReturnValue(id self); -extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject) { +namespace { + +ALWAYS_INLINE void send_releaseAsAssociatedObject(void* associatedObject, ReleaseMode mode) { if (associatedObject != nullptr) { - auto msgSend = reinterpret_cast(&objc_msgSend); - msgSend(associatedObject, Kotlin_ObjCExport_releaseAsAssociatedObjectSelector); + auto msgSend = reinterpret_cast(&objc_msgSend); + msgSend(associatedObject, Kotlin_ObjCExport_releaseAsAssociatedObjectSelector, mode); } } +} // namespace + +extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject) { + send_releaseAsAssociatedObject(associatedObject, ReleaseMode::kRelease); +} + +extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAndReleaseAssociatedObject(void* associatedObject) { + send_releaseAsAssociatedObject(associatedObject, ReleaseMode::kDetachAndRelease); +} + +extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAssociatedObject(void* associatedObject) { + send_releaseAsAssociatedObject(associatedObject, ReleaseMode::kDetach); +} + extern "C" id Kotlin_ObjCExport_convertUnit(ObjHeader* unitInstance) { static dispatch_once_t onceToken; static id instance = nullptr; @@ -300,7 +316,7 @@ static OBJ_GETTER(blockToKotlinImp, id self, SEL cmd); static OBJ_GETTER(boxedBooleanToKotlinImp, NSNumber* self, SEL cmd); static OBJ_GETTER(SwiftObject_toKotlinImp, id self, SEL cmd); -static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd); +static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd, ReleaseMode mode); static void initTypeAdaptersFrom(const ObjCTypeAdapter** adapters, int count) { for (int index = 0; index < count; ++index) { @@ -359,7 +375,7 @@ static void Kotlin_ObjCExport_initializeImpl() { swiftRootClass, releaseAsAssociatedObjectSelector, (IMP)SwiftObject_releaseAsAssociatedObjectImp, releaseAsAssociatedObjectTypeEncoding ); - RuntimeAssert(added, "Unable to add 'releaseAsAssociatedObject' method to SwiftObject class"); + RuntimeAssert(added, "Unable to add 'releaseAsAssociatedObject:' method to SwiftObject class"); } } } @@ -379,7 +395,9 @@ static OBJ_GETTER(SwiftObject_toKotlinImp, id self, SEL cmd) { RETURN_RESULT_OF(Kotlin_ObjCExport_convertUnmappedObjCObject, self); } -static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd) { +static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd, ReleaseMode mode) { + if (!ReleaseModeHasRelease(mode)) + return; objc_release(self); } diff --git a/kotlin-native/runtime/src/main/cpp/ObjCExportPrivate.h b/kotlin-native/runtime/src/main/cpp/ObjCExportPrivate.h index b68b33b6d00..2a3d1b4deb4 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCExportPrivate.h +++ b/kotlin-native/runtime/src/main/cpp/ObjCExportPrivate.h @@ -18,6 +18,32 @@ +(instancetype)createWrapper:(ObjHeader*)obj; @end; +enum class ReleaseMode { + kRelease, + kDetachAndRelease, + kDetach, +}; + +inline bool ReleaseModeHasDetach(ReleaseMode mode) { + switch (mode) { + case ReleaseMode::kRelease: + return false; + case ReleaseMode::kDetachAndRelease: + case ReleaseMode::kDetach: + return true; + } +} + +inline bool ReleaseModeHasRelease(ReleaseMode mode) { + switch (mode) { + case ReleaseMode::kRelease: + case ReleaseMode::kDetachAndRelease: + return true; + case ReleaseMode::kDetach: + return false; + } +} + extern "C" void Kotlin_ObjCExport_initializeClass(Class clazz); extern "C" const TypeInfo* Kotlin_ObjCExport_getAssociatedTypeInfo(Class clazz); extern "C" OBJ_GETTER(Kotlin_ObjCExport_convertUnmappedObjCObject, id obj); diff --git a/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm b/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm index 453a3d60d1b..a57ca7fa30f 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm +++ b/kotlin-native/runtime/src/main/cpp/ObjCInterop.mm @@ -126,13 +126,15 @@ void releaseImp(id self, SEL _cmd) { getBackRef(self)->releaseRef(); } -void releaseAsAssociatedObjectImp(id self, SEL _cmd) { +void releaseAsAssociatedObjectImp(id self, SEL _cmd, ReleaseMode mode) { auto* classData = GetKotlinClassData(self); // This function is called by the GC. It made a decision to reclaim Kotlin object, and runs // deallocation hooks at the moment, including deallocation of the "associated object" ([self]) // using the [super release] call below. + auto* backRef = getBackRef(self, classData); + // The deallocation involves running [self dealloc] which can contain arbitrary code. // In particular, this code can retain and release [self]. Obj-C and Swift runtimes handle this // gracefully (unless the object gets accessed after the deallocation of course), but Kotlin doesn't. @@ -141,17 +143,24 @@ void releaseAsAssociatedObjectImp(id self, SEL _cmd) { // Generally retaining and releasing Kotlin object that is being deallocated would lead to // use-after-dispose and double-dispose problems (with unpredictable consequences) or to an assertion failure. // To workaround this, detach the back ref from the Kotlin object: - getBackRef(self, classData)->detach(); + if (ReleaseModeHasDetach(mode)) { + backRef->detach(); + } else { + // With Mark&Sweep this object should already have been detached earlier. + backRef->assertDetached(); + } + // So retain/release/etc. on [self] won't affect the Kotlin object, and an attempt to get // the reference to it (e.g. when calling Kotlin method on [self]) would crash. // The latter is generally ok, because by the time superclass dealloc gets launched, subclass state // should already be deinitialized, and Kotlin methods operate on the subclass. - - // [super release] - Class clazz = classData->objcClass; - struct objc_super s = {self, clazz}; - auto messenger = reinterpret_cast(objc_msgSendSuper2); - messenger(&s, @selector(release)); + if (ReleaseModeHasRelease(mode)) { + // [super release] + Class clazz = classData->objcClass; + struct objc_super s = {self, clazz}; + auto messenger = reinterpret_cast(objc_msgSendSuper2); + messenger(&s, @selector(release)); + } } } diff --git a/kotlin-native/runtime/src/main/cpp/ObjCMMAPI.h b/kotlin-native/runtime/src/main/cpp/ObjCMMAPI.h index 3478e0b0e68..2a8af15a3d4 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCMMAPI.h +++ b/kotlin-native/runtime/src/main/cpp/ObjCMMAPI.h @@ -12,6 +12,8 @@ #if KONAN_OBJC_INTEROP extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject); +extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAndReleaseAssociatedObject(void* associatedObject); +extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAssociatedObject(void* associatedObject); namespace konan { class AutoreleasePool : private kotlin::Pinned { diff --git a/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.cpp b/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.cpp index b0086d7e3b2..9288ad7f774 100644 --- a/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.cpp @@ -65,6 +65,12 @@ void mm::ExtraObjectData::Uninstall(ObjHeader* object) noexcept { delete &data; } +void mm::ExtraObjectData::DetachAssociatedObject() noexcept { +#ifdef KONAN_OBJC_INTEROP + Kotlin_ObjCExport_detachAssociatedObject(associatedObject_); +#endif +} + bool mm::ExtraObjectData::HasWeakReferenceCounter() noexcept { return weakReferenceCounter_ != nullptr; } diff --git a/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.hpp b/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.hpp index b4693ed0305..c17dfafc56f 100644 --- a/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.hpp +++ b/kotlin-native/runtime/src/mm/cpp/ExtraObjectData.hpp @@ -42,6 +42,7 @@ public: #ifdef KONAN_OBJC_INTEROP void** GetAssociatedObjectLocation() noexcept { return &associatedObject_; } #endif + void DetachAssociatedObject() noexcept; ObjHeader** GetWeakCounterLocation() noexcept { return &weakReferenceCounter_; } diff --git a/kotlin-native/runtime/src/mm/cpp/Memory.cpp b/kotlin-native/runtime/src/mm/cpp/Memory.cpp index 050a71b5dfb..e3f66930340 100644 --- a/kotlin-native/runtime/src/mm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/mm/cpp/Memory.cpp @@ -370,6 +370,24 @@ extern "C" RUNTIME_NOTHROW void PerformFullGC(MemoryState* memory) { memory->GetThreadData()->gc().PerformFullGC(); } +extern "C" bool TryAddHeapRef(const ObjHeader* object) { + RuntimeFail("Only for legacy MM"); +} + +extern "C" RUNTIME_NOTHROW void ReleaseHeapRefNoCollect(const ObjHeader* object) { + RuntimeFail("Only for legacy MM"); +} + +extern "C" RUNTIME_NOTHROW OBJ_GETTER(TryRef, ObjHeader* object) { + // TODO: With CMS this needs: + // * during marking phase if `object` is unmarked: barrier (might be automatic because of the stack write) + // and return `object`; + // * during marking phase if `object` is marked: return `object`; + // * during sweeping phase if `object` is unmarked: return nullptr; + // * during sweeping phase if `object` is marked: return `object`; + RETURN_OBJ(object); +} + extern "C" RUNTIME_NOTHROW bool ClearSubgraphReferences(ObjHeader* root, bool checked) { // TODO: Remove when legacy MM is gone. return true; diff --git a/kotlin-native/runtime/src/mm/cpp/Stubs.cpp b/kotlin-native/runtime/src/mm/cpp/Stubs.cpp deleted file mode 100644 index 8316b67221f..00000000000 --- a/kotlin-native/runtime/src/mm/cpp/Stubs.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the LICENSE file. - */ - -#include "Memory.h" - -#include "KAssert.h" - -extern "C" { - -bool TryAddHeapRef(const ObjHeader* object) { - TODO(); -} - -RUNTIME_NOTHROW void ReleaseHeapRef(const ObjHeader* object) { - TODO(); -} - -RUNTIME_NOTHROW void ReleaseHeapRefNoCollect(const ObjHeader* object) { - TODO(); -} - -} // extern "C" diff --git a/kotlin-native/runtime/src/objc/cpp/ObjCExportClasses.mm b/kotlin-native/runtime/src/objc/cpp/ObjCExportClasses.mm index 2562983e36d..563d73fc506 100644 --- a/kotlin-native/runtime/src/objc/cpp/ObjCExportClasses.mm +++ b/kotlin-native/runtime/src/objc/cpp/ObjCExportClasses.mm @@ -98,7 +98,7 @@ static void injectToRuntime(); id old = AtomicCompareAndSwapAssociatedObject(obj, nullptr, candidate); if (old != nullptr) { candidate->refHolder.releaseRef(); - [candidate releaseAsAssociatedObject]; + [candidate releaseAsAssociatedObject:ReleaseMode::kDetachAndRelease]; return objc_retainAutoreleaseReturnValue(old); } } @@ -132,7 +132,7 @@ static void injectToRuntime(); } } --(void)releaseAsAssociatedObject { +-(void)releaseAsAssociatedObject:(ReleaseMode)mode { // This function is called by the GC. It made a decision to reclaim Kotlin object, and runs // deallocation hooks at the moment, including deallocation of the "associated object" ([self]) // using the [super release] call below. @@ -143,13 +143,20 @@ static void injectToRuntime(); // Generally retaining and releasing Kotlin object that is being deallocated would lead to // use-after-dispose and double-dispose problems (with unpredictable consequences) or to an assertion failure. // To workaround this, detach the back ref from the Kotlin object: - refHolder.detach(); + if (ReleaseModeHasDetach(mode)) { + refHolder.detach(); + } else { + // With Mark&Sweep this object should already have been detached earlier. + refHolder.assertDetached(); + } + // So retain/release/etc. on [self] won't affect the Kotlin object, and an attempt to get // the reference to it (e.g. when calling Kotlin method on [self]) would crash. // The latter is generally ok because can be triggered only by user-defined Swift/Obj-C // subclasses of Kotlin classes. - - [super release]; + if (ReleaseModeHasRelease(mode)) { + [super release]; + } } - (instancetype)copyWithZone:(NSZone *)zone { @@ -167,7 +174,9 @@ static void injectToRuntime(); RETURN_RESULT_OF(Kotlin_ObjCExport_convertUnmappedObjCObject, self); } --(void)releaseAsAssociatedObject { +-(void)releaseAsAssociatedObject:(ReleaseMode)mode { + if (!ReleaseModeHasRelease(mode)) + return; objc_release(self); } @end; @@ -240,7 +249,7 @@ static void injectToRuntime() { Kotlin_ObjCExport_toKotlinSelector = @selector(toKotlin:); RuntimeCheck(Kotlin_ObjCExport_releaseAsAssociatedObjectSelector == nullptr, errorMessage); - Kotlin_ObjCExport_releaseAsAssociatedObjectSelector = @selector(releaseAsAssociatedObject); + Kotlin_ObjCExport_releaseAsAssociatedObjectSelector = @selector(releaseAsAssociatedObject:); } #endif // KONAN_OBJC_INTEROP diff --git a/kotlin-native/runtime/src/objc/cpp/ObjCExportCollections.mm b/kotlin-native/runtime/src/objc/cpp/ObjCExportCollections.mm index 10755c8e56d..e1fcdc2aadc 100644 --- a/kotlin-native/runtime/src/objc/cpp/ObjCExportCollections.mm +++ b/kotlin-native/runtime/src/objc/cpp/ObjCExportCollections.mm @@ -32,6 +32,7 @@ #import "Runtime.h" #import "ObjCExport.h" #import "ObjCExportCollections.h" +#import "ObjCExportPrivate.h" extern "C" { @@ -92,7 +93,9 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) { RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSArrayAsKList_create, objc_retain(self)); } --(void)releaseAsAssociatedObject { +-(void)releaseAsAssociatedObject:(ReleaseMode)mode { + if (!ReleaseModeHasRelease(mode)) + return; objc_release(self); } @end; @@ -105,7 +108,9 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) { RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSMutableArrayAsKMutableList_create, objc_retain(self)); } --(void)releaseAsAssociatedObject { +-(void)releaseAsAssociatedObject:(ReleaseMode)mode { + if (!ReleaseModeHasRelease(mode)) + return; objc_release(self); } @end; @@ -119,7 +124,9 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) { RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSSetAsKSet_create, objc_retain(self)); } --(void)releaseAsAssociatedObject { +-(void)releaseAsAssociatedObject:(ReleaseMode)mode { + if (!ReleaseModeHasRelease(mode)) + return; objc_release(self); } @@ -133,7 +140,9 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) { RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSDictionaryAsKMap_create, objc_retain(self)); } --(void)releaseAsAssociatedObject { +-(void)releaseAsAssociatedObject:(ReleaseMode)mode { + if (!ReleaseModeHasRelease(mode)) + return; objc_release(self); } @@ -560,7 +569,9 @@ static inline id KMap_get(KRef map, id aKey) { @end; @implementation NSEnumerator (NSEnumeratorAsAssociatedObject) --(void)releaseAsAssociatedObject { +-(void)releaseAsAssociatedObject:(ReleaseMode)mode { + if (!ReleaseModeHasRelease(mode)) + return; objc_release(self); } @end; diff --git a/kotlin-native/runtime/src/objc/cpp/ObjCInteropUtilsClasses.mm b/kotlin-native/runtime/src/objc/cpp/ObjCInteropUtilsClasses.mm index 182c730e713..1a4ac4214ee 100644 --- a/kotlin-native/runtime/src/objc/cpp/ObjCInteropUtilsClasses.mm +++ b/kotlin-native/runtime/src/objc/cpp/ObjCInteropUtilsClasses.mm @@ -9,6 +9,7 @@ #import #import "Memory.h" #import "MemorySharedRefs.hpp" +#import "ObjCExportPrivate.h" #import "ObjCInteropUtilsPrivate.h" // TODO: rework the interface to reduce the number of virtual calls @@ -73,7 +74,9 @@ void objc_release(id obj); } // Called when removing Kotlin object. --(void)releaseAsAssociatedObject { +-(void)releaseAsAssociatedObject:(ReleaseMode)mode { + if (!ReleaseModeHasRelease(mode)) + return; objc_destroyWeak(&referred); objc_release(self); }