Native: improve thread state switching for Obj-C interop
This commit is contained in:
committed by
Space
parent
85ab4f68df
commit
79d4047e86
+3
@@ -785,6 +785,9 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
|
|||||||
// Calls to other ObjC class constructors must be lowered.
|
// Calls to other ObjC class constructors must be lowered.
|
||||||
require(constructedClass.isKotlinObjCClass()) { renderCompilerError(expression) }
|
require(constructedClass.isKotlinObjCClass()) { renderCompilerError(expression) }
|
||||||
return builder.at(expression).irBlock {
|
return builder.at(expression).irBlock {
|
||||||
|
// Note: using [interopAllocObjCObject] and [interopObjCRelease] here is suboptimal: they switch the thread to Native state
|
||||||
|
// and then back to Runnable.
|
||||||
|
// TODO: consider calling specialized versions of allocWithZoneImp and releaseImp directly.
|
||||||
val rawPtr = irTemporary(irCall(symbols.interopAllocObjCObject.owner).apply {
|
val rawPtr = irTemporary(irCall(symbols.interopAllocObjCObject.owner).apply {
|
||||||
putValueArgument(0, getObjCClass(symbols, constructedClass.symbol))
|
putValueArgument(0, getObjCClass(symbols, constructedClass.symbol))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -125,20 +125,21 @@ template <ErrorPolicy errorPolicy>
|
|||||||
void BackRefFromAssociatedObject::addRef() {
|
void BackRefFromAssociatedObject::addRef() {
|
||||||
static_assert(errorPolicy != ErrorPolicy::kDefaultValue, "Cannot use default return value here");
|
static_assert(errorPolicy != ErrorPolicy::kDefaultValue, "Cannot use default return value here");
|
||||||
|
|
||||||
|
// Can be called both from Native state (if ObjC or Swift code adds RC)
|
||||||
|
// and from Runnable state (Kotlin_ObjCExport_refToObjC).
|
||||||
|
|
||||||
if (atomicAdd(&refCount, 1) == 1) {
|
if (atomicAdd(&refCount, 1) == 1) {
|
||||||
if (obj_ == nullptr) return; // E.g. after [detach].
|
if (obj_ == nullptr) return; // E.g. after [detach].
|
||||||
|
|
||||||
|
kotlin::CalledFromNativeGuard guard(/* reentrant */ true);
|
||||||
|
|
||||||
// There are no references to the associated object itself, so Kotlin object is being passed from Kotlin,
|
// There are no references to the associated object itself, so Kotlin object is being passed from Kotlin,
|
||||||
// and it is owned therefore.
|
// and it is owned therefore.
|
||||||
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
|
|
||||||
ensureRefAccessible<errorPolicy>(obj_, context_); // TODO: consider removing explicit verification.
|
ensureRefAccessible<errorPolicy>(obj_, context_); // TODO: consider removing explicit verification.
|
||||||
|
|
||||||
// Foreign reference has already been deinitialized (see [releaseRef]).
|
// Foreign reference has already been deinitialized (see [releaseRef]).
|
||||||
// Create a new one:
|
// Create a new one:
|
||||||
context_ = InitForeignRef(obj_);
|
context_ = InitForeignRef(obj_);
|
||||||
} else {
|
|
||||||
// Can be called both from Native state (if ObjC or Swift code adds RC)
|
|
||||||
// and from Runnable state (Kotlin_ObjCExport_refToObjC).
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ id allocWithZoneImp(Class self, SEL _cmd, void* zone) {
|
|||||||
id result = messenger(&s, _cmd, zone);
|
id result = messenger(&s, _cmd, zone);
|
||||||
|
|
||||||
auto* typeInfo = classData->typeInfo;
|
auto* typeInfo = classData->typeInfo;
|
||||||
|
|
||||||
|
kotlin::CalledFromNativeGuard guard;
|
||||||
ObjHolder holder;
|
ObjHolder holder;
|
||||||
auto kotlinObj = AllocInstanceWithAssociatedObject(typeInfo, result, holder.slot());
|
auto kotlinObj = AllocInstanceWithAssociatedObject(typeInfo, result, holder.slot());
|
||||||
|
|
||||||
@@ -372,6 +374,7 @@ void Kotlin_objc_autoreleasePoolPop(void* ptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id Kotlin_objc_allocWithZone(Class clazz) {
|
id Kotlin_objc_allocWithZone(Class clazz) {
|
||||||
|
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||||
return objc_allocWithZone(clazz);
|
return objc_allocWithZone(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +383,7 @@ id Kotlin_objc_retain(id ptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Kotlin_objc_release(id ptr) {
|
void Kotlin_objc_release(id ptr) {
|
||||||
|
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||||
objc_release(ptr);
|
objc_release(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
-(void)dealloc {
|
-(void)dealloc {
|
||||||
refHolder.dispose();
|
refHolder.disposeFromNative();
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,9 +88,23 @@ extern "C" OBJ_GETTER(Kotlin_Interop_refFromObjC, id obj);
|
|||||||
static OBJ_GETTER(Konan_ObjCInterop_getWeakReference, KRef ref) {
|
static OBJ_GETTER(Konan_ObjCInterop_getWeakReference, KRef ref) {
|
||||||
KotlinObjCWeakReference* objcRef = (KotlinObjCWeakReference*)ref->GetAssociatedObject();
|
KotlinObjCWeakReference* objcRef = (KotlinObjCWeakReference*)ref->GetAssociatedObject();
|
||||||
|
|
||||||
id objcReferred = objc_loadWeakRetained(&objcRef->referred);
|
// objc_loadWeakRetained can call arbitrary user code, so it needs Native state:
|
||||||
|
id objcReferred = kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(objc_loadWeakRetained, &objcRef->referred);
|
||||||
|
|
||||||
|
// Kotlin_Interop_refFromObjC creates Kotlin objects, so it needs Runnable state:
|
||||||
KRef result = Kotlin_Interop_refFromObjC(objcReferred, OBJ_RESULT);
|
KRef result = Kotlin_Interop_refFromObjC(objcReferred, OBJ_RESULT);
|
||||||
objc_release(objcReferred);
|
|
||||||
|
// objc_release can call arbitrary user code, so it needs Native state:
|
||||||
|
kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(objc_release, objcReferred);
|
||||||
|
|
||||||
|
// Possible optimizations for thread state switching above:
|
||||||
|
// 1. `Kotlin_Interop_refFromObjC` typically does `objc_retain` under the hood. We could coalesce it with `objc_release` above.
|
||||||
|
// 2. `objc_loadWeakRetained` and `objc_release` typically shouldn't call arbitrary user code here:
|
||||||
|
// the latter can't deallocate the object because it is still alive by this point, so the only possibility for user code here is
|
||||||
|
// when a user overrides `_tryRetain` or `release`.
|
||||||
|
// We do this for Kotlin subclasses of Obj-C classes, which also use this implementation.
|
||||||
|
// But we could use the other weak implementation for such classes, and assume no one else overrides these methods
|
||||||
|
// (in a dangerous way).
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user