Alexander Shabalin
2021-05-27 17:18:55 +00:00
committed by Space
parent 0b1884c994
commit af1f57007a
23 changed files with 190 additions and 75 deletions
@@ -101,7 +101,7 @@ private fun PatchBuilder.addObjCPatches() {
addProtocolImport("NSCopying") addProtocolImport("NSCopying")
addPrivateSelector("toKotlin:") addPrivateSelector("toKotlin:")
addPrivateSelector("releaseAsAssociatedObject") addPrivateSelector("releaseAsAssociatedObject:")
addPrivateClass("KIteratorAsNSEnumerator", "iteratorHolder") addPrivateClass("KIteratorAsNSEnumerator", "iteratorHolder")
addPrivateClass("KListAsNSArray", "listHolder") addPrivateClass("KListAsNSArray", "listHolder")
@@ -4316,7 +4316,7 @@ if (PlatformInfo.isAppleTarget(project)) {
} }
interopTestMultifile("interop_objc_tests") { 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/" source = "interop/objc/tests/"
interop = 'objcTests' interop = 'objcTests'
flags = ['-tr', '-e', 'main'] flags = ['-tr', '-e', 'main']
@@ -4787,7 +4787,7 @@ Task frameworkTest(String name, Closure<FrameworkTest> configurator) {
if (isAppleTarget(project)) { if (isAppleTarget(project)) {
frameworkTest('testObjCExport') { 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 frameworkName = 'Kt'
final String dir = "$testOutputFramework/testObjCExport" final String dir = "$testOutputFramework/testObjCExport"
final File lazyHeader = file("$dir/$target-lazy.h") final File lazyHeader = file("$dir/$target-lazy.h")
@@ -4836,7 +4836,7 @@ if (isAppleTarget(project)) {
} }
frameworkTest('testObjCExportNoGenerics') { 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 frameworkName = 'KtNoGenerics'
final String frameworkArtifactName = 'Kt' final String frameworkArtifactName = 'Kt'
final String dir = "$testOutputFramework/testObjCExportNoGenerics" final String dir = "$testOutputFramework/testObjCExportNoGenerics"
@@ -4889,7 +4889,7 @@ if (isAppleTarget(project)) {
} }
frameworkTest('testObjCExportStatic') { 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 frameworkName = 'KtStatic'
final String frameworkArtifactName = 'Kt' final String frameworkArtifactName = 'Kt'
final String libraryName = frameworkName + "Library" final String libraryName = frameworkName + "Library"
@@ -2312,6 +2312,7 @@ __attribute__((swift_name("ValuesKt")))
@property (class) id _Nullable warningVar __attribute__((swift_name("warningVar"))) __attribute__((deprecated("warning"))); @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 gh3525BaseInitCount __attribute__((swift_name("gh3525BaseInitCount")));
@property (class) int32_t gh3525InitCount __attribute__((swift_name("gh3525InitCount"))); @property (class) int32_t gh3525InitCount __attribute__((swift_name("gh3525InitCount")));
@property (class, readonly) BOOL isExperimentalMM __attribute__((swift_name("isExperimentalMM")));
@end; @end;
__attribute__((swift_name("InvariantSuper"))) __attribute__((swift_name("InvariantSuper")))
@@ -2285,6 +2285,7 @@ __attribute__((swift_name("ValuesKt")))
@property (class) id _Nullable warningVar __attribute__((swift_name("warningVar"))) __attribute__((deprecated("warning"))); @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 gh3525BaseInitCount __attribute__((swift_name("gh3525BaseInitCount")));
@property (class) int32_t gh3525InitCount __attribute__((swift_name("gh3525InitCount"))); @property (class) int32_t gh3525InitCount __attribute__((swift_name("gh3525InitCount")));
@property (class, readonly) BOOL isExperimentalMM __attribute__((swift_name("isExperimentalMM")));
@end; @end;
__attribute__((swift_name("InvariantSuper"))) __attribute__((swift_name("InvariantSuper")))
@@ -1020,4 +1020,7 @@ interface Bar_FakeOverrideInInterface : Foo_FakeOverrideInInterface<String>
fun callFoo_FakeOverrideInInterface(obj: Bar_FakeOverrideInInterface) { fun callFoo_FakeOverrideInInterface(obj: Bar_FakeOverrideInInterface) {
obj.foo(null) obj.foo(null)
} }
val isExperimentalMM: Boolean
get() = kotlin.native.Platform.memoryModel == kotlin.native.MemoryModel.EXPERIMENTAL
@@ -596,10 +596,17 @@ func testShared() throws {
try assertFalse(ValuesKt.isFrozen(obj: obj), "isFrozen(\(obj))") try assertFalse(ValuesKt.isFrozen(obj: obj), "isFrozen(\(obj))")
} }
try assertFrozen(NSObject()) if ValuesKt.isExperimentalMM {
try assertFrozen(TestSharedIImpl()) try assertNotFrozen(NSObject())
try assertFrozen(ValuesKt.kotlinLambda(block: { return $0 }) as AnyObject) try assertNotFrozen(TestSharedIImpl())
try assertNotFrozen(FinalClassExtOpen()) 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 { class PureSwiftClass {
@@ -1411,4 +1418,4 @@ class ValuesTests : SimpleTestProvider {
// Stress test, must remain the last one: // Stress test, must remain the last one:
test("TestGH2931", testGH2931) test("TestGH2931", testGH2931)
} }
} }
@@ -62,6 +62,7 @@ typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFact
auto* objHeader = it->IsArray() ? it->GetArrayHeader()->obj() : it->GetObjHeader(); auto* objHeader = it->IsArray() ? it->GetArrayHeader()->obj() : it->GetObjHeader();
if (auto* extraObject = mm::ExtraObjectData::Get(objHeader)) { if (auto* extraObject = mm::ExtraObjectData::Get(objHeader)) {
extraObject->ClearWeakReferenceCounter(); extraObject->ClearWeakReferenceCounter();
extraObject->DetachAssociatedObject();
} }
if (HasFinalizers(objHeader)) { if (HasFinalizers(objHeader)) {
iter.MoveAndAdvance(finalizerQueue, it); iter.MoveAndAdvance(finalizerQueue, it);
@@ -3137,7 +3137,7 @@ void ObjHeader::destroyMetaObject(ObjHeader* object) {
} }
#ifdef KONAN_OBJC_INTEROP #ifdef KONAN_OBJC_INTEROP
Kotlin_ObjCExport_releaseAssociatedObject(meta->associatedObject_); Kotlin_ObjCExport_detachAndReleaseAssociatedObject(meta->associatedObject_);
#endif #endif
konanFreeMemory(meta); konanFreeMemory(meta);
@@ -3286,6 +3286,10 @@ RUNTIME_NOTHROW void ReleaseHeapRefNoCollectRelaxed(const ObjHeader* object) {
releaseHeapRef<false, /* CanCollect = */ false>(const_cast<ObjHeader*>(object)); releaseHeapRef<false, /* CanCollect = */ false>(const_cast<ObjHeader*>(object));
} }
RUNTIME_NOTHROW OBJ_GETTER(TryRef, ObjHeader* object) {
RuntimeFail("Only for experimental MM");
}
ForeignRefContext InitLocalForeignRef(ObjHeader* object) { ForeignRefContext InitLocalForeignRef(ObjHeader* object) {
return initLocalForeignRef(object); return initLocalForeignRef(object);
} }
@@ -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, EnterFrame, ObjHeader** start, int parameters, int count);
MODEL_VARIANTS(void, LeaveFrame, 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, ReleaseHeapRef, const ObjHeader* object);
MODEL_VARIANTS(void, ReleaseHeapRefNoCollect, const ObjHeader* object); MODEL_VARIANTS(void, ReleaseHeapRefNoCollect, const ObjHeader* object);
+4 -2
View File
@@ -298,11 +298,13 @@ bool Kotlin_Any_isShareable(ObjHeader* thiz);
void Kotlin_Any_share(ObjHeader* thiz); void Kotlin_Any_share(ObjHeader* thiz);
void PerformFullGC(MemoryState* memory) RUNTIME_NOTHROW; void PerformFullGC(MemoryState* memory) RUNTIME_NOTHROW;
// Only for legacy
bool TryAddHeapRef(const ObjHeader* object); bool TryAddHeapRef(const ObjHeader* object);
void ReleaseHeapRef(const ObjHeader* object) RUNTIME_NOTHROW;
void ReleaseHeapRefNoCollect(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 InitLocalForeignRef(ObjHeader* object);
ForeignRefContext InitForeignRef(ObjHeader* object); ForeignRefContext InitForeignRef(ObjHeader* object);
@@ -146,19 +146,30 @@ bool BackRefFromAssociatedObject::tryAddRef() {
if (obj_ == nullptr) return false; // E.g. after [detach]. if (obj_ == nullptr) return false; // E.g. after [detach].
// Suboptimal but simple: if (CurrentMemoryModel == MemoryModel::kExperimental) {
ensureRefAccessible<errorPolicy>(obj_, context_); 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<ErrorPolicy::kIgnore>();
return true;
} else {
// Suboptimal but simple:
ensureRefAccessible<errorPolicy>(obj_, context_);
ObjHeader* obj = obj_; ObjHeader* obj = obj_;
if (!TryAddHeapRef(obj)) return false; if (!TryAddHeapRef(obj)) return false;
RuntimeAssert(isForeignRefAccessible(obj_, context_), "Cannot be inaccessible because of the check above"); RuntimeAssert(isForeignRefAccessible(obj_, context_), "Cannot be inaccessible because of the check above");
// TODO: This is a very weird way to ask for "unsafe" addRef. // TODO: This is a very weird way to ask for "unsafe" addRef.
addRef<ErrorPolicy::kIgnore>(); addRef<ErrorPolicy::kIgnore>();
ReleaseHeapRefNoCollect(obj); // Balance TryAddHeapRef. ReleaseHeapRefNoCollect(obj); // Balance TryAddHeapRef.
// TODO: consider optimizing for non-shared objects. // TODO: consider optimizing for non-shared objects.
return true; return true;
}
} }
template bool BackRefFromAssociatedObject::tryAddRef<ErrorPolicy::kThrow>(); template bool BackRefFromAssociatedObject::tryAddRef<ErrorPolicy::kThrow>();
@@ -183,6 +194,10 @@ void BackRefFromAssociatedObject::detach() {
obj_ = nullptr; // Handled in addRef/tryAddRef/releaseRef/ref. 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 <ErrorPolicy errorPolicy> template <ErrorPolicy errorPolicy>
ObjHeader* BackRefFromAssociatedObject::ref() const { ObjHeader* BackRefFromAssociatedObject::ref() const {
RuntimeAssert(obj_ != nullptr, "no valid Kotlin object found"); RuntimeAssert(obj_ != nullptr, "no valid Kotlin object found");
@@ -54,6 +54,7 @@ class BackRefFromAssociatedObject {
void releaseRef(); void releaseRef();
void detach(); void detach();
void assertDetached();
// Error if called from the wrong worker with non-frozen obj_. // Error if called from the wrong worker with non-frozen obj_.
template <ErrorPolicy errorPolicy> template <ErrorPolicy errorPolicy>
@@ -123,13 +123,29 @@ static Class getOrCreateClass(const TypeInfo* typeInfo);
extern "C" id objc_retainAutoreleaseReturnValue(id self); 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) { if (associatedObject != nullptr) {
auto msgSend = reinterpret_cast<void (*)(void* self, SEL cmd)>(&objc_msgSend); auto msgSend = reinterpret_cast<void (*)(void* self, SEL cmd, ReleaseMode mode)>(&objc_msgSend);
msgSend(associatedObject, Kotlin_ObjCExport_releaseAsAssociatedObjectSelector); 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) { extern "C" id Kotlin_ObjCExport_convertUnit(ObjHeader* unitInstance) {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
static id instance = nullptr; 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(boxedBooleanToKotlinImp, NSNumber* self, SEL cmd);
static OBJ_GETTER(SwiftObject_toKotlinImp, id 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) { static void initTypeAdaptersFrom(const ObjCTypeAdapter** adapters, int count) {
for (int index = 0; index < count; ++index) { for (int index = 0; index < count; ++index) {
@@ -359,7 +375,7 @@ static void Kotlin_ObjCExport_initializeImpl() {
swiftRootClass, releaseAsAssociatedObjectSelector, swiftRootClass, releaseAsAssociatedObjectSelector,
(IMP)SwiftObject_releaseAsAssociatedObjectImp, releaseAsAssociatedObjectTypeEncoding (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); 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); objc_release(self);
} }
@@ -18,6 +18,32 @@
+(instancetype)createWrapper:(ObjHeader*)obj; +(instancetype)createWrapper:(ObjHeader*)obj;
@end; @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" void Kotlin_ObjCExport_initializeClass(Class clazz);
extern "C" const TypeInfo* Kotlin_ObjCExport_getAssociatedTypeInfo(Class clazz); extern "C" const TypeInfo* Kotlin_ObjCExport_getAssociatedTypeInfo(Class clazz);
extern "C" OBJ_GETTER(Kotlin_ObjCExport_convertUnmappedObjCObject, id obj); extern "C" OBJ_GETTER(Kotlin_ObjCExport_convertUnmappedObjCObject, id obj);
@@ -126,13 +126,15 @@ void releaseImp(id self, SEL _cmd) {
getBackRef(self)->releaseRef(); getBackRef(self)->releaseRef();
} }
void releaseAsAssociatedObjectImp(id self, SEL _cmd) { void releaseAsAssociatedObjectImp(id self, SEL _cmd, ReleaseMode mode) {
auto* classData = GetKotlinClassData(self); auto* classData = GetKotlinClassData(self);
// This function is called by the GC. It made a decision to reclaim Kotlin object, and runs // 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]) // deallocation hooks at the moment, including deallocation of the "associated object" ([self])
// using the [super release] call below. // using the [super release] call below.
auto* backRef = getBackRef(self, classData);
// The deallocation involves running [self dealloc] which can contain arbitrary code. // 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 // 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. // 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 // 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. // 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: // 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 // 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 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 // 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. // should already be deinitialized, and Kotlin methods operate on the subclass.
if (ReleaseModeHasRelease(mode)) {
// [super release] // [super release]
Class clazz = classData->objcClass; Class clazz = classData->objcClass;
struct objc_super s = {self, clazz}; struct objc_super s = {self, clazz};
auto messenger = reinterpret_cast<void (*) (struct objc_super*, SEL _cmd)>(objc_msgSendSuper2); auto messenger = reinterpret_cast<void (*) (struct objc_super*, SEL _cmd)>(objc_msgSendSuper2);
messenger(&s, @selector(release)); messenger(&s, @selector(release));
}
} }
} }
@@ -12,6 +12,8 @@
#if KONAN_OBJC_INTEROP #if KONAN_OBJC_INTEROP
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject); 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 { namespace konan {
class AutoreleasePool : private kotlin::Pinned { class AutoreleasePool : private kotlin::Pinned {
@@ -65,6 +65,12 @@ void mm::ExtraObjectData::Uninstall(ObjHeader* object) noexcept {
delete &data; delete &data;
} }
void mm::ExtraObjectData::DetachAssociatedObject() noexcept {
#ifdef KONAN_OBJC_INTEROP
Kotlin_ObjCExport_detachAssociatedObject(associatedObject_);
#endif
}
bool mm::ExtraObjectData::HasWeakReferenceCounter() noexcept { bool mm::ExtraObjectData::HasWeakReferenceCounter() noexcept {
return weakReferenceCounter_ != nullptr; return weakReferenceCounter_ != nullptr;
} }
@@ -42,6 +42,7 @@ public:
#ifdef KONAN_OBJC_INTEROP #ifdef KONAN_OBJC_INTEROP
void** GetAssociatedObjectLocation() noexcept { return &associatedObject_; } void** GetAssociatedObjectLocation() noexcept { return &associatedObject_; }
#endif #endif
void DetachAssociatedObject() noexcept;
ObjHeader** GetWeakCounterLocation() noexcept { return &weakReferenceCounter_; } ObjHeader** GetWeakCounterLocation() noexcept { return &weakReferenceCounter_; }
@@ -370,6 +370,24 @@ extern "C" RUNTIME_NOTHROW void PerformFullGC(MemoryState* memory) {
memory->GetThreadData()->gc().PerformFullGC(); 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) { extern "C" RUNTIME_NOTHROW bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
// TODO: Remove when legacy MM is gone. // TODO: Remove when legacy MM is gone.
return true; return true;
@@ -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"
@@ -98,7 +98,7 @@ static void injectToRuntime();
id old = AtomicCompareAndSwapAssociatedObject(obj, nullptr, candidate); id old = AtomicCompareAndSwapAssociatedObject(obj, nullptr, candidate);
if (old != nullptr) { if (old != nullptr) {
candidate->refHolder.releaseRef(); candidate->refHolder.releaseRef();
[candidate releaseAsAssociatedObject]; [candidate releaseAsAssociatedObject:ReleaseMode::kDetachAndRelease];
return objc_retainAutoreleaseReturnValue(old); 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 // 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]) // deallocation hooks at the moment, including deallocation of the "associated object" ([self])
// using the [super release] call below. // 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 // 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. // 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: // 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 // 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 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 // The latter is generally ok because can be triggered only by user-defined Swift/Obj-C
// subclasses of Kotlin classes. // subclasses of Kotlin classes.
if (ReleaseModeHasRelease(mode)) {
[super release]; [super release];
}
} }
- (instancetype)copyWithZone:(NSZone *)zone { - (instancetype)copyWithZone:(NSZone *)zone {
@@ -167,7 +174,9 @@ static void injectToRuntime();
RETURN_RESULT_OF(Kotlin_ObjCExport_convertUnmappedObjCObject, self); RETURN_RESULT_OF(Kotlin_ObjCExport_convertUnmappedObjCObject, self);
} }
-(void)releaseAsAssociatedObject { -(void)releaseAsAssociatedObject:(ReleaseMode)mode {
if (!ReleaseModeHasRelease(mode))
return;
objc_release(self); objc_release(self);
} }
@end; @end;
@@ -240,7 +249,7 @@ static void injectToRuntime() {
Kotlin_ObjCExport_toKotlinSelector = @selector(toKotlin:); Kotlin_ObjCExport_toKotlinSelector = @selector(toKotlin:);
RuntimeCheck(Kotlin_ObjCExport_releaseAsAssociatedObjectSelector == nullptr, errorMessage); RuntimeCheck(Kotlin_ObjCExport_releaseAsAssociatedObjectSelector == nullptr, errorMessage);
Kotlin_ObjCExport_releaseAsAssociatedObjectSelector = @selector(releaseAsAssociatedObject); Kotlin_ObjCExport_releaseAsAssociatedObjectSelector = @selector(releaseAsAssociatedObject:);
} }
#endif // KONAN_OBJC_INTEROP #endif // KONAN_OBJC_INTEROP
@@ -32,6 +32,7 @@
#import "Runtime.h" #import "Runtime.h"
#import "ObjCExport.h" #import "ObjCExport.h"
#import "ObjCExportCollections.h" #import "ObjCExportCollections.h"
#import "ObjCExportPrivate.h"
extern "C" { extern "C" {
@@ -92,7 +93,9 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) {
RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSArrayAsKList_create, objc_retain(self)); RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSArrayAsKList_create, objc_retain(self));
} }
-(void)releaseAsAssociatedObject { -(void)releaseAsAssociatedObject:(ReleaseMode)mode {
if (!ReleaseModeHasRelease(mode))
return;
objc_release(self); objc_release(self);
} }
@end; @end;
@@ -105,7 +108,9 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) {
RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSMutableArrayAsKMutableList_create, objc_retain(self)); RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSMutableArrayAsKMutableList_create, objc_retain(self));
} }
-(void)releaseAsAssociatedObject { -(void)releaseAsAssociatedObject:(ReleaseMode)mode {
if (!ReleaseModeHasRelease(mode))
return;
objc_release(self); objc_release(self);
} }
@end; @end;
@@ -119,7 +124,9 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) {
RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSSetAsKSet_create, objc_retain(self)); RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSSetAsKSet_create, objc_retain(self));
} }
-(void)releaseAsAssociatedObject { -(void)releaseAsAssociatedObject:(ReleaseMode)mode {
if (!ReleaseModeHasRelease(mode))
return;
objc_release(self); objc_release(self);
} }
@@ -133,7 +140,9 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) {
RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSDictionaryAsKMap_create, objc_retain(self)); RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSDictionaryAsKMap_create, objc_retain(self));
} }
-(void)releaseAsAssociatedObject { -(void)releaseAsAssociatedObject:(ReleaseMode)mode {
if (!ReleaseModeHasRelease(mode))
return;
objc_release(self); objc_release(self);
} }
@@ -560,7 +569,9 @@ static inline id KMap_get(KRef map, id aKey) {
@end; @end;
@implementation NSEnumerator (NSEnumeratorAsAssociatedObject) @implementation NSEnumerator (NSEnumeratorAsAssociatedObject)
-(void)releaseAsAssociatedObject { -(void)releaseAsAssociatedObject:(ReleaseMode)mode {
if (!ReleaseModeHasRelease(mode))
return;
objc_release(self); objc_release(self);
} }
@end; @end;
@@ -9,6 +9,7 @@
#import <Foundation/NSObject.h> #import <Foundation/NSObject.h>
#import "Memory.h" #import "Memory.h"
#import "MemorySharedRefs.hpp" #import "MemorySharedRefs.hpp"
#import "ObjCExportPrivate.h"
#import "ObjCInteropUtilsPrivate.h" #import "ObjCInteropUtilsPrivate.h"
// TODO: rework the interface to reduce the number of virtual calls // 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. // Called when removing Kotlin object.
-(void)releaseAsAssociatedObject { -(void)releaseAsAssociatedObject:(ReleaseMode)mode {
if (!ReleaseModeHasRelease(mode))
return;
objc_destroyWeak(&referred); objc_destroyWeak(&referred);
objc_release(self); objc_release(self);
} }