diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt index bb98d68cdc7..4f2e4c12992 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt @@ -26,6 +26,8 @@ object BinaryOptions : BinaryOptionRegistry() { val unitSuspendFunctionObjCExport by option() + val objcExportSuspendFunctionLaunchThreadRestriction by option() + val gcSchedulerType by option() val linkRuntime by option() diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index 43173e17748..55c265d4ac0 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -112,6 +112,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration else -> WorkerExceptionHandling.LEGACY } val runtimeLogs: String? get() = configuration.get(KonanConfigKeys.RUNTIME_LOGS) + val suspendFunctionsFromAnyThreadFromObjC: Boolean by lazy { configuration.get(BinaryOptions.objcExportSuspendFunctionLaunchThreadRestriction) == ObjCExportSuspendFunctionLaunchThreadRestriction.NONE } val freezing: Freezing by lazy { val freezingMode = configuration.get(BinaryOptions.freezing) when { diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCExportSuspendFunctionLaunchThreadRestriction.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCExportSuspendFunctionLaunchThreadRestriction.kt new file mode 100644 index 00000000000..b0b8837997b --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCExportSuspendFunctionLaunchThreadRestriction.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.konan + +enum class ObjCExportSuspendFunctionLaunchThreadRestriction { + /** + * In this mode, suspend functions called from ObjC/Swift may only be called from the main thread + */ + MAIN, + + /** + * In this mode, suspend functions called from ObjC/Swift may be called from any thread + */ + NONE, + ; +} diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index c4d5cdaf87c..644e451077f 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -2775,6 +2775,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map null diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index f15f39adf67..2116ffed732 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -5564,8 +5564,10 @@ if (isAppleTarget(project)) { artifact = frameworkArtifactName library = libraryName isStatic = true + opts = ["-Xbinary=objcExportSuspendFunctionLaunchThreadRestriction=none"] } swiftSources = ['objcexport'] + swiftExtraOpts = [ '-D', 'ALLOW_SUSPEND_ANY_THREAD' ] if (isNoopGC) { swiftExtraOpts += ["-D", "NOOP_GC"] } diff --git a/kotlin-native/backend.native/tests/objcexport/coroutines.swift b/kotlin-native/backend.native/tests/objcexport/coroutines.swift index 09c60f592ee..819e094ba46 100644 --- a/kotlin-native/backend.native/tests/objcexport/coroutines.swift +++ b/kotlin-native/backend.native/tests/objcexport/coroutines.swift @@ -48,6 +48,41 @@ private func testUnitCallSimple() throws { try assertNil(error) } +private func testUnitCallNonMainDispatcher() throws { +#if LEGACY_SUSPEND_UNIT_FUNCTION_EXPORT + var result: KotlinUnit? = nil +#endif + var error: Error? = nil + var completionCalled = 0 + + let group = DispatchGroup() + +#if ALLOW_SUSPEND_ANY_THREAD +#if LEGACY_SUSPEND_UNIT_FUNCTION_EXPORT + DispatchQueue.global().async(group: group) { + CoroutinesKt.unitSuspendFun { _result, _error in + completionCalled += 1 + result = _result + error = _error + } + } +#else + DispatchQueue.global().async(group: group) { + CoroutinesKt.unitSuspendFun { _error in + completionCalled += 1 + error = _error + } + } +#endif +#endif + +#if ALLOW_SUSPEND_ANY_THREAD + group.wait() + try assertEquals(actual: completionCalled, expected: 1) +#endif +} + + private func testCallSuspendFun(doSuspend: Bool, doThrow: Bool) throws { class C {} let expectedResult = C() @@ -630,6 +665,7 @@ class CoroutinesTests : SimpleTestProvider { test("TestCallSimple", testCallSimple) test("TestCallUnitSimple", testUnitCallSimple) + test("TestCallFromNonMainDispatcher", testUnitCallNonMainDispatcher) test("TestCall", testCall) test("TestCallChain", testCallChain) test("TestOverride", testOverride) @@ -640,4 +676,4 @@ class CoroutinesTests : SimpleTestProvider { test("TestKSuspendFunctionType", testSuspendFunctionType) test("TestSuspendFunctionSwiftImpl", testSuspendFunctionSwiftImpl) } -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/main/cpp/CompilerConstants.cpp b/kotlin-native/runtime/src/main/cpp/CompilerConstants.cpp index 4e716062fba..64ec7e16e8f 100644 --- a/kotlin-native/runtime/src/main/cpp/CompilerConstants.cpp +++ b/kotlin-native/runtime/src/main/cpp/CompilerConstants.cpp @@ -16,6 +16,7 @@ RUNTIME_WEAK int32_t Kotlin_gcSchedulerType = 2; RUNTIME_WEAK int32_t Kotlin_workerExceptionHandling = 0; RUNTIME_WEAK int32_t Kotlin_freezingEnabled = 1; RUNTIME_WEAK int32_t Kotlin_freezingChecksEnabled = 1; +RUNTIME_WEAK int32_t Kotlin_suspendFunctionsFromAnyThreadFromObjC = 0; RUNTIME_WEAK const Kotlin_getSourceInfo_FunctionType Kotlin_getSourceInfo_Function = nullptr; #ifdef KONAN_ANDROID RUNTIME_WEAK int32_t Kotlin_printToAndroidLogcat = 1; @@ -37,6 +38,10 @@ ALWAYS_INLINE bool compiler::freezingChecksEnabled() noexcept { return Kotlin_freezingChecksEnabled != 0; } +ALWAYS_INLINE bool compiler::suspendFunctionsFromAnyThreadFromObjCEnabled() noexcept { + return Kotlin_suspendFunctionsFromAnyThreadFromObjC != 0; +} + ALWAYS_INLINE compiler::GCSchedulerType compiler::getGCSchedulerType() noexcept { return static_cast(Kotlin_gcSchedulerType); } diff --git a/kotlin-native/runtime/src/main/cpp/CompilerConstants.hpp b/kotlin-native/runtime/src/main/cpp/CompilerConstants.hpp index 5f8affebab8..441beeb817e 100644 --- a/kotlin-native/runtime/src/main/cpp/CompilerConstants.hpp +++ b/kotlin-native/runtime/src/main/cpp/CompilerConstants.hpp @@ -80,6 +80,7 @@ ALWAYS_INLINE inline std::string_view runtimeLogs() noexcept { bool freezingEnabled() noexcept; bool freezingChecksEnabled() noexcept; +bool suspendFunctionsFromAnyThreadFromObjCEnabled() noexcept; ALWAYS_INLINE inline int getSourceInfo(void* addr, SourceInfo *result, int result_size) { diff --git a/kotlin-native/runtime/src/main/cpp/ObjCExportCoroutines.mm b/kotlin-native/runtime/src/main/cpp/ObjCExportCoroutines.mm index f93abc9bcf8..a41a2335709 100644 --- a/kotlin-native/runtime/src/main/cpp/ObjCExportCoroutines.mm +++ b/kotlin-native/runtime/src/main/cpp/ObjCExportCoroutines.mm @@ -37,7 +37,7 @@ extern "C" OBJ_GETTER(Kotlin_ObjCExport_createContinuationArgumentImpl, KRef completionHolder, const TypeInfo** exceptionTypes); extern "C" OBJ_GETTER(Kotlin_ObjCExport_createContinuationArgument, id completion, const TypeInfo** exceptionTypes) { - if (pthread_main_np() != 1) { + if (!kotlin::compiler::suspendFunctionsFromAnyThreadFromObjCEnabled() && pthread_main_np() != 1) { [NSException raise:NSGenericException format:@"Calling Kotlin suspend functions from Swift/Objective-C is currently supported only on main thread"]; }