Native: optimize autorelease in ObjCExport calls to Kotlin

Use objc_autoreleaseReturnValue to eliminate the autorelease operation
for return value if the caller is optimized (usually it is).

This required moving autorelease operation from Kotlin -> ObjC ref
conversion to bridge epilogue.

To achieve this, also make ObjCExport Kotlin ref -> ObjC ref dynamic
converters return retained reference (instead of autoreleased one).
To reflect this, rename the corresponding entities in the code.
This commit is contained in:
Svyatoslav Scherbina
2021-08-26 17:40:58 +03:00
committed by Space
parent 75a3070067
commit 8c923f6504
14 changed files with 209 additions and 160 deletions
@@ -34,6 +34,7 @@ inline static OBJ_GETTER(AllocInstanceWithAssociatedObject, const TypeInfo* type
}
extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj);
extern "C" id Kotlin_ObjCExport_refToLocalObjC(ObjHeader* obj);
extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj);
extern "C" id Kotlin_Interop_CreateNSStringFromKString(KRef str);
@@ -83,11 +83,11 @@ struct ObjCTypeAdapter {
int reverseAdapterNum;
};
typedef id (*convertReferenceToObjC)(ObjHeader* obj);
typedef id (*convertReferenceToRetainedObjC)(ObjHeader* obj);
typedef OBJ_GETTER((*convertReferenceFromObjC), id obj);
struct TypeInfoObjCExportAddition {
/*convertReferenceToObjC*/ void* convert;
/*convertReferenceToRetainedObjC*/ void* convertToRetained;
Class objCClass;
const ObjCTypeAdapter* typeAdapter;
};
@@ -138,8 +138,6 @@ RUNTIME_NOTHROW extern "C" OBJ_GETTER(Kotlin_ObjCExport_AllocInstanceWithAssocia
static Class getOrCreateClass(const TypeInfo* typeInfo);
extern "C" id objc_retainAutoreleaseReturnValue(id self);
namespace {
ALWAYS_INLINE void send_releaseAsAssociatedObject(void* associatedObject, ReleaseMode mode) {
@@ -171,25 +169,25 @@ extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_detachAssociatedObject(void* ass
}
}
extern "C" id Kotlin_ObjCExport_convertUnit(ObjHeader* unitInstance) {
extern "C" id Kotlin_ObjCExport_convertUnitToRetained(ObjHeader* unitInstance) {
static dispatch_once_t onceToken;
static id instance = nullptr;
dispatch_once(&onceToken, ^{
Class unitClass = getOrCreateClass(unitInstance->type_info());
instance = [[unitClass createWrapper:unitInstance] retain];
instance = [unitClass createRetainedWrapper:unitInstance];
});
return instance;
return objc_retain(instance);
}
extern "C" id Kotlin_ObjCExport_CreateNSStringFromKString(ObjHeader* str) {
extern "C" id Kotlin_ObjCExport_CreateRetainedNSStringFromKString(ObjHeader* str) {
KChar* utf16Chars = CharArrayAddressOfElementAt(str->array(), 0);
auto numBytes = str->array()->count_ * sizeof(KChar);
if (str->permanent()) {
return [[[NSString alloc] initWithBytesNoCopy:utf16Chars
return [[NSString alloc] initWithBytesNoCopy:utf16Chars
length:numBytes
encoding:NSUTF16LittleEndianStringEncoding
freeWhenDone:NO] autorelease];
freeWhenDone:NO];
} else {
// TODO: consider making NSString subclass to avoid copying here.
NSString* candidate = [[NSString alloc] initWithBytes:utf16Chars
@@ -202,11 +200,11 @@ extern "C" id Kotlin_ObjCExport_CreateNSStringFromKString(ObjHeader* str) {
id old = AtomicCompareAndSwapAssociatedObject(str, nullptr, candidate);
if (old != nullptr) {
objc_release(candidate);
return objc_retainAutoreleaseReturnValue(old);
return objc_retain(old);
}
}
return objc_retainAutoreleaseReturnValue(candidate);
return objc_retain(candidate);
}
}
static const ObjCTypeAdapter* findAdapterByName(
@@ -519,9 +517,16 @@ static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) {
}
}
static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj);
static id Kotlin_ObjCExport_refToRetainedObjC_slowpath(ObjHeader* obj);
template <bool retainAutorelease>
extern "C" id objc_autorelease(id self);
// retain = true means that it returns retained result, which must be eventually released by the caller.
//
// retain = false means that it returns unretained result, which is not guaranteed to outlive [obj],
// but doesn't require any balancing release operation.
// It might use autorelease though, which will be suboptimal.
template <bool retain>
static ALWAYS_INLINE id Kotlin_ObjCExport_refToObjCImpl(ObjHeader* obj) {
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
@@ -529,22 +534,34 @@ static ALWAYS_INLINE id Kotlin_ObjCExport_refToObjCImpl(ObjHeader* obj) {
id associatedObject = GetAssociatedObject(obj);
if (associatedObject != nullptr) {
return retainAutorelease ? objc_retainAutoreleaseReturnValue(associatedObject) : associatedObject;
return retain ? objc_retain(associatedObject) : associatedObject;
}
// TODO: propagate [retainAutorelease] to the code below.
convertReferenceToObjC converter = (convertReferenceToObjC)obj->type_info()->writableInfo_->objCExport.convert;
if (converter != nullptr) {
return converter(obj);
convertReferenceToRetainedObjC convertToRetained = (convertReferenceToRetainedObjC)obj->type_info()->writableInfo_->objCExport.convertToRetained;
id retainedResult;
if (convertToRetained != nullptr) {
retainedResult = convertToRetained(obj);
} else {
retainedResult = Kotlin_ObjCExport_refToRetainedObjC_slowpath(obj);
}
return Kotlin_ObjCExport_refToObjC_slowpath(obj);
// Balance retain with objc_autorelease if required:
return retain ? retainedResult : objc_autorelease(retainedResult);
}
extern "C" id Kotlin_ObjCExport_refToRetainedObjC(ObjHeader* obj) {
return Kotlin_ObjCExport_refToObjCImpl<true>(obj);
}
extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) {
// TODO: in some cases (e.g. when converting a bridge argument) performing retain-autorelease is not necessary.
return Kotlin_ObjCExport_refToObjCImpl<true>(obj);
return objc_autorelease(Kotlin_ObjCExport_refToObjCImpl<true>(obj));
}
extern "C" id Kotlin_ObjCExport_refToLocalObjC(ObjHeader* obj) {
return Kotlin_ObjCExport_refToObjCImpl<false>(obj);
}
extern "C" ALWAYS_INLINE id Kotlin_Interop_refToObjC(ObjHeader* obj) {
@@ -570,13 +587,13 @@ extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj) {
RETURN_RESULT_OF(msgSend, obj, Kotlin_ObjCExport_toKotlinSelector);
}
static id convertKotlinObject(ObjHeader* obj) {
static id convertKotlinObjectToRetained(ObjHeader* obj) {
Class clazz = obj->type_info()->writableInfo_->objCExport.objCClass;
RuntimeAssert(clazz != nullptr, "");
return [clazz createWrapper:obj];
return [clazz createRetainedWrapper:obj];
}
static convertReferenceToObjC findConverterFromInterfaces(const TypeInfo* typeInfo) {
static convertReferenceToRetainedObjC findConvertToRetainedFromInterfaces(const TypeInfo* typeInfo) {
const TypeInfo* foundTypeInfo = nullptr;
for (int i = 0; i < typeInfo->implementedInterfacesCount_; ++i) {
@@ -601,7 +618,7 @@ static convertReferenceToObjC findConverterFromInterfaces(const TypeInfo* typeIn
return nullptr;
}
if (interfaceTypeInfo->writableInfo_->objCExport.convert != nullptr) {
if (interfaceTypeInfo->writableInfo_->objCExport.convertToRetained != nullptr) {
if (foundTypeInfo == nullptr || IsSubInterface(interfaceTypeInfo, foundTypeInfo)) {
foundTypeInfo = interfaceTypeInfo;
} else if (!IsSubInterface(foundTypeInfo, interfaceTypeInfo)) {
@@ -615,23 +632,21 @@ static convertReferenceToObjC findConverterFromInterfaces(const TypeInfo* typeIn
return foundTypeInfo == nullptr ?
nullptr :
(convertReferenceToObjC)foundTypeInfo->writableInfo_->objCExport.convert;
(convertReferenceToRetainedObjC)foundTypeInfo->writableInfo_->objCExport.convertToRetained;
}
static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj) {
static id Kotlin_ObjCExport_refToRetainedObjC_slowpath(ObjHeader* obj) {
const TypeInfo* typeInfo = obj->type_info();
convertReferenceToObjC converter = nullptr;
convertReferenceToRetainedObjC convertToRetained = findConvertToRetainedFromInterfaces(typeInfo);
converter = findConverterFromInterfaces(typeInfo);
if (converter == nullptr) {
if (convertToRetained == nullptr) {
getOrCreateClass(typeInfo);
converter = (typeInfo == theUnitTypeInfo) ? &Kotlin_ObjCExport_convertUnit : &convertKotlinObject;
convertToRetained = (typeInfo == theUnitTypeInfo) ? &Kotlin_ObjCExport_convertUnitToRetained : &convertKotlinObjectToRetained;
}
typeInfo->writableInfo_->objCExport.convert = (void*)converter;
typeInfo->writableInfo_->objCExport.convertToRetained = (void*)convertToRetained;
return converter(obj);
return convertToRetained(obj);
}
static void buildITable(TypeInfo* result, const KStdOrderedMap<ClassId, KStdVector<VTableElement>>& interfaceVTables) {
@@ -17,7 +17,7 @@ typedef void (^Completion)(id _Nullable, NSError* _Nullable);
extern "C" void Kotlin_ObjCExport_runCompletionSuccess(KRef completionHolder, KRef result) {
Completion completion = (Completion)GetAssociatedObject(completionHolder);
id objCResult = Kotlin_ObjCExport_refToObjC(result);
id objCResult = Kotlin_ObjCExport_refToLocalObjC(result);
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
completion(objCResult, nullptr);
}
@@ -82,7 +82,7 @@ extern "C" id Kotlin_ObjCExport_WrapExceptionToNSError(KRef exception) {
KRef message = Kotlin_Throwable_getMessage(exception, messageHolder.slot());
NSString* description = Kotlin_Interop_CreateNSStringFromKString(message);
id exceptionObjCRef = Kotlin_ObjCExport_refToObjC(exception);
id exceptionObjCRef = Kotlin_ObjCExport_refToLocalObjC(exception);
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
@@ -24,7 +24,7 @@
//! TODO: Use not_null signature.
OBJ_GETTER(Kotlin_ObjCExport_ExceptionDetails, KRef /*thiz*/, KRef exceptionHolder) {
if (NSException* exception = (NSException*)Kotlin_ObjCExport_refToObjC(exceptionHolder)) {
if (NSException* exception = (NSException*)Kotlin_ObjCExport_refToLocalObjC(exceptionHolder)) {
RuntimeAssert([exception isKindOfClass:[NSException class]], "Illegal type: NSException expected");
NSString* ret = [NSString stringWithFormat: @"%@:: %@", exception.name, exception.reason];
RETURN_RESULT_OF(Kotlin_Interop_CreateKStringFromNSString, ret);
@@ -15,7 +15,7 @@
#import "ObjCExport.h"
@interface KotlinBase : NSObject <NSCopying>
+(instancetype)createWrapper:(ObjHeader*)obj;
+(instancetype)createRetainedWrapper:(ObjHeader*)obj;
@end;
enum class ReleaseMode {
@@ -47,7 +47,9 @@ namespace {
extern "C" {
id Kotlin_ObjCExport_CreateNSStringFromKString(ObjHeader* str);
id Kotlin_ObjCExport_CreateRetainedNSStringFromKString(ObjHeader* str);
extern "C" id objc_autorelease(id self);
id Kotlin_Interop_CreateNSStringFromKString(ObjHeader* str) {
// Note: this function is just a bit specialized [Kotlin_Interop_refToObjC].
@@ -59,7 +61,7 @@ id Kotlin_Interop_CreateNSStringFromKString(ObjHeader* str) {
return (id)associatedObject;
}
return Kotlin_ObjCExport_CreateNSStringFromKString(str);
return objc_autorelease(Kotlin_ObjCExport_CreateRetainedNSStringFromKString(str));
}
OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, NSString* str) {
@@ -168,6 +168,7 @@ extern "C" const char* Kotlin_callsCheckerGoodFunctionNames[] = {
"-[NSObject retain]",
"-[NSPlaceholderString initWithBytes:length:encoding:]",
"-[NSPlaceholderString initWithBytesNoCopy:length:encoding:freeWhenDone:]",
"-[NSValue init]",
"-[NSValue pointerValue]",
"-[__NSCFBoolean boolValue]",
"-[__NSCFNumber doubleValue]",
@@ -237,6 +238,7 @@ extern "C" const char* Kotlin_callsCheckerGoodFunctionNames[] = {
"llvm.memcpy.*",
"llvm.memmove.*",
"llvm.memset.*",
"llvm.objc.autorelease",
"llvm.objc.autoreleaseReturnValue",
"llvm.objc.retain",
"llvm.objectsize.*",
@@ -25,9 +25,6 @@
#import "Mutex.hpp"
#import "Exceptions.h"
extern "C" id objc_retainAutoreleaseReturnValue(id self);
extern "C" id objc_autoreleaseReturnValue(id self);
@interface NSObject (NSObjectPrivateMethods)
// Implemented for NSObject in libobjc/NSObject.mm
-(BOOL)_tryRetain;
@@ -85,7 +82,7 @@ static void injectToRuntime();
return result;
}
+(instancetype)createWrapper:(ObjHeader*)obj {
+(instancetype)createRetainedWrapper:(ObjHeader*)obj {
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
KotlinBase* candidate = [super allocWithZone:nil];
@@ -104,12 +101,12 @@ static void injectToRuntime();
candidate->refHolder.releaseRef();
[candidate releaseAsAssociatedObject:ReleaseMode::kDetachAndRelease];
}
return objc_retainAutoreleaseReturnValue(old);
return objc_retain(old);
}
}
}
return objc_autoreleaseReturnValue(candidate);
return candidate;
}
-(instancetype)retain {
@@ -190,8 +190,8 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) {
[super dealloc];
}
+(id)createWithKList:(KRef)list {
KListAsNSArray* result = [[[KListAsNSArray alloc] init] autorelease];
+(id)createRetainedWithKList:(KRef)list {
KListAsNSArray* result = [[KListAsNSArray alloc] init];
result->listHolder.init(list);
return result;
}
@@ -226,8 +226,8 @@ static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) {
[super dealloc];
}
+(id)createWithKList:(KRef)list {
KMutableListAsNSMutableArray* result = [[[KMutableListAsNSMutableArray alloc] init] autorelease];
+(id)createRetainedWithKList:(KRef)list {
KMutableListAsNSMutableArray* result = [[KMutableListAsNSMutableArray alloc] init];
result->listHolder.init(list);
return result;
}
@@ -306,8 +306,8 @@ static inline id KSet_getElement(KRef set, id object) {
[super dealloc];
}
+(id)createWithKSet:(KRef)set {
KSetAsNSSet* result = [[[KSetAsNSSet alloc] init] autorelease];
+(id)createRetainedWithKSet:(KRef)set {
KSetAsNSSet* result = [[KSetAsNSSet alloc] init];
result->setHolder.init(set);
return result;
}
@@ -468,8 +468,8 @@ static inline id KMap_get(KRef map, id aKey) {
[super dealloc];
}
+(id)createWithKMap:(KRef)map {
KMapAsNSDictionary* result = [[[KMapAsNSDictionary alloc] init] autorelease];
+(id)createRetainedWithKMap:(KRef)map {
KMapAsNSDictionary* result = [[KMapAsNSDictionary alloc] init];
result->mapHolder.init(map);
return result;
}
@@ -608,28 +608,28 @@ static inline id KMap_get(KRef map, id aKey) {
// Referenced from the generated code:
extern "C" id Kotlin_Interop_CreateNSArrayFromKList(KRef obj) {
return [KListAsNSArray createWithKList:obj];
extern "C" id Kotlin_Interop_CreateRetainedNSArrayFromKList(KRef obj) {
return [KListAsNSArray createRetainedWithKList:obj];
}
extern "C" id Kotlin_Interop_CreateNSMutableArrayFromKList(KRef obj) {
return [KMutableListAsNSMutableArray createWithKList:obj];
extern "C" id Kotlin_Interop_CreateRetainedNSMutableArrayFromKList(KRef obj) {
return [KMutableListAsNSMutableArray createRetainedWithKList:obj];
}
extern "C" id Kotlin_Interop_CreateNSSetFromKSet(KRef obj) {
return [KSetAsNSSet createWithKSet:obj];
extern "C" id Kotlin_Interop_CreateRetainedNSSetFromKSet(KRef obj) {
return [KSetAsNSSet createRetainedWithKSet:obj];
}
extern "C" id Kotlin_Interop_CreateKotlinMutableSetFromKSet(KRef obj) {
return [[[KotlinMutableSet alloc] initWithKSet:obj] autorelease];
extern "C" id Kotlin_Interop_CreateRetainedKotlinMutableSetFromKSet(KRef obj) {
return [[KotlinMutableSet alloc] initWithKSet:obj];
}
extern "C" id Kotlin_Interop_CreateNSDictionaryFromKMap(KRef obj) {
return [KMapAsNSDictionary createWithKMap:obj];
extern "C" id Kotlin_Interop_CreateRetainedNSDictionaryFromKMap(KRef obj) {
return [KMapAsNSDictionary createRetainedWithKMap:obj];
}
extern "C" id Kotlin_Interop_CreateKotlinMutableDictonaryFromKMap(KRef obj) {
return [[[KotlinMutableDictionary alloc] initWithKMap:obj] autorelease];
extern "C" id Kotlin_Interop_CreateRetainedKotlinMutableDictionaryFromKMap(KRef obj) {
return [[KotlinMutableDictionary alloc] initWithKMap:obj];
}
#endif // KONAN_OBJC_INTEROP