[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:
+1
@@ -33,4 +33,5 @@ object KonanFqNames {
|
|||||||
val hasFinalizer = FqName("kotlin.native.internal.HasFinalizer")
|
val hasFinalizer = FqName("kotlin.native.internal.HasFinalizer")
|
||||||
val hasFreezeHook = FqName("kotlin.native.internal.HasFreezeHook")
|
val hasFreezeHook = FqName("kotlin.native.internal.HasFreezeHook")
|
||||||
val gcUnsafeCall = FqName("kotlin.native.internal.GCUnsafeCall")
|
val gcUnsafeCall = FqName("kotlin.native.internal.GCUnsafeCall")
|
||||||
|
val eagerInitialization = FqName("kotlin.native.EagerInitialization")
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-10
@@ -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.descriptors.*
|
||||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||||
import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation
|
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_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_STANDALONE_THREAD_LOCAL_INITIALIZER
|
||||||
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_FILE_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@{
|
using(VariableScope()) usingVariableScope@{
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
.filter { it.storageKind == FieldStorageKind.SHARED_FROZEN }
|
.filter { it.storageKind == FieldStorageKind.SHARED_FROZEN }
|
||||||
|
.filterNot { it.shouldBeInitializedEagerly }
|
||||||
.forEach { initGlobalField(it) }
|
.forEach { initGlobalField(it) }
|
||||||
ret(null)
|
ret(null)
|
||||||
}
|
}
|
||||||
@@ -390,11 +392,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
positionAtEnd(bbInitGlobals)
|
positionAtEnd(bbInitGlobals)
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
.filter { it.storageKind == FieldStorageKind.GLOBAL }
|
.filter { it.storageKind == FieldStorageKind.GLOBAL }
|
||||||
|
.filterNot { it.shouldBeInitializedEagerly }
|
||||||
.forEach { initGlobalField(it) }
|
.forEach { initGlobalField(it) }
|
||||||
br(bbInitThreadLocals)
|
br(bbInitThreadLocals)
|
||||||
positionAtEnd(bbInitThreadLocals)
|
positionAtEnd(bbInitThreadLocals)
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
||||||
|
.filterNot { it.shouldBeInitializedEagerly }
|
||||||
.forEach { initThreadLocalField(it) }
|
.forEach { initThreadLocalField(it) }
|
||||||
ret(null)
|
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.
|
// Globals initializers may contain accesses to objects, so visit them first.
|
||||||
appendingTo(bbInit) {
|
appendingTo(bbInit) {
|
||||||
if (!context.useLazyFileInitializers()) {
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
|
||||||
.filterNot { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
.filterNot { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
||||||
.forEach { initGlobalField(it) }
|
.forEach { initGlobalField(it) }
|
||||||
}
|
|
||||||
context.llvm.initializersGenerationState.moduleGlobalInitializers.forEach {
|
context.llvm.initializersGenerationState.moduleGlobalInitializers.forEach {
|
||||||
if (context.shouldContainLocationDebugInfo())
|
if (context.shouldContainLocationDebugInfo())
|
||||||
debugLocation(it.startLocation!!, it.endLocation)
|
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)
|
store(Int32(FILE_NOT_INITIALIZED).llvm, address)
|
||||||
LLVMSetInitializer(address, Int32(FILE_NOT_INITIALIZED).llvm)
|
LLVMSetInitializer(address, Int32(FILE_NOT_INITIALIZED).llvm)
|
||||||
}
|
}
|
||||||
if (!context.useLazyFileInitializers()) {
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
|
||||||
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
||||||
.forEach { initThreadLocalField(it) }
|
.forEach { initThreadLocalField(it) }
|
||||||
}
|
|
||||||
context.llvm.initializersGenerationState.moduleThreadLocalInitializers.forEach {
|
context.llvm.initializersGenerationState.moduleThreadLocalInitializers.forEach {
|
||||||
if (context.shouldContainLocationDebugInfo())
|
if (context.shouldContainLocationDebugInfo())
|
||||||
debugLocation(it.startLocation!!, it.endLocation)
|
debugLocation(it.startLocation!!, it.endLocation)
|
||||||
|
|||||||
+9
-1
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.konan.lower
|
|||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.konan.Context
|
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.FieldStorageKind
|
||||||
import org.jetbrains.kotlin.backend.konan.llvm.storageKind
|
import org.jetbrains.kotlin.backend.konan.llvm.storageKind
|
||||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
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.expressions.IrConst
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
|
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.util.setDeclarationsParent
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
@@ -39,6 +41,12 @@ internal val IrFunction.isFileInitializer: Boolean
|
|||||||
internal fun IrBuilderWithScope.irCallFileInitializer(initializer: IrFunctionSymbol) =
|
internal fun IrBuilderWithScope.irCallFileInitializer(initializer: IrFunctionSymbol) =
|
||||||
irCall(initializer).apply { putValueArgument(0, irFalse()) }
|
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.
|
// TODO: ExplicitlyExported for IR proto are not longer needed.
|
||||||
internal class FileInitializersLowering(val context: Context) : FileLoweringPass {
|
internal class FileInitializersLowering(val context: Context) : FileLoweringPass {
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irFile: IrFile) {
|
||||||
@@ -47,7 +55,7 @@ internal class FileInitializersLowering(val context: Context) : FileLoweringPass
|
|||||||
var kPropertiesField: IrField? = null
|
var kPropertiesField: IrField? = null
|
||||||
for (declaration in irFile.declarations) {
|
for (declaration in irFile.declarations) {
|
||||||
val irField = (declaration as? IrField) ?: (declaration as? IrProperty)?.backingField
|
val irField = (declaration as? IrField) ?: (declaration as? IrProperty)?.backingField
|
||||||
if (irField == null || !irField.hasNonConstInitializer) continue
|
if (irField == null || !irField.hasNonConstInitializer || irField.shouldBeInitializedEagerly) continue
|
||||||
when {
|
when {
|
||||||
irField.origin == DECLARATION_ORIGIN_KPROPERTIES_FOR_DELEGATION -> {
|
irField.origin == DECLARATION_ORIGIN_KPROPERTIES_FOR_DELEGATION -> {
|
||||||
require(kPropertiesField == null) { "Expected at most one kProperties field" }
|
require(kPropertiesField == null) { "Expected at most one kProperties field" }
|
||||||
|
|||||||
@@ -1485,6 +1485,26 @@ standaloneTest("initializers_globalInitedBeforeThreadLocal") {
|
|||||||
source = "codegen/initializers/globalInitedBeforeThreadLocal.kt"
|
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") {
|
standaloneTest("initializers_workers1") {
|
||||||
source = "codegen/initializers/workers1.kt"
|
source = "codegen/initializers/workers1.kt"
|
||||||
goldValue = "42\n3\n"
|
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)
|
||||||
|
}
|
||||||
+39
@@ -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)
|
||||||
|
}
|
||||||
+34
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user