[K/N][IR][codegen] Implemented eager initialization

Properties marked with @EagerInitialization will be initialized at program/worker start
even in lazy initialization mode
This commit is contained in:
Igor Chevdar
2021-08-10 17:57:01 +05:00
parent e159392d22
commit 81ce59cf48
8 changed files with 172 additions and 11 deletions
@@ -33,4 +33,5 @@ object KonanFqNames {
val hasFinalizer = FqName("kotlin.native.internal.HasFinalizer")
val hasFreezeHook = FqName("kotlin.native.internal.HasFreezeHook")
val gcUnsafeCall = FqName("kotlin.native.internal.GCUnsafeCall")
val eagerInitialization = FqName("kotlin.native.EagerInitialization")
}
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.backend.konan.cgen.CBridgeOrigin
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation
import org.jetbrains.kotlin.backend.konan.lower.*
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
@@ -370,6 +371,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
using(VariableScope()) usingVariableScope@{
context.llvm.initializersGenerationState.topLevelFields
.filter { it.storageKind == FieldStorageKind.SHARED_FROZEN }
.filterNot { it.shouldBeInitializedEagerly }
.forEach { initGlobalField(it) }
ret(null)
}
@@ -390,11 +392,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
positionAtEnd(bbInitGlobals)
context.llvm.initializersGenerationState.topLevelFields
.filter { it.storageKind == FieldStorageKind.GLOBAL }
.filterNot { it.shouldBeInitializedEagerly }
.forEach { initGlobalField(it) }
br(bbInitThreadLocals)
positionAtEnd(bbInitThreadLocals)
context.llvm.initializersGenerationState.topLevelFields
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
.filterNot { it.shouldBeInitializedEagerly }
.forEach { initThreadLocalField(it) }
ret(null)
}
@@ -492,11 +496,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
// Globals initializers may contain accesses to objects, so visit them first.
appendingTo(bbInit) {
if (!context.useLazyFileInitializers()) {
context.llvm.initializersGenerationState.topLevelFields
.filterNot { it.storageKind == FieldStorageKind.THREAD_LOCAL }
.forEach { initGlobalField(it) }
}
context.llvm.initializersGenerationState.topLevelFields
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
.filterNot { it.storageKind == FieldStorageKind.THREAD_LOCAL }
.forEach { initGlobalField(it) }
context.llvm.initializersGenerationState.moduleGlobalInitializers.forEach {
if (context.shouldContainLocationDebugInfo())
debugLocation(it.startLocation!!, it.endLocation)
@@ -511,11 +514,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
store(Int32(FILE_NOT_INITIALIZED).llvm, address)
LLVMSetInitializer(address, Int32(FILE_NOT_INITIALIZED).llvm)
}
if (!context.useLazyFileInitializers()) {
context.llvm.initializersGenerationState.topLevelFields
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
.forEach { initThreadLocalField(it) }
}
context.llvm.initializersGenerationState.topLevelFields
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
.forEach { initThreadLocalField(it) }
context.llvm.initializersGenerationState.moduleThreadLocalInitializers.forEach {
if (context.shouldContainLocationDebugInfo())
debugLocation(it.startLocation!!, it.endLocation)
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.KonanFqNames
import org.jetbrains.kotlin.backend.konan.llvm.FieldStorageKind
import org.jetbrains.kotlin.backend.konan.llvm.storageKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
@@ -20,6 +21,7 @@ import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.setDeclarationsParent
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -39,6 +41,12 @@ internal val IrFunction.isFileInitializer: Boolean
internal fun IrBuilderWithScope.irCallFileInitializer(initializer: IrFunctionSymbol) =
irCall(initializer).apply { putValueArgument(0, irFalse()) }
internal val IrField.shouldBeInitializedEagerly: Boolean
get() {
val annotations = correspondingPropertySymbol?.owner?.annotations ?: annotations
return annotations.hasAnnotation(KonanFqNames.eagerInitialization)
}
// TODO: ExplicitlyExported for IR proto are not longer needed.
internal class FileInitializersLowering(val context: Context) : FileLoweringPass {
override fun lower(irFile: IrFile) {
@@ -47,7 +55,7 @@ internal class FileInitializersLowering(val context: Context) : FileLoweringPass
var kPropertiesField: IrField? = null
for (declaration in irFile.declarations) {
val irField = (declaration as? IrField) ?: (declaration as? IrProperty)?.backingField
if (irField == null || !irField.hasNonConstInitializer) continue
if (irField == null || !irField.hasNonConstInitializer || irField.shouldBeInitializedEagerly) continue
when {
irField.origin == DECLARATION_ORIGIN_KPROPERTIES_FOR_DELEGATION -> {
require(kPropertiesField == null) { "Expected at most one kProperties field" }
@@ -1485,6 +1485,26 @@ standaloneTest("initializers_globalInitedBeforeThreadLocal") {
source = "codegen/initializers/globalInitedBeforeThreadLocal.kt"
}
standaloneTest("initializers_eagerInitializationGlobal1") {
enabled = project.globalTestArgs.contains('-Xir-property-lazy-initialization')
source = "codegen/initializers/eagerInitializationGlobal1.kt"
}
standaloneTest("initializers_eagerInitializationGlobal2") {
enabled = project.globalTestArgs.contains('-Xir-property-lazy-initialization')
source = "codegen/initializers/eagerInitializationGlobal2.kt"
}
standaloneTest("initializers_eagerInitializationThreadLocal1") {
enabled = project.globalTestArgs.contains('-Xir-property-lazy-initialization')
source = "codegen/initializers/eagerInitializationThreadLocal1.kt"
}
standaloneTest("initializers_eagerInitializationThreadLocal2") {
enabled = project.globalTestArgs.contains('-Xir-property-lazy-initialization')
source = "codegen/initializers/eagerInitializationThreadLocal2.kt"
}
standaloneTest("initializers_workers1") {
source = "codegen/initializers/workers1.kt"
goldValue = "42\n3\n"
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2021 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.
*/
// FILE: lib.kt
var z1 = false
var z2 = false
// FILE: lib2.kt
import kotlin.test.*
@OptIn(kotlin.ExperimentalStdlibApi::class)
@EagerInitialization
val x = foo()
private fun foo(): Int {
z1 = true
return 42
}
// Will be initialized since [x]'s initializer calls a function from the file.
val y = run { z2 = true; 117 }
// FILE: main.kt
import kotlin.test.*
fun main() {
assertTrue(z1)
assertTrue(z2)
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2021 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.
*/
// FILE: lib.kt
var z1 = false
var z2 = false
// FILE: lib2.kt
import kotlin.test.*
@OptIn(kotlin.ExperimentalStdlibApi::class)
@EagerInitialization
val x = run { z1 = true; 42 }
// Won't be initialized (cause no function from the file will be called during [x] initialization).
val y = run { z2 = true; 117 }
// FILE: main.kt
import kotlin.test.*
fun main() {
assertTrue(z1)
assertFalse(z2)
}
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2021 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.
*/
// FILE: lib.kt
@ThreadLocal
var z1 = false
var z2 = false
@ThreadLocal
var z3 = false
// FILE: lib2.kt
import kotlin.test.*
@OptIn(kotlin.ExperimentalStdlibApi::class)
@EagerInitialization
@ThreadLocal
val x = foo()
private fun foo(): Int {
z1 = true
return 42
}
// Both will be initialized since [x]'s initializer calls a function from the file.
val y1 = run { z2 = true; 117 }
@ThreadLocal
val y2 = run { z3 = true; 117 }
// FILE: main.kt
import kotlin.test.*
fun main() {
assertTrue(z1)
assertTrue(z2)
assertTrue(z3)
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2021 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.
*/
// FILE: lib.kt
@ThreadLocal
var z1 = false
var z2 = false
@ThreadLocal
var z3 = false
// FILE: lib2.kt
import kotlin.test.*
@OptIn(kotlin.ExperimentalStdlibApi::class)
@EagerInitialization
@ThreadLocal
val x = run { z1 = true; 42 }
// Won't be initialized (cause no function from the file will be called during [x] initialization).
val y1 = run { z2 = true; 117 }
@ThreadLocal
val y2 = run { z3 = true; 117 }
// FILE: main.kt
import kotlin.test.*
fun main() {
assertTrue(z1)
assertFalse(z2)
assertFalse(z3)
}