Native: add tests for autorelease optimization in ObjCExport

The compiler should do its best to avoid putting objects to autorelease
pools.
This commit is contained in:
Svyatoslav Scherbina
2021-08-10 18:08:24 +03:00
committed by Space
parent 8c923f6504
commit b32ab48596
4 changed files with 839 additions and 0 deletions
@@ -923,6 +923,116 @@ __attribute__((swift_name("ArraysInitBlock")))
- (NSString *)log __attribute__((swift_name("log()")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("KotlinLivenessTracker")))
@interface KtKotlinLivenessTracker : KtBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (void)addObj:(id _Nullable)obj __attribute__((swift_name("add(obj:)")));
- (BOOL)isEmpty __attribute__((swift_name("isEmpty()")));
- (BOOL)objectsAreDead __attribute__((swift_name("objectsAreDead()")));
@property (readonly) NSMutableArray<KtKotlinWeakReference<id> *> *weakRefs __attribute__((swift_name("weakRefs")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("KotlinObject")))
@interface KtKotlinObject : KtBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end;
__attribute__((swift_name("NoAutoreleaseSendHelper")))
@protocol KtNoAutoreleaseSendHelper
@required
- (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)")));
- (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)")));
- (void)sendListList:(NSArray<id> *)list __attribute__((swift_name("sendList(list:)")));
- (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)")));
- (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)")));
- (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)")));
@end;
__attribute__((swift_name("NoAutoreleaseReceiveHelper")))
@protocol KtNoAutoreleaseReceiveHelper
@required
- (KtKotlinObject *)receiveKotlinObject __attribute__((swift_name("receiveKotlinObject()")));
- (id)receiveSwiftObject __attribute__((swift_name("receiveSwiftObject()")));
- (NSArray<id> *)receiveList __attribute__((swift_name("receiveList()")));
- (NSString *)receiveString __attribute__((swift_name("receiveString()")));
- (id)receiveNumber __attribute__((swift_name("receiveNumber()")));
- (KtKotlinObject *(^)(void))receiveBlock __attribute__((swift_name("receiveBlock()")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseKotlinSendHelper")))
@interface KtNoAutoreleaseKotlinSendHelper : KtBase <KtNoAutoreleaseSendHelper>
- (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer));
- (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)")));
- (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)")));
- (void)sendListList:(NSArray<id> *)list __attribute__((swift_name("sendList(list:)")));
- (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)")));
- (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)")));
- (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)")));
@property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseKotlinReceiveHelper")))
@interface KtNoAutoreleaseKotlinReceiveHelper : KtBase <KtNoAutoreleaseReceiveHelper>
- (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer));
- (KtKotlinObject *)receiveKotlinObject __attribute__((swift_name("receiveKotlinObject()")));
- (id)receiveSwiftObject __attribute__((swift_name("receiveSwiftObject()")));
- (NSArray<id> *)receiveList __attribute__((swift_name("receiveList()")));
- (NSString *)receiveString __attribute__((swift_name("receiveString()")));
- (id)receiveNumber __attribute__((swift_name("receiveNumber()")));
- (KtKotlinObject *(^)(void))receiveBlock __attribute__((swift_name("receiveBlock()")));
@property id swiftObject __attribute__((swift_name("swiftObject")));
@property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseSingleton")))
@interface KtNoAutoreleaseSingleton : KtBase
+ (instancetype)alloc __attribute__((unavailable));
+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
+ (instancetype)noAutoreleaseSingleton __attribute__((swift_name("init()")));
@property (class, readonly, getter=shared) KtNoAutoreleaseSingleton *shared __attribute__((swift_name("shared")));
@property (readonly) int32_t x __attribute__((swift_name("x")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseEnum")))
@interface KtNoAutoreleaseEnum : KtKotlinEnum<KtNoAutoreleaseEnum *>
+ (instancetype)alloc __attribute__((unavailable));
+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
- (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
@property (class, readonly) KtNoAutoreleaseEnum *entry __attribute__((swift_name("entry")));
+ (KtKotlinArray<KtNoAutoreleaseEnum *> *)values __attribute__((swift_name("values()")));
@property (readonly) int32_t x __attribute__((swift_name("x")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseKt")))
@interface KtNoAutoreleaseKt : KtBase
+ (void)gc __attribute__((swift_name("gc()")));
+ (void)callSendKotlinObjectHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendKotlinObject(helper:tracker:)")));
+ (void)callSendSwiftObjectHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker swiftObject:(id)swiftObject __attribute__((swift_name("callSendSwiftObject(helper:tracker:swiftObject:)")));
+ (void)callSendListHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendList(helper:tracker:)")));
+ (void)callSendStringHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendString(helper:tracker:)")));
+ (void)callSendNumberHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendNumber(helper:tracker:)")));
+ (void)callSendBlockHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendBlock(helper:tracker:)")));
+ (void)callReceiveKotlinObjectHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveKotlinObject(helper:tracker:)")));
+ (void)callReceiveSwiftObjectHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveSwiftObject(helper:tracker:)")));
+ (void)callReceiveListHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveList(helper:tracker:)")));
+ (void)callReceiveStringHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveString(helper:tracker:)")));
+ (void)callReceiveNumberHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveNumber(helper:tracker:)")));
+ (void)callReceiveBlockHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveBlock(helper:tracker:)")));
+ (void)callReceiveBlockAndCallHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveBlockAndCall(helper:tracker:)")));
+ (void * _Nullable)objc_autoreleasePoolPush __attribute__((swift_name("objc_autoreleasePoolPush()")));
+ (void)objc_autoreleasePoolPopHandle:(void * _Nullable)handle __attribute__((swift_name("objc_autoreleasePoolPop(handle:)")));
+ (void)useIntArrayArray:(KtKotlinIntArray *)array __attribute__((swift_name("useIntArray(array:)")));
@end;
__attribute__((swift_name("OverrideKotlinMethods2")))
@protocol KtOverrideKotlinMethods2
@required
@@ -865,6 +865,116 @@ __attribute__((swift_name("ArraysInitBlock")))
- (NSString *)log __attribute__((swift_name("log()")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("KotlinLivenessTracker")))
@interface KtKotlinLivenessTracker : KtBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (void)addObj:(id _Nullable)obj __attribute__((swift_name("add(obj:)")));
- (BOOL)isEmpty __attribute__((swift_name("isEmpty()")));
- (BOOL)objectsAreDead __attribute__((swift_name("objectsAreDead()")));
@property (readonly) NSMutableArray<KtKotlinWeakReference *> *weakRefs __attribute__((swift_name("weakRefs")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("KotlinObject")))
@interface KtKotlinObject : KtBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end;
__attribute__((swift_name("NoAutoreleaseSendHelper")))
@protocol KtNoAutoreleaseSendHelper
@required
- (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)")));
- (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)")));
- (void)sendListList:(NSArray<id> *)list __attribute__((swift_name("sendList(list:)")));
- (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)")));
- (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)")));
- (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)")));
@end;
__attribute__((swift_name("NoAutoreleaseReceiveHelper")))
@protocol KtNoAutoreleaseReceiveHelper
@required
- (KtKotlinObject *)receiveKotlinObject __attribute__((swift_name("receiveKotlinObject()")));
- (id)receiveSwiftObject __attribute__((swift_name("receiveSwiftObject()")));
- (NSArray<id> *)receiveList __attribute__((swift_name("receiveList()")));
- (NSString *)receiveString __attribute__((swift_name("receiveString()")));
- (id)receiveNumber __attribute__((swift_name("receiveNumber()")));
- (KtKotlinObject *(^)(void))receiveBlock __attribute__((swift_name("receiveBlock()")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseKotlinSendHelper")))
@interface KtNoAutoreleaseKotlinSendHelper : KtBase <KtNoAutoreleaseSendHelper>
- (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer));
- (void)sendKotlinObjectKotlinObject:(KtKotlinObject *)kotlinObject __attribute__((swift_name("sendKotlinObject(kotlinObject:)")));
- (void)sendSwiftObjectSwiftObject:(id)swiftObject __attribute__((swift_name("sendSwiftObject(swiftObject:)")));
- (void)sendListList:(NSArray<id> *)list __attribute__((swift_name("sendList(list:)")));
- (void)sendStringString:(NSString *)string __attribute__((swift_name("sendString(string:)")));
- (void)sendNumberNumber:(id)number __attribute__((swift_name("sendNumber(number:)")));
- (void)sendBlockBlock:(KtKotlinObject *(^)(void))block __attribute__((swift_name("sendBlock(block:)")));
@property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseKotlinReceiveHelper")))
@interface KtNoAutoreleaseKotlinReceiveHelper : KtBase <KtNoAutoreleaseReceiveHelper>
- (instancetype)initWithKotlinLivenessTracker:(KtKotlinLivenessTracker *)kotlinLivenessTracker __attribute__((swift_name("init(kotlinLivenessTracker:)"))) __attribute__((objc_designated_initializer));
- (KtKotlinObject *)receiveKotlinObject __attribute__((swift_name("receiveKotlinObject()")));
- (id)receiveSwiftObject __attribute__((swift_name("receiveSwiftObject()")));
- (NSArray<id> *)receiveList __attribute__((swift_name("receiveList()")));
- (NSString *)receiveString __attribute__((swift_name("receiveString()")));
- (id)receiveNumber __attribute__((swift_name("receiveNumber()")));
- (KtKotlinObject *(^)(void))receiveBlock __attribute__((swift_name("receiveBlock()")));
@property id swiftObject __attribute__((swift_name("swiftObject")));
@property (readonly) KtKotlinLivenessTracker *kotlinLivenessTracker __attribute__((swift_name("kotlinLivenessTracker")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseSingleton")))
@interface KtNoAutoreleaseSingleton : KtBase
+ (instancetype)alloc __attribute__((unavailable));
+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
+ (instancetype)noAutoreleaseSingleton __attribute__((swift_name("init()")));
@property (class, readonly, getter=shared) KtNoAutoreleaseSingleton *shared __attribute__((swift_name("shared")));
@property (readonly) int32_t x __attribute__((swift_name("x")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseEnum")))
@interface KtNoAutoreleaseEnum : KtKotlinEnum
+ (instancetype)alloc __attribute__((unavailable));
+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
- (instancetype)initWithName:(NSString *)name ordinal:(int32_t)ordinal __attribute__((swift_name("init(name:ordinal:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
@property (class, readonly) KtNoAutoreleaseEnum *entry __attribute__((swift_name("entry")));
+ (KtKotlinArray *)values __attribute__((swift_name("values()")));
@property (readonly) int32_t x __attribute__((swift_name("x")));
@end;
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("NoAutoreleaseKt")))
@interface KtNoAutoreleaseKt : KtBase
+ (void)gc __attribute__((swift_name("gc()")));
+ (void)callSendKotlinObjectHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendKotlinObject(helper:tracker:)")));
+ (void)callSendSwiftObjectHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker swiftObject:(id)swiftObject __attribute__((swift_name("callSendSwiftObject(helper:tracker:swiftObject:)")));
+ (void)callSendListHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendList(helper:tracker:)")));
+ (void)callSendStringHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendString(helper:tracker:)")));
+ (void)callSendNumberHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendNumber(helper:tracker:)")));
+ (void)callSendBlockHelper:(id<KtNoAutoreleaseSendHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callSendBlock(helper:tracker:)")));
+ (void)callReceiveKotlinObjectHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveKotlinObject(helper:tracker:)")));
+ (void)callReceiveSwiftObjectHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveSwiftObject(helper:tracker:)")));
+ (void)callReceiveListHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveList(helper:tracker:)")));
+ (void)callReceiveStringHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveString(helper:tracker:)")));
+ (void)callReceiveNumberHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveNumber(helper:tracker:)")));
+ (void)callReceiveBlockHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveBlock(helper:tracker:)")));
+ (void)callReceiveBlockAndCallHelper:(id<KtNoAutoreleaseReceiveHelper>)helper tracker:(KtKotlinLivenessTracker *)tracker __attribute__((swift_name("callReceiveBlockAndCall(helper:tracker:)")));
+ (void * _Nullable)objc_autoreleasePoolPush __attribute__((swift_name("objc_autoreleasePoolPush()")));
+ (void)objc_autoreleasePoolPopHandle:(void * _Nullable)handle __attribute__((swift_name("objc_autoreleasePoolPop(handle:)")));
+ (void)useIntArrayArray:(KtKotlinIntArray *)array __attribute__((swift_name("useIntArray(array:)")));
@end;
__attribute__((swift_name("OverrideKotlinMethods2")))
@protocol KtOverrideKotlinMethods2
@required
@@ -0,0 +1,161 @@
package noAutorelease
import kotlin.native.internal.NativePtr
import kotlin.native.ref.WeakReference
import kotlin.test.*
class KotlinLivenessTracker {
val weakRefs = mutableListOf<WeakReference<Any>>()
fun add(obj: Any?) {
assertNotNull(obj)
weakRefs += WeakReference(obj)
assertFalse(objectsAreDead())
}
fun isEmpty() = weakRefs.isEmpty()
fun objectsAreDead() = weakRefs.all { it.value === null }
}
fun gc() = kotlin.native.internal.GC.collect()
class KotlinObject
interface NoAutoreleaseSendHelper {
fun sendKotlinObject(kotlinObject: KotlinObject)
fun sendSwiftObject(swiftObject: Any)
fun sendList(list: List<*>)
fun sendString(string: String)
fun sendNumber(number: Any)
fun sendBlock(block: () -> KotlinObject)
}
interface NoAutoreleaseReceiveHelper {
fun receiveKotlinObject(): KotlinObject
fun receiveSwiftObject(): Any
fun receiveList(): List<*>
fun receiveString(): String
fun receiveNumber(): Any
fun receiveBlock(): () -> KotlinObject
}
class NoAutoreleaseKotlinSendHelper(val kotlinLivenessTracker: KotlinLivenessTracker) : NoAutoreleaseSendHelper {
override fun sendKotlinObject(kotlinObject: KotlinObject) = kotlinLivenessTracker.add(kotlinObject)
override fun sendSwiftObject(swiftObject: Any) = kotlinLivenessTracker.add(swiftObject)
override fun sendList(list: List<*>) = kotlinLivenessTracker.add(list)
override fun sendString(string: String) = kotlinLivenessTracker.add(string)
override fun sendNumber(number: Any) = kotlinLivenessTracker.add(number)
override fun sendBlock(block: () -> KotlinObject) = kotlinLivenessTracker.add(block)
}
class NoAutoreleaseKotlinReceiveHelper(val kotlinLivenessTracker: KotlinLivenessTracker) : NoAutoreleaseReceiveHelper {
private val kotlinObject = KotlinObject()
lateinit var swiftObject: Any
private val list = listOf(Any())
private val string = Any().toString()
private val number = createKotlinNumber()
private val block = createLambda()
override fun receiveKotlinObject(): KotlinObject = kotlinObject.also { kotlinLivenessTracker.add(it) }
override fun receiveSwiftObject(): Any = swiftObject.also { kotlinLivenessTracker.add(it) }
override fun receiveList(): List<*> = list.also { kotlinLivenessTracker.add(it) }
override fun receiveString(): String = string.also { kotlinLivenessTracker.add(it) }
override fun receiveNumber(): Any = number.also { kotlinLivenessTracker.add(it) }
override fun receiveBlock(): () -> KotlinObject = block.also { kotlinLivenessTracker.add(it) }
}
object NoAutoreleaseSingleton {
val x = 1
}
enum class NoAutoreleaseEnum {
ENTRY;
val x = 2
}
fun callSendKotlinObject(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) {
val kotlinObject = KotlinObject()
// Repeating twice to cover possible fast paths after caching something for an object.
helper.sendKotlinObject(kotlinObject)
helper.sendKotlinObject(kotlinObject)
tracker.add(kotlinObject)
}
fun callSendSwiftObject(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker, swiftObject: Any) {
helper.sendSwiftObject(swiftObject)
helper.sendSwiftObject(swiftObject)
tracker.add(swiftObject)
}
fun callSendList(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) {
val list = listOf(Any())
helper.sendList(list)
helper.sendList(list)
tracker.add(list)
}
fun callSendString(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) {
val string = Any().toString()
helper.sendString(string)
helper.sendString(string)
tracker.add(string)
}
fun callSendNumber(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) {
val number = createKotlinNumber()
helper.sendNumber(number)
helper.sendNumber(number)
tracker.add(number)
}
fun callSendBlock(helper: NoAutoreleaseSendHelper, tracker: KotlinLivenessTracker) {
val block = createLambda()
helper.sendBlock(block)
helper.sendBlock(block)
tracker.add(block)
}
fun callReceiveKotlinObject(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) {
tracker.add(helper.receiveKotlinObject())
}
fun callReceiveSwiftObject(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) {
tracker.add(helper.receiveSwiftObject())
}
fun callReceiveList(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) {
tracker.add(helper.receiveList())
}
fun callReceiveString(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) {
tracker.add(helper.receiveString())
}
fun callReceiveNumber(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) {
tracker.add(helper.receiveNumber())
}
fun callReceiveBlock(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) {
tracker.add(helper.receiveBlock())
}
fun callReceiveBlockAndCall(helper: NoAutoreleaseReceiveHelper, tracker: KotlinLivenessTracker) = repeat(2) {
tracker.add(helper.receiveBlock()())
}
fun objc_autoreleasePoolPush() = kotlinx.cinterop.objc_autoreleasePoolPush()
fun objc_autoreleasePoolPop(handle: NativePtr) = kotlinx.cinterop.objc_autoreleasePoolPop(handle)
fun useIntArray(array: IntArray) {} // Just to make IntArray available from Swift.
private fun createLambda(): () -> KotlinObject {
val lambdaResult = KotlinObject()
return { lambdaResult } // make it capturing thus dynamic.
}
private fun createKotlinNumber(): Any = (0.5 + Any().hashCode().toDouble()) // to make it dynamic.
@@ -0,0 +1,458 @@
import Kt
private class SwiftLivenessTracker {
class SwiftWeakRef {
weak var value: AnyObject?
}
var weakRefs: [SwiftWeakRef] = []
func add(_ obj: AnyObject?) {
try! assertTrue(obj != nil)
let weakRef = SwiftWeakRef()
weakRef.value = obj
weakRefs.append(weakRef)
try! assertFalse(objectsAreDead())
}
func isEmpty() -> Bool {
return weakRefs.isEmpty
}
func objectsAreDead() -> Bool {
for weakRef in weakRefs {
if weakRef.value !== nil { return false }
}
return true
}
}
private class NoAutoreleaseSwiftHelper : NoAutoreleaseSendHelper, NoAutoreleaseReceiveHelper {
fileprivate var swiftLivenessTracker: SwiftLivenessTracker
init(swiftLivenessTracker: SwiftLivenessTracker) {
self.swiftLivenessTracker = swiftLivenessTracker
}
func sendKotlinObject(kotlinObject: KotlinObject) {
swiftLivenessTracker.add(kotlinObject)
}
func sendSwiftObject(swiftObject: Any) {
swiftLivenessTracker.add(swiftObject as AnyObject)
}
func sendList(list: [Any]) {
}
func sendString(string: String) {
}
func sendNumber(number: Any) {
swiftLivenessTracker.add(number as AnyObject)
}
func sendBlock(block: @escaping () -> KotlinObject) {
}
let kotlinObject = KotlinObject()
let swiftObject = SwiftObject2()
let list = createList()
let string = createString()
let number = createNumber()
let block = createBlock()
func receiveKotlinObject() -> KotlinObject {
let result = kotlinObject
swiftLivenessTracker.add(result)
return result
}
func receiveSwiftObject() -> Any {
let result = swiftObject
swiftLivenessTracker.add(result)
return result
}
func receiveList() -> [Any] {
return list
}
func receiveString() -> String {
return string
}
func receiveNumber() -> Any {
let result = number
swiftLivenessTracker.add(result)
return number
}
func receiveBlock() -> (() -> KotlinObject) {
return block
}
}
private struct TestFlags {
var trackSwiftLifetime: Bool = true
var checkAutorelease: Bool = true
}
private func testOnce(flags: TestFlags, block: (KotlinLivenessTracker, SwiftLivenessTracker) throws -> Void) throws {
try assertAutoreleasepoolNotUsed(flags: flags) { // Note: this assertion will also fail if something in the "test infra" below uses autoreleasepool.
let kotlinLivenessTracker = KotlinLivenessTracker()
let swiftLivenessTracker = SwiftLivenessTracker()
try assertTrue(kotlinLivenessTracker.isEmpty())
try assertTrue(swiftLivenessTracker.isEmpty())
try block(kotlinLivenessTracker, swiftLivenessTracker)
NoAutoreleaseKt.gc()
try assertFalse(kotlinLivenessTracker.isEmpty())
if flags.trackSwiftLifetime {
try assertFalse(swiftLivenessTracker.isEmpty())
} else {
// Note: some of the tests don't actually add objects to the swiftLivenessTracker,
// because e.g. Swift represents certain Obj-C classes as value types.
try assertTrue(swiftLivenessTracker.isEmpty())
}
#if !NOOP_GC
// If something has "leaked" to autoreleasepool, one of the assertions below should fail:
try assertTrue(kotlinLivenessTracker.objectsAreDead())
try assertTrue(swiftLivenessTracker.objectsAreDead())
#endif
}
}
private func test(flags: TestFlags, block: (KotlinLivenessTracker, SwiftLivenessTracker) -> Void) throws {
for _ in 1...2 {
// Repeating twice to cover possible fast paths after caching something for a type.
try testOnce(flags: flags, block: block)
}
}
private func assertAutoreleasepoolNotUsed(flags: TestFlags = TestFlags(), block: () throws -> Void) throws {
let poolBefore = NoAutoreleaseKt.objc_autoreleasePoolPush()
NoAutoreleaseKt.objc_autoreleasePoolPop(handle: poolBefore)
try block()
let poolAfter = NoAutoreleaseKt.objc_autoreleasePoolPush()
NoAutoreleaseKt.objc_autoreleasePoolPop(handle: poolAfter)
// autoreleasepool machinery is implemented as a stack.
// So here we check that stack top after `block()` is the same as before.
// Which means that no objects were added to this stack inbetween.
if flags.checkAutorelease {
try assertEquals(actual: poolAfter, expected: poolBefore)
} else {
// Just to ensure we disable the hack once it is not needed anymore:
try assertFalse(poolAfter == poolBefore)
}
}
private func testSendToKotlin<T>(
_ createObject: () -> T,
flags: TestFlags = TestFlags(),
sendObject: (NoAutoreleaseKotlinSendHelper, T) -> Void
) throws {
try test(flags: flags) { kotlinLivenessTracker, swiftLivenessTracker in
let helper = NoAutoreleaseKotlinSendHelper(kotlinLivenessTracker: kotlinLivenessTracker)
let obj = createObject()
if flags.trackSwiftLifetime {
swiftLivenessTracker.add(obj as AnyObject)
}
// Repeating twice to cover possible fast paths after caching something for an object.
sendObject(helper, obj)
sendObject(helper, obj)
}
}
private func testReceiveFromKotlin<T>(flags: TestFlags = TestFlags(), receiveObject: (NoAutoreleaseKotlinReceiveHelper) -> T) throws {
try test(flags: flags) { kotlinLivenessTracker, swiftLivenessTracker in
let helper = NoAutoreleaseKotlinReceiveHelper(kotlinLivenessTracker: kotlinLivenessTracker)
// Repeating twice to cover possible fast paths after caching something for an object.
let obj1 = receiveObject(helper)
let obj2 = receiveObject(helper)
if flags.trackSwiftLifetime {
swiftLivenessTracker.add(obj1 as AnyObject)
swiftLivenessTracker.add(obj2 as AnyObject)
}
}
}
private func testCallToSwift(flags: TestFlags = TestFlags(), callKotlin: (NoAutoreleaseSwiftHelper, KotlinLivenessTracker) -> Void) throws {
try test(flags: flags) { kotlinLivenessTracker, swiftLivenessTracker in
let helper = NoAutoreleaseSwiftHelper(swiftLivenessTracker: swiftLivenessTracker)
callKotlin(helper, kotlinLivenessTracker)
}
}
private class SwiftObject1 {}
private class SwiftObject2 {}
private class SwiftObject3 {}
private class SwiftObject4 {}
private func testSendKotlinObjectToKotlin() throws {
try testSendToKotlin({ KotlinObject() }) {
$0.sendKotlinObject(kotlinObject: $1)
}
}
private func testSendSwiftObjectToKotlin() throws {
try testSendToKotlin({ SwiftObject1() }) {
$0.sendSwiftObject(swiftObject: $1)
}
}
private func testSendListToKotlin() throws {
try testSendToKotlin({ createList() }, flags: TestFlags(trackSwiftLifetime: false)) {
$0.sendList(list: $1)
}
}
private func testSendStringToKotlin() throws {
try testSendToKotlin({ createString() }, flags: TestFlags(trackSwiftLifetime: false)) {
$0.sendString(string: $1)
}
}
private func testSendNumberToKotlin() throws {
try testSendToKotlin({ createNumber() }) {
$0.sendNumber(number: $1)
}
}
private func testSendBlockToKotlin() throws {
try testSendToKotlin({ createBlock() }, flags: TestFlags(trackSwiftLifetime: false)) {
$0.sendBlock(block: $1)
}
}
private func testReceiveKotlinObjectFromKotlin() throws {
try testReceiveFromKotlin {
$0.receiveKotlinObject()
}
}
private func testReceiveSwiftObjectFromKotlin() throws {
try testReceiveFromKotlin { (helper: NoAutoreleaseKotlinReceiveHelper) -> Any in
helper.swiftObject = SwiftObject3()
return helper.receiveSwiftObject()
}
}
private func testReceiveListFromKotlin() throws {
// Swift conversion from NSArray to Swift Array uses non-optimized autorelease in the implementation,
// So regardless of our efforts, passing List from Kotlin to Swift will use autoreleasepool.
// Use `checkAutorelease: false` flag to disable the relevant check.
try testReceiveFromKotlin(flags: TestFlags(trackSwiftLifetime: false, checkAutorelease: false)) {
$0.receiveList()
}
}
private func testReceiveStringFromKotlin() throws {
try testReceiveFromKotlin(flags: TestFlags(trackSwiftLifetime: false)) {
$0.receiveString()
}
}
private func testReceiveNumberFromKotlin() throws {
try testReceiveFromKotlin {
$0.receiveNumber()
}
}
private func testReceiveBlockFromKotlin() throws {
try testReceiveFromKotlin(flags: TestFlags(trackSwiftLifetime: false)) {
$0.receiveBlock()
}
}
private func testReceiveBlockFromKotlinAndCall() throws {
try testReceiveFromKotlin {
$0.receiveBlock()()
}
}
private func testCreateKotlinArray() throws {
try testReceiveFromKotlin { (helper: NoAutoreleaseKotlinReceiveHelper) -> KotlinIntArray in
let array = KotlinIntArray(size: 2)
helper.kotlinLivenessTracker.add(obj: array)
return array
}
}
private func testGetKotlinUnit() throws {
try assertAutoreleasepoolNotUsed {
try assertSame(actual: KotlinUnit.shared, expected: KotlinUnit.shared)
}
}
private func testGetKotlinSingleton() throws {
try assertAutoreleasepoolNotUsed {
try assertEquals(actual: NoAutoreleaseSingleton.shared.x, expected: 1)
}
}
private func testGetKotlinEnumEntry() throws {
try assertAutoreleasepoolNotUsed {
try assertEquals(actual: NoAutoreleaseEnum.entry.x, expected: 2)
}
}
private func testSendKotlinObjectToSwift() throws {
try testCallToSwift {
NoAutoreleaseKt.callSendKotlinObject(helper: $0, tracker: $1)
}
}
private func testSendSwiftObjectToSwift() throws {
try testCallToSwift {
NoAutoreleaseKt.callSendSwiftObject(helper: $0, tracker: $1, swiftObject: SwiftObject4())
}
}
private func testSendListToSwift() throws {
// Swift conversion from NSArray to Swift Array uses non-optimized autorelease in the implementation,
// So regardless of our efforts, passing List from Kotlin to Swift will use autoreleasepool.
// Use `checkAutorelease: false` flag to disable the relevant check.
try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false, checkAutorelease: false)) {
NoAutoreleaseKt.callSendList(helper: $0, tracker: $1)
}
}
private func testSendStringToSwift() throws {
try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) {
NoAutoreleaseKt.callSendString(helper: $0, tracker: $1)
}
}
private func testSendNumberToSwift() throws {
try testCallToSwift {
NoAutoreleaseKt.callSendNumber(helper: $0, tracker: $1)
}
}
private func testSendBlockToSwift() throws {
try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) {
NoAutoreleaseKt.callSendBlock(helper: $0, tracker: $1)
}
}
private func testReceiveKotlinObjectFromSwift() throws {
try testCallToSwift {
NoAutoreleaseKt.callReceiveKotlinObject(helper: $0, tracker: $1)
}
}
private func testReceiveSwiftObjectFromSwift() throws {
try testCallToSwift {
NoAutoreleaseKt.callReceiveSwiftObject(helper: $0, tracker: $1)
}
}
private func testReceiveListFromSwift() throws {
try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) {
NoAutoreleaseKt.callReceiveList(helper: $0, tracker: $1)
}
}
private func testReceiveStringFromSwift() throws {
try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) {
NoAutoreleaseKt.callReceiveString(helper: $0, tracker: $1)
}
}
private func testReceiveNumberFromSwift() throws {
try testCallToSwift {
NoAutoreleaseKt.callReceiveNumber(helper: $0, tracker: $1)
}
}
private func testReceiveBlockFromSwift() throws {
try testCallToSwift(flags: TestFlags(trackSwiftLifetime: false)) {
NoAutoreleaseKt.callReceiveBlock(helper: $0, tracker: $1)
}
}
private func testReceiveBlockFromSwiftAndCall() throws {
try testCallToSwift {
NoAutoreleaseKt.callReceiveBlockAndCall(helper: $0, tracker: $1)
}
}
private func createList() -> [Any] {
return [NSObject()]
}
private func createBlock() -> () -> KotlinObject {
let blockResult = KotlinObject()
return { return blockResult } // Make capturing thus dynamic.
}
private func createString() -> String {
return NSObject().description // Make it dynamic.
}
private func createNumber() -> NSNumber {
return (0.5 + Double(NSObject().hash)) as NSNumber
}
class NoAutoreleaseTests : SimpleTestProvider {
override init() {
super.init()
test("testSendKotlinObjectToKotlin", testSendKotlinObjectToKotlin)
test("testSendSwiftObjectToKotlin", testSendSwiftObjectToKotlin)
test("testSendListToKotlin", testSendListToKotlin)
test("testSendStringToKotlin", testSendStringToKotlin)
test("testSendNumberToKotlin", testSendNumberToKotlin)
test("testSendBlockToKotlin", testSendBlockToKotlin)
test("testReceiveKotlinObjectFromKotlin", testReceiveKotlinObjectFromKotlin)
test("testReceiveSwiftObjectFromKotlin", testReceiveSwiftObjectFromKotlin)
test("testReceiveListFromKotlin", testReceiveListFromKotlin)
test("testReceiveStringFromKotlin", testReceiveStringFromKotlin)
test("testReceiveNumberFromKotlin", testReceiveNumberFromKotlin)
test("testReceiveBlockFromKotlin", testReceiveBlockFromKotlin)
test("testReceiveBlockFromKotlinAndCall", testReceiveBlockFromKotlinAndCall)
test("testCreateKotlinArray", testCreateKotlinArray)
test("testGetKotlinUnit", testGetKotlinUnit)
test("testGetKotlinSingleton", testGetKotlinSingleton)
test("testGetKotlinEnumEntry", testGetKotlinEnumEntry)
#if false
test("testSendKotlinObjectToSwift", testSendKotlinObjectToSwift)
#endif
test("testSendSwiftObjectToSwift", testSendSwiftObjectToSwift)
#if false
test("testSendListToSwift", testSendListToSwift)
test("testSendStringToSwift", testSendStringToSwift)
test("testSendNumberToSwift", testSendNumberToSwift)
test("testSendBlockToSwift", testSendBlockToSwift)
test("testReceiveKotlinObjectFromSwift", testReceiveKotlinObjectFromSwift)
test("testReceiveSwiftObjectFromSwift", testReceiveSwiftObjectFromSwift)
test("testReceiveListFromSwift", testReceiveListFromSwift)
test("testReceiveStringFromSwift", testReceiveStringFromSwift)
test("testReceiveNumberFromSwift", testReceiveNumberFromSwift)
test("testReceiveBlockFromSwift", testReceiveBlockFromSwift)
test("testReceiveBlockFromSwiftAndCall", testReceiveBlockFromSwiftAndCall)
#endif
}
}