From 066429831e571a865655670549816e3d1e9948dc Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Fri, 24 Aug 2018 12:37:59 +0300 Subject: [PATCH] Top level values can be only accessed from the main thread. (#1922) --- CONCURRENCY.md | 17 ++++++++++ .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 7 +++- .../backend/konan/llvm/CodeGenerator.kt | 4 +++ .../kotlin/backend/konan/llvm/ContextUtils.kt | 1 + .../kotlin/backend/konan/llvm/IrToBitcode.kt | 33 +++++++++++++++---- .../backend/konan/llvm/LlvmDeclarations.kt | 9 ++--- .../backend/konan/lower/DelegationLowering.kt | 6 +++- runtime/src/main/cpp/Exceptions.h | 1 + runtime/src/main/cpp/Runtime.cpp | 7 ++++ .../main/kotlin/kotlin/native/Annotations.kt | 8 +++++ .../src/main/kotlin/kotlin/native/Runtime.kt | 17 ++++++++-- .../kotlin/native/internal/RuntimeUtils.kt | 10 ++++-- .../src/main/kotlin/DecoderWorker.kt | 1 + .../videoplayer/src/main/kotlin/SDLAudio.kt | 1 + 14 files changed, 104 insertions(+), 18 deletions(-) diff --git a/CONCURRENCY.md b/CONCURRENCY.md index 149da99099f..4007237dbae 100644 --- a/CONCURRENCY.md +++ b/CONCURRENCY.md @@ -112,3 +112,20 @@ class SharedData(rawPtr: NativePtr) : CStructVar(rawPtr) { ``` So combined with the top level variable declared above, it allows seeing the same memory from different threads and building traditional concurrent structures with platform-specific synchronization primitives. + + ## Global variables and singletons + + Frequently, global variables are a source of unintended concurrency issues, so _Kotlin/Native_ implements +following mechanisms to prevent unintended sharing of state via global objects: + + * global variables, unless specially marked, can be only accessed from the main thread (that is, the thread + _Kotlin/Native_ runtime was first initialized), if other thread access such a global, `IncorrectDereferenceException` is thrown + * for global variables marked with the `@kotlin.native.ThreadLocal` annotation each threads keeps thread-local copy, + so changes are not visible between threads + * for global variables marked with the `@kotlin.native.SharedImmutable` annotation value is shared, but frozen + before publishing, so each threads sees the same value + * singleton objects unless marked with `@kotlin.native.ThreadLocal` are frozen and shared, lazy values allowed, + unless cyclic frozen structures were attempted to be created + * enums are always frozen + + Combined, those mechanisms allows natural race-freeze programming with code reuse across platforms in MPP projects. \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index 63a79f13057..0cc6251020d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -370,7 +370,12 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val val listOfInternal = internalFunction("listOfInternal") val threadLocal = - context.builtIns.builtInsModule.findClassAcrossModuleDependencies(ClassId.topLevel(FqName("kotlin.native.ThreadLocal")))!! + context.builtIns.builtInsModule.findClassAcrossModuleDependencies( + ClassId.topLevel(FqName("kotlin.native.ThreadLocal")))!! + + val sharedImmutable = + context.builtIns.builtInsModule.findClassAcrossModuleDependencies( + ClassId.topLevel(FqName("kotlin.native.SharedImmutable")))!! private fun internalFunction(name: String): IrSimpleFunctionSymbol = symbolTable.referenceSimpleFunction(context.getInternalFunctions(name).single()) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index 5ccccd10de6..0e5d75f9937 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -305,6 +305,10 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, call(context.llvm.freezeSubgraph, listOf(value), Lifetime.IRRELEVANT, exceptionHandler) } + fun checkMainThread(exceptionHandler: ExceptionHandler) { + call(context.llvm.checkMainThread, emptyList(), Lifetime.IRRELEVANT, exceptionHandler) + } + private fun updateReturnRef(value: LLVMValueRef, address: LLVMValueRef) { call(context.llvm.updateReturnRefFunction, listOf(address, value)) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 1e2bdd9eb69..4ad38635d25 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -395,6 +395,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded") val mutationCheck = importRtFunction("MutationCheck") val freezeSubgraph = importRtFunction("FreezeSubgraph") + val checkMainThread = importRtFunction("CheckIsMainThread") val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") } val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 05c163376c1..00cd8590077 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.irasdescriptors.* import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport import org.jetbrains.kotlin.backend.konan.optimizations.* +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality @@ -49,12 +50,27 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe private val threadLocalAnnotationFqName = FqName("kotlin.native.ThreadLocal") +private val sharedAnnotationFqName = FqName("kotlin.native.SharedImmutable") +// TODO: maybe unannotated singleton objects shall be accessed from main thread only as well? val IrClass.objectIsShared get() = !descriptor.annotations.hasAnnotation(threadLocalAnnotationFqName) +val IrField.isThreadLocal get() = + descriptor.annotations.hasAnnotation(threadLocalAnnotationFqName) + val IrField.isShared get() = - !descriptor.annotations.hasAnnotation(threadLocalAnnotationFqName) && !descriptor.isVar + descriptor.annotations.hasAnnotation(sharedAnnotationFqName) + +val IrField.isMainOnly get() = + !descriptor.annotations.hasAnnotation(threadLocalAnnotationFqName) && + !descriptor.annotations.hasAnnotation(sharedAnnotationFqName) && + !descriptor.isDelegated + +val IrField.isMainOnlyNonPrimitive get() = when { + KotlinBuiltIns.isPrimitiveType(descriptor.type) -> false + else -> isMainOnly + } internal fun emitLLVM(context: Context, phaser: PhaseManager) { val irModule = context.irModule!! @@ -404,7 +420,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map?) { - if (it.isShared) { + if (!it.isThreadLocal) { val initialization = evaluateExpression(it.initializer!!.expression) val address = context.llvmDeclarations.forStaticField(it).storage freeze(initialization, currentCodeContext.exceptionHandler) @@ -419,7 +435,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map?) { - if (!it.isShared) { + if (it.isThreadLocal) { val initialization = evaluateExpression(it.initializer!!.expression) val address = context.llvmDeclarations.forStaticField(it).storage storeAny(initialization, address) @@ -432,7 +448,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map?, length: Int) {