Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Commit

Permalink
Fix Objective-C block to kotlin.Function* dynamic conversion (#3499)
Browse files Browse the repository at this point in the history
after migrating to synthetic function classes.
  • Loading branch information
SvyatoslavScherbina committed Oct 29, 2019
1 parent 6da4bd2 commit 8254f75
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,9 @@ private fun ObjCExportCodeGenerator.setObjCExportTypeInfo(
private val ObjCExportCodeGenerator.kotlinToObjCFunctionType: LLVMTypeRef
get() = functionType(int8TypePtr, false, codegen.kObjHeaderPtr)

private val ObjCExportCodeGenerator.objCToKotlinFunctionType: LLVMTypeRef
get() = functionType(codegen.kObjHeaderPtr, false, int8TypePtr, codegen.kObjHeaderPtrPtr)

private fun ObjCExportCodeGenerator.emitBoxConverters() {
val irBuiltIns = context.irBuiltIns

Expand Down Expand Up @@ -482,18 +485,30 @@ private fun ObjCExportCodeGenerator.emitFunctionConverters() {
}

private fun ObjCExportCodeGenerator.emitBlockToKotlinFunctionConverters() {
val converters = context.ir.symbols.functionIrClassFactory.builtFunctionNClasses.map {
val bridge = BlockPointerBridge(numberOfParameters = it.arity, returnsVoid = false)
constPointer(blockToKotlinFunctionConverter(bridge))
val functionClassesByArity =
context.ir.symbols.functionIrClassFactory.builtFunctionNClasses.associateBy { it.arity }

val count = (functionClassesByArity.keys.max() ?: -1) + 1

val converters = (0 until count).map { arity ->
val functionClass = functionClassesByArity[arity]
if (functionClass != null) {
val bridge = BlockPointerBridge(numberOfParameters = functionClass.arity, returnsVoid = false)
constPointer(blockToKotlinFunctionConverter(bridge))
} else {
NullPointer(objCToKotlinFunctionType)
}
}

val ptr = staticData.placeGlobalArray(
"",
converters.first().llvmType,
pointerType(objCToKotlinFunctionType),
converters
).pointer.getElementPtr(0)

// Note: this global replaces the weak global defined in runtime.
// Note: replacing weak globals defined in runtime.
replaceExternalWeakOrCommonGlobal("Kotlin_ObjCExport_blockToFunctionConverters", ptr)
replaceExternalWeakOrCommonGlobal("Kotlin_ObjCExport_blockToFunctionConverters_size", Int32(count))
}

private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
Expand Down
2 changes: 2 additions & 0 deletions backend.native/tests/framework/values/expectedLazy.h
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,8 @@ __attribute__((swift_name("ValuesKt")))
+ (void (^)(void))asNothingBlockBlock:(id _Nullable (^)(void))block __attribute__((swift_name("asNothingBlock(block:)")));
+ (void (^ _Nullable)(void))getNullBlock __attribute__((swift_name("getNullBlock()")));
+ (BOOL)isBlockNullBlock:(void (^ _Nullable)(void))block __attribute__((swift_name("isBlockNull(block:)")));
+ (BOOL)isFunctionObj:(id _Nullable)obj __attribute__((swift_name("isFunction(obj:)")));
+ (BOOL)isFunction0Obj:(id _Nullable)obj __attribute__((swift_name("isFunction0(obj:)")));
+ (void)takeForwardDeclaredClassObj:(ForwardDeclaredClass *)obj __attribute__((swift_name("takeForwardDeclaredClass(obj:)")));
+ (void)takeForwardDeclaredProtocolObj:(id<ForwardDeclared>)obj __attribute__((swift_name("takeForwardDeclaredProtocol(obj:)")));
+ (void)error __attribute__((swift_name("error()"))) __attribute__((unavailable("error")));
Expand Down
6 changes: 6 additions & 0 deletions backend.native/tests/framework/values/values.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlinx.cinterop.*

// Ensure loaded function IR classes aren't ordered by arity:
internal fun referenceFunction1(block: (Any?) -> Unit) {}

// Constants
const val dbl: Double = 3.14
const val flt: Float = 2.73F
Expand Down Expand Up @@ -432,6 +435,9 @@ object UnitBlockCoercionImpl : UnitBlockCoercion<() -> Unit> {
override fun uncoerce(block: () -> Unit): () -> Unit = block
}

fun isFunction(obj: Any?): Boolean = obj is Function<*>
fun isFunction0(obj: Any?): Boolean = obj is Function0<*>

abstract class MyAbstractList : List<Any?>

fun takeForwardDeclaredClass(obj: objcnames.classes.ForwardDeclaredClass) {}
Expand Down
12 changes: 12 additions & 0 deletions backend.native/tests/framework/values/values.swift
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,18 @@ func testLambda() throws {
}
try assertTrue(uncoercedUnitBlock() == Void())
try assertEquals(actual: blockRuns, expected: 5)

let blockMustBeFunction0: @convention(block) () -> AnyObject? = { return nil }
try assertTrue(ValuesKt.isFunction0(obj: blockMustBeFunction0))
try assertFalse(ValuesKt.isFunction(obj: NSObject()))
try assertFalse(ValuesKt.isFunction0(obj: NSObject()))

// Test no function class for dynamic conversion:
let blockAsMissingFunction: @convention(block) (AnyObject?, AnyObject?, AnyObject?, AnyObject?, AnyObject?) -> AnyObject?
= { return $0 ?? $1 ?? $2 ?? $3 ?? $4 }

try assertTrue(ValuesKt.isFunction(obj: blockAsMissingFunction))
try assertFalse(ValuesKt.isFunction0(obj: blockAsMissingFunction))
}

// -------- Tests for classes and interfaces -------
Expand Down
19 changes: 14 additions & 5 deletions runtime/src/main/cpp/ObjCExport.mm
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,9 @@ -(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {

// Note: replaced by compiler in appropriate compilation modes.
__attribute__((weak)) convertReferenceFromObjC* Kotlin_ObjCExport_blockToFunctionConverters = nullptr;
__attribute__((weak)) int Kotlin_ObjCExport_blockToFunctionConverters_size = 0;

extern "C" id objc_retainBlock(id self);

static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) {
const char* encoding = getBlockEncoding(block);
Expand All @@ -607,10 +610,6 @@ static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:encoding];
int parameterCount = signature.numberOfArguments - 1; // 1 for the block itself.

if (parameterCount > 22) {
[NSException raise:NSGenericException format:@"Blocks with %d (>22) parameters aren't supported", parameterCount];
}

for (int i = 1; i <= parameterCount; ++i) {
const char* argEncoding = [signature getArgumentTypeAtIndex:i];
if (argEncoding[0] != '@') {
Expand All @@ -625,7 +624,17 @@ static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) {
format:@"Blocks with non-reference-typed return value aren't supported (%s)", returnTypeEncoding];
}

RETURN_RESULT_OF(Kotlin_ObjCExport_blockToFunctionConverters[parameterCount], block);
auto converter = parameterCount < Kotlin_ObjCExport_blockToFunctionConverters_size
? Kotlin_ObjCExport_blockToFunctionConverters[parameterCount]
: nullptr;

if (converter != nullptr) {
RETURN_RESULT_OF(converter, block);
} else {
// There is no function class for this arity, so resulting object will not be cast to FunctionN class,
// and it is enough to convert block to arbitrary object conforming Function.
RETURN_RESULT_OF(AllocInstanceWithAssociatedObject, theOpaqueFunctionTypeInfo, objc_retainBlock(block));
}
}

static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj);
Expand Down
1 change: 1 addition & 0 deletions runtime/src/main/cpp/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ extern const TypeInfo* theFloatArrayTypeInfo;
extern const TypeInfo* theForeignObjCObjectTypeInfo;
extern const TypeInfo* theFreezableAtomicReferenceTypeInfo;
extern const TypeInfo* theObjCObjectWrapperTypeInfo;
extern const TypeInfo* theOpaqueFunctionTypeInfo;
extern const TypeInfo* theShortArrayTypeInfo;
extern const TypeInfo* theStringTypeInfo;
extern const TypeInfo* theThrowableTypeInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,7 @@ private fun Kotlin_Throwable_getMessage(throwable: Throwable): String? = throwab
@ExportForCppRuntime
private fun Kotlin_ObjCExport_getWrappedError(throwable: Throwable): Any? =
(throwable as? ObjCErrorException)?.error

@ExportTypeInfo("theOpaqueFunctionTypeInfo")
@PublishedApi
internal class OpaqueFunction : Function<Any?>

0 comments on commit 8254f75

Please sign in to comment.