[K/N] Enforce the native thread state for skiko's _nFlushAndSubmit
`org_jetbrains_skia_DirectContext__1nFlushAndSubmit` is a function from
Skiko written in C++ and called from Kotlin through a
`@SymbolName external fun`.
As discovered in KT-75895, this function might block and wait for
another Kotlin thread. Therefore, it violates the contract of
`SymbolName`. Switching the thread state to "native" is required when
calling such a function, which `SymbolName` doesn't do.
This commit adds a hack to the compiler: when calling a function with
such a symbol name, enforce thread state switching.
The hack is configurable: it is added in the form of a binary option,
`-Xbinary=forceNativeThreadStateForFunctions=fun1;fun2`, with
`org_jetbrains_skia_DirectContext__1nFlushAndSubmit` as the default
value.
It can also be disabled with
`-Xbinary=forceNativeThreadStateForFunctions=`.
Specifying a value for `forceNativeThreadStateForFunctions` disabled
compiler caches (since callsites to listed functions might be in the
caches).
^KT-77489 Fixed
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 ad0b59a..2eb4055 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
@@ -84,6 +84,8 @@
val enableSafepointSignposts by booleanOption()
+ val forceNativeThreadStateForFunctions by listOption(StringValueParser)
+
val packFields by booleanOption()
val cInterfaceMode by option<CInterfaceGenerationMode>()
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 376c942..5f55861 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
@@ -274,6 +274,12 @@
}
} ?: false // Disabled by default because of KT-68928
+ val forceNativeThreadStateForFunctions: Set<String> =
+ configuration.get(BinaryOptions.forceNativeThreadStateForFunctions)?.toSet()
+ ?: setOf(
+ "org_jetbrains_skia_DirectContext__1nFlushAndSubmit", // KT-75895
+ )
+
val globalDataLazyInit: Boolean by lazy {
configuration.get(BinaryOptions.globalDataLazyInit) ?: true
}
@@ -598,6 +604,7 @@
internal val ignoreCacheReason = when {
optimizationsEnabled -> "for optimized compilation"
runtimeLogsEnabled -> "with runtime logs"
+ configuration.get(BinaryOptions.forceNativeThreadStateForFunctions) != null -> "with non-default forceNativeThreadStateForFunctions"
else -> null
}
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 85e8f77..95a2194 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
@@ -2598,15 +2598,41 @@
resultLifetime: Lifetime, resultSlot: LLVMValueRef?): LLVMValueRef {
check(!function.isTypedIntrinsic)
- val needsNativeThreadState = function.needsNativeThreadState
- val exceptionHandler = function.annotations.findAnnotation(RuntimeNames.filterExceptions)?.let {
- val foreignExceptionMode = ForeignExceptionMode.byValue(it.getAnnotationValueOrNull<String>("mode"))
+ val foreignExceptionModeFromAnnotation = function.annotations.findAnnotation(RuntimeNames.filterExceptions)?.let {
+ ForeignExceptionMode.byValue(it.getAnnotationValueOrNull<String>("mode"))
+ }
+
+ val needsNativeThreadState: Boolean
+ val filterExceptionWith: ForeignExceptionMode.Mode?
+
+ if (llvmCallable.name in context.config.forceNativeThreadStateForFunctions) {
+ // This is a quick hack for functions that break the contract of `SymbolName` by being blocking,
+ // and therefore need the native thread state.
+ // See e.g., KT-75895.
+ needsNativeThreadState = true
+
+ // Switching to the native thread state requires a filteringExceptionHandler,
+ // so we enforce one here.
+ // Otherwise, nothing will switch the state back to runnable in case of exception.
+ // A more flexible approach can be implemented but is not necessary for this quick hack.
+ filterExceptionWith = foreignExceptionModeFromAnnotation ?: ForeignExceptionMode.Mode.TERMINATE
+ } else {
+ needsNativeThreadState = function.needsNativeThreadState
+ filterExceptionWith = foreignExceptionModeFromAnnotation
+ }
+
+ val exceptionHandler = if (filterExceptionWith != null) {
functionGenerationContext.filteringExceptionHandler(
currentCodeContext.exceptionHandler,
- foreignExceptionMode,
+ filterExceptionWith,
needsNativeThreadState
)
- } ?: currentCodeContext.exceptionHandler
+ } else {
+ check(!needsNativeThreadState) {
+ "${llvmCallable.name} needs native thread state, but doesn't have a filtering exception handler"
+ }
+ currentCodeContext.exceptionHandler
+ }
if (needsNativeThreadState) {
functionGenerationContext.switchThreadState(ThreadState.Native)