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) {