[K/N] Apply initializers lowering to any declaration container

This commit is contained in:
Pavel Kunyavskiy
2022-07-08 16:09:52 +02:00
committed by Space Team
parent 9516094755
commit 7006eb938d
11 changed files with 184 additions and 163 deletions
@@ -163,7 +163,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config), Confi
}
fun needGlobalInit(field: IrField): Boolean {
if (field.descriptor.containingDeclaration !is PackageFragmentDescriptor) return false
if (field.descriptor.containingDeclaration !is PackageFragmentDescriptor) return field.isStatic
// TODO: add some smartness here. Maybe if package of the field is in never accessed
// assume its global init can be actually omitted.
return true
@@ -184,7 +184,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
configuration.get(BinaryOptions.mimallocUseDefaultOptions) ?: false
}
val mimallocUseCompaction by lazy {
val mimallocUseCompaction: Boolean by lazy {
// Turned off by default, because it slows down allocation.
configuration.get(BinaryOptions.mimallocUseCompaction) ?: false
}
@@ -573,8 +573,6 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) {
}
disableUnless(verifyBitcodePhase, config.needCompilerVerification || getBoolean(KonanConfigKeys.VERIFY_BITCODE))
disableUnless(staticInitializersPhase, config.propertyLazyInitialization)
disableUnless(removeRedundantCallsToStaticInitializersPhase, config.propertyLazyInitialization)
disableUnless(removeRedundantSafepointsPhase, config.memoryModel == MemoryModel.EXPERIMENTAL)
@@ -101,11 +101,11 @@ internal val dcePhase = makeKonanModuleOpPhase(
val referencedFunctions = mutableSetOf<IrFunction>()
callGraph.rootExternalFunctions.forEach {
if (!it.isTopLevelFieldInitializer)
if (!it.isStaticFieldInitializer)
referencedFunctions.add(it.irFunction ?: error("No IR for: $it"))
}
for (node in callGraph.directEdges.values) {
if (!node.symbol.isTopLevelFieldInitializer)
if (!node.symbol.isStaticFieldInitializer)
referencedFunctions.add(node.symbol.irFunction ?: error("No IR for: ${node.symbol}"))
node.callSites.forEach {
assert (!it.isVirtual) { "There should be no virtual calls in the call graph, but was: ${it.actualCallee}" }
@@ -235,25 +235,30 @@ internal interface ContextUtils : RuntimeAware {
*/
internal fun stringAsBytes(str: String) = str.toByteArray(Charsets.UTF_8)
internal class InitializersGenerationState {
val fileGlobalInitStates = mutableMapOf<IrFile, LLVMValueRef>()
val fileThreadLocalInitStates = mutableMapOf<IrFile, AddressAccess>()
internal class ScopeInitializersGenerationState {
val topLevelFields = mutableListOf<IrField>()
var globalInitFunction: IrFunction? = null
var globalInitState: LLVMValueRef? = null
var threadLocalInitFunction: IrFunction? = null
var threadLocalInitState: AddressAccess? = null
val globalSharedObjects = mutableSetOf<LLVMValueRef>()
fun isEmpty() = topLevelFields.isEmpty() &&
globalInitState == null &&
threadLocalInitState == null &&
globalSharedObjects.isEmpty()
}
fun reset() {
topLevelFields.clear()
globalInitFunction = null
globalInitState = null
threadLocalInitFunction = null
threadLocalInitState = null
internal class InitializersGenerationState {
val fileGlobalInitStates = mutableMapOf<IrDeclarationContainer, LLVMValueRef>()
val fileThreadLocalInitStates = mutableMapOf<IrDeclarationContainer, AddressAccess>()
var scopeState = ScopeInitializersGenerationState()
fun reset(newState: ScopeInitializersGenerationState) : ScopeInitializersGenerationState {
val t = scopeState
scopeState = newState
return t
}
fun isEmpty() = topLevelFields.isEmpty() && globalInitState == null && threadLocalInitState == null
}
internal class ConstInt1(llvm: Llvm, val value: Boolean) : ConstValue {
@@ -15,9 +15,9 @@ 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
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_STATIC_THREAD_LOCAL_INITIALIZER
import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrElement
@@ -364,24 +364,16 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
}
private fun runAndProcessInitializers(konanLibrary: KotlinLibrary?, f: () -> Unit) {
// TODO: collect those two in one place.
llvm.fileUsesThreadLocalObjects = false
llvm.globalSharedObjects.clear()
llvm.initializersGenerationState.reset()
f()
llvm.initializersGenerationState.globalInitFunction?.let { fileInitFunction ->
private fun buildInitializerFunctions(scopeState: ScopeInitializersGenerationState) {
scopeState.globalInitFunction?.let { fileInitFunction ->
generateFunction(codegen, fileInitFunction, fileInitFunction.location(start = true), fileInitFunction.location(start = false)) {
using(FunctionScope(fileInitFunction, this)) {
val parameterScope = ParameterScope(fileInitFunction, functionGenerationContext)
using(parameterScope) usingParameterScope@{
using(VariableScope()) usingVariableScope@{
llvm.initializersGenerationState.topLevelFields
scopeState.topLevelFields
.filter { it.storageKind(context) != FieldStorageKind.THREAD_LOCAL }
.filterNot { it.shouldBeInitializedEagerly }
.filterNot { context.shouldBeInitializedEagerly(it) }
.forEach { initGlobalField(it) }
ret(null)
}
@@ -390,15 +382,15 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
}
llvm.initializersGenerationState.threadLocalInitFunction?.let { fileInitFunction ->
scopeState.threadLocalInitFunction?.let { fileInitFunction ->
generateFunction(codegen, fileInitFunction, fileInitFunction.location(start = true), fileInitFunction.location(start = false)) {
using(FunctionScope(fileInitFunction, this)) {
val parameterScope = ParameterScope(fileInitFunction, functionGenerationContext)
using(parameterScope) usingParameterScope@{
using(VariableScope()) usingVariableScope@{
llvm.initializersGenerationState.topLevelFields
scopeState.topLevelFields
.filter { it.storageKind(context) == FieldStorageKind.THREAD_LOCAL }
.filterNot { it.shouldBeInitializedEagerly }
.filterNot { context.shouldBeInitializedEagerly(it) }
.forEach { initThreadLocalField(it) }
ret(null)
}
@@ -406,15 +398,17 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
}
}
}
if (!llvm.fileUsesThreadLocalObjects && llvm.globalSharedObjects.isEmpty()
&& llvm.initializersGenerationState.isEmpty()) {
return
private fun runAndProcessInitializers(konanLibrary: KotlinLibrary?, f: () -> Unit) {
val oldScopeState = llvm.initializersGenerationState.reset(ScopeInitializersGenerationState())
f()
val scopeState = llvm.initializersGenerationState.reset(oldScopeState)
scopeState.takeIf { !it.isEmpty() }?.let {
buildInitializerFunctions(it)
val initNode = createInitNode(createInitBody(it))
llvm.irStaticInitializers.add(IrStaticInitializer(konanLibrary, createInitCtor(initNode)))
}
// Create global initialization records.
val initNode = createInitNode(createInitBody())
llvm.irStaticInitializers.add(IrStaticInitializer(konanLibrary, createInitCtor(initNode)))
}
//-------------------------------------------------------------------------//
@@ -468,7 +462,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val FILE_NOT_INITIALIZED = 0
val FILE_INITIALIZED = 2
private fun createInitBody(): LLVMValueRef {
private fun createInitBody(state: ScopeInitializersGenerationState): LLVMValueRef {
val initFunction = addLlvmFunctionWithDefaultAttributes(
context,
llvm.module,
@@ -495,21 +489,16 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
// Globals initializers may contain accesses to objects, so visit them first.
appendingTo(bbInit) {
llvm.initializersGenerationState.topLevelFields
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
state.topLevelFields
.filter { context.shouldBeInitializedEagerly(it) }
.filterNot { it.storageKind(context) == FieldStorageKind.THREAD_LOCAL }
.forEach { initGlobalField(it) }
ret(null)
}
appendingTo(bbLocalInit) {
llvm.initializersGenerationState.threadLocalInitState?.let {
val address = it.getAddress(functionGenerationContext)
store(llvm.int32(FILE_NOT_INITIALIZED), address)
LLVMSetInitializer(address, llvm.int32(FILE_NOT_INITIALIZED))
}
llvm.initializersGenerationState.topLevelFields
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
state.topLevelFields
.filter { context.shouldBeInitializedEagerly(it) }
.filter { it.storageKind(context) == FieldStorageKind.THREAD_LOCAL }
.forEach { initThreadLocalField(it) }
ret(null)
@@ -524,7 +513,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
appendingTo(bbGlobalDeinit) {
llvm.initializersGenerationState.topLevelFields
state.topLevelFields
// Only if a subject for memory management.
.forEach { irField ->
if (irField.type.binaryTypeIsReference() && irField.storageKind(context) != FieldStorageKind.THREAD_LOCAL) {
@@ -534,10 +523,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
storeHeapRef(codegen.kNullObjHeaderPtr, address)
}
}
llvm.globalSharedObjects.forEach { address ->
state.globalSharedObjects.forEach { address ->
storeHeapRef(codegen.kNullObjHeaderPtr, address)
}
llvm.initializersGenerationState.globalInitState?.let {
state.globalInitState?.let {
store(llvm.int32(FILE_NOT_INITIALIZED), it)
}
ret(null)
@@ -774,16 +763,24 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}.toMap()
}
private fun getGlobalInitStateFor(file: IrFile): LLVMValueRef =
llvm.initializersGenerationState.fileGlobalInitStates.getOrPut(file) {
codegen.addGlobal("state_global$${file.fileEntry.name}", llvm.int32Type, false).also {
private val IrDeclarationContainer.initVariableSuffix get() = when (this) {
is IrFile -> "${fqName}\$${fileEntry.name}"
else -> fqNameForIrSerialization.asString()
}
private fun getGlobalInitStateFor(container: IrDeclarationContainer): LLVMValueRef =
llvm.initializersGenerationState.fileGlobalInitStates.getOrPut(container) {
codegen.addGlobal("state_global$${container.initVariableSuffix}", llvm.int32Type, false).also {
LLVMSetInitializer(it, llvm.int32(FILE_NOT_INITIALIZED))
LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage)
}
}
private fun getThreadLocalInitStateFor(file: IrFile): AddressAccess =
llvm.initializersGenerationState.fileThreadLocalInitStates.getOrPut(file) {
codegen.addKotlinThreadLocal("state_thread_local$${file.fileEntry.name}", llvm.int32Type)
private fun getThreadLocalInitStateFor(container: IrDeclarationContainer): AddressAccess =
llvm.initializersGenerationState.fileThreadLocalInitStates.getOrPut(container) {
codegen.addKotlinThreadLocal("state_thread_local$${container.initVariableSuffix}", llvm.int32Type).also {
LLVMSetInitializer((it as GlobalAddressAccess).getAddress(null), llvm.int32(FILE_NOT_INITIALIZED))
}
}
override fun visitFunction(declaration: IrFunction) {
@@ -791,22 +788,23 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val body = declaration.body
if (declaration.origin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER) {
require(llvm.initializersGenerationState.globalInitFunction == null) { "There can only be at most one global file initializer" }
val scopeState = llvm.initializersGenerationState.scopeState
if (declaration.origin == DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER) {
require(scopeState.globalInitFunction == null) { "There can only be at most one global file initializer" }
require(body == null) { "The body of file initializer should be null" }
require(declaration.valueParameters.isEmpty()) { "File initializer must be parameterless" }
require(declaration.returnsUnit()) { "File initializer must return Unit" }
llvm.initializersGenerationState.globalInitFunction = declaration
llvm.initializersGenerationState.globalInitState = getGlobalInitStateFor(declaration.parent as IrFile)
scopeState.globalInitFunction = declaration
scopeState.globalInitState = getGlobalInitStateFor(declaration.parent as IrDeclarationContainer)
}
if (declaration.origin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
|| declaration.origin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER) {
require(llvm.initializersGenerationState.threadLocalInitFunction == null) { "There can only be at most one thread local file initializer" }
if (declaration.origin == DECLARATION_ORIGIN_STATIC_THREAD_LOCAL_INITIALIZER
|| declaration.origin == DECLARATION_ORIGIN_STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER) {
require(scopeState.threadLocalInitFunction == null) { "There can only be at most one thread local file initializer" }
require(body == null) { "The body of file initializer should be null" }
require(declaration.valueParameters.isEmpty()) { "File initializer must be parameterless" }
require(declaration.returnsUnit()) { "File initializer must return Unit" }
llvm.initializersGenerationState.threadLocalInitFunction = declaration
llvm.initializersGenerationState.threadLocalInitState = getThreadLocalInitStateFor(declaration.parent as IrFile)
scopeState.threadLocalInitFunction = declaration
scopeState.threadLocalInitState = getThreadLocalInitStateFor(declaration.parent as IrDeclarationContainer)
}
@@ -879,8 +877,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
return
}
using(ClassScope(declaration)) {
declaration.declarations.forEach {
it.acceptVoid(this)
runAndProcessInitializers(declaration.konanLibrary) {
declaration.declarations.forEach {
it.acceptVoid(this)
}
}
}
@@ -947,7 +947,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
// (Cannot do this before the global is initialized).
LLVMSetLinkage(globalProperty, LLVMLinkage.LLVMInternalLinkage)
}
llvm.initializersGenerationState.topLevelFields.add(declaration)
llvm.initializersGenerationState.scopeState.topLevelFields.add(declaration)
}
}
@@ -2399,15 +2399,15 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
return when {
function.isTypedIntrinsic -> intrinsicGenerator.evaluateCall(callee, args, resultSlot)
function.isBuiltInOperator -> evaluateOperatorCall(callee, args)
function.origin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER -> evaluateFileGlobalInitializerCall(function)
function.origin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER -> evaluateFileThreadLocalInitializerCall(function)
function.origin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER -> evaluateFileStandaloneThreadLocalInitializerCall(function)
function.origin == DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER -> evaluateFileGlobalInitializerCall(function)
function.origin == DECLARATION_ORIGIN_STATIC_THREAD_LOCAL_INITIALIZER -> evaluateFileThreadLocalInitializerCall(function)
function.origin == DECLARATION_ORIGIN_STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER -> evaluateFileStandaloneThreadLocalInitializerCall(function)
else -> evaluateSimpleFunctionCall(function, args, resultLifetime, callee.superQualifierSymbol?.owner, resultSlot)
}
}
private fun evaluateFileGlobalInitializerCall(fileInitializer: IrFunction) = with(functionGenerationContext) {
val statePtr = getGlobalInitStateFor(fileInitializer.parent as IrFile)
val statePtr = getGlobalInitStateFor(fileInitializer.parent as IrDeclarationContainer)
val initializerPtr = with(codegen) { fileInitializer.llvmFunction.llvmValue }
val bbInit = basicBlock("label_init", null)
@@ -2426,8 +2426,8 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
private fun evaluateFileThreadLocalInitializerCall(fileInitializer: IrFunction) = with(functionGenerationContext) {
val globalStatePtr = getGlobalInitStateFor(fileInitializer.parent as IrFile)
val localState = getThreadLocalInitStateFor(fileInitializer.parent as IrFile)
val globalStatePtr = getGlobalInitStateFor(fileInitializer.parent as IrDeclarationContainer)
val localState = getThreadLocalInitStateFor(fileInitializer.parent as IrDeclarationContainer)
val localStatePtr = localState.getAddress(functionGenerationContext)
val initializerPtr = with(codegen) { fileInitializer.llvmFunction.llvmValue }
@@ -2453,7 +2453,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
private fun evaluateFileStandaloneThreadLocalInitializerCall(fileInitializer: IrFunction) = with(functionGenerationContext) {
val state = getThreadLocalInitStateFor(fileInitializer.parent as IrFile)
val state = getThreadLocalInitStateFor(fileInitializer.parent as IrDeclarationContainer)
val statePtr = state.getAddress(functionGenerationContext)
val initializerPtr = with(codegen) { fileInitializer.llvmFunction.llvmValue }
@@ -7,6 +7,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.ConfigChecks
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.DECLARATION_ORIGIN_ENTRY_POINT
import org.jetbrains.kotlin.backend.konan.KonanFqNames
@@ -14,6 +15,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.FieldStorageKind
import org.jetbrains.kotlin.backend.konan.llvm.needsGCRegistration
import org.jetbrains.kotlin.backend.konan.llvm.storageKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
@@ -23,34 +25,55 @@ import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.hasNonConstInitializer
import org.jetbrains.kotlin.ir.util.simpleFunctions
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.name.Name
internal object DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER : IrDeclarationOriginImpl("FILE_GLOBAL_INITIALIZER")
internal object DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("FILE_THREAD_LOCAL_INITIALIZER")
internal object DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("FILE_STANDALONE_THREAD_LOCAL_INITIALIZER")
internal object DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER : IrDeclarationOriginImpl("STATIC_GLOBAL_INITIALIZER")
internal object DECLARATION_ORIGIN_STATIC_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("STATIC_THREAD_LOCAL_INITIALIZER")
internal object DECLARATION_ORIGIN_STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER")
internal val IrFunction.isFileInitializer: Boolean
get() = origin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
|| origin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
|| origin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
internal val IrFunction.isStaticInitializer: Boolean
get() = origin == DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER
|| origin == DECLARATION_ORIGIN_STATIC_THREAD_LOCAL_INITIALIZER
|| origin == DECLARATION_ORIGIN_STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER
internal fun IrBuilderWithScope.irCallFileInitializer(initializer: IrFunctionSymbol) =
irCall(initializer)
internal val IrField.shouldBeInitializedEagerly: Boolean
get() {
val annotations = correspondingPropertySymbol?.owner?.annotations ?: annotations
return annotations.hasAnnotation(KonanFqNames.eagerInitialization)
internal fun ConfigChecks.shouldBeInitializedEagerly(irField: IrField): Boolean {
if (irField.parent is IrFile || irField.correspondingPropertySymbol?.owner?.parent is IrFile) {
if (!useLazyFileInitializers()) return true
}
val annotations = irField.correspondingPropertySymbol?.owner?.annotations ?: irField.annotations
return annotations.hasAnnotation(KonanFqNames.eagerInitialization)
}
// TODO: ExplicitlyExported for IR proto are not longer needed.
internal class StaticInitializersLowering(val context: Context) : FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFile(declaration: IrFile) {
processDeclarationContainter(declaration)
declaration.acceptChildrenVoid(this)
}
override fun visitClass(declaration: IrClass) {
processDeclarationContainter(declaration)
declaration.acceptChildrenVoid(this)
}
})
}
fun processDeclarationContainter(container: IrDeclarationContainer) {
var requireGlobalInitializer = false
var requireThreadLocalInitializer = false
for (declaration in irFile.declarations) {
for (declaration in container.declarations) {
val irField = (declaration as? IrField) ?: (declaration as? IrProperty)?.backingField
if (irField == null || !irField.needsInitializationAtRuntime || irField.shouldBeInitializedEagerly) continue
if (irField == null || !irField.isStatic || !irField.needsInitializationAtRuntime || context.shouldBeInitializedEagerly(irField)) continue
if (irField.storageKind(context) != FieldStorageKind.THREAD_LOCAL) {
requireGlobalInitializer = true
} else {
@@ -64,18 +87,18 @@ internal class StaticInitializersLowering(val context: Context) : FileLoweringPa
val globalInitFunction =
if (requireGlobalInitializer)
buildInitFileFunction(irFile, "\$init_global", DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER)
buildInitFileFunction(container, "\$init_global", DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER)
else null
val threadLocalInitFunction =
if (requireThreadLocalInitializer)
buildInitFileFunction(irFile, "\$init_thread_local",
buildInitFileFunction(container, "\$init_thread_local",
if (requireGlobalInitializer)
DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
else DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
DECLARATION_ORIGIN_STATIC_THREAD_LOCAL_INITIALIZER
else DECLARATION_ORIGIN_STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER
)
else null
irFile.simpleFunctions()
container.simpleFunctions()
.filterNot { it.origin == DECLARATION_ORIGIN_ENTRY_POINT }
.forEach {
val body = it.body ?: return@forEach
@@ -89,7 +112,7 @@ internal class StaticInitializersLowering(val context: Context) : FileLoweringPa
}
}
private fun buildInitFileFunction(irFile: IrFile, name: String, origin: IrDeclarationOrigin) = context.irFactory.buildFun {
private fun buildInitFileFunction(container: IrDeclarationContainer, name: String, origin: IrDeclarationOrigin) = context.irFactory.buildFun {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
this.origin = origin
@@ -97,8 +120,8 @@ internal class StaticInitializersLowering(val context: Context) : FileLoweringPa
visibility = DescriptorVisibilities.PRIVATE
returnType = context.irBuiltIns.unitType
}.apply {
parent = irFile
irFile.declarations.add(0, this)
parent = container
container.declarations.add(0, this)
}
private val IrField.needsInitializationAtRuntime: Boolean
@@ -205,7 +205,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
}
override fun visitField(declaration: IrField) {
if (declaration.parent is IrFile)
if (declaration.isStatic)
declaration.initializer?.let {
context.logMultiple {
+"Analysing global field ${declaration.descriptor}"
@@ -110,7 +110,7 @@ internal object DataFlowIR {
}
object FunctionAttributes {
val IS_TOP_LEVEL_FIELD_INITIALIZER = 1
val IS_STATIC_FIELD_INITIALIZER = 1
val RETURNS_UNIT = 4
val RETURNS_NOTHING = 8
val EXPLICITLY_EXPORTED = 16
@@ -122,7 +122,7 @@ internal object DataFlowIR {
lateinit var parameters: Array<FunctionParameter>
lateinit var returnParameter: FunctionParameter
val isTopLevelFieldInitializer = attributes.and(FunctionAttributes.IS_TOP_LEVEL_FIELD_INITIALIZER) != 0
val isStaticFieldInitializer = attributes.and(FunctionAttributes.IS_STATIC_FIELD_INITIALIZER) != 0
val returnsUnit = attributes.and(FunctionAttributes.RETURNS_UNIT) != 0
val returnsNothing = attributes.and(FunctionAttributes.RETURNS_NOTHING) != 0
val explicitlyExported = attributes.and(FunctionAttributes.EXPLICITLY_EXPORTED) != 0
@@ -656,8 +656,8 @@ internal object DataFlowIR {
private fun mapPropertyInitializer(irField: IrField): FunctionSymbol {
functionMap[irField]?.let { return it }
assert(irField.parent !is IrClass) { "All local properties initializers should've been lowered" }
val attributes = FunctionAttributes.IS_TOP_LEVEL_FIELD_INITIALIZER or FunctionAttributes.RETURNS_UNIT
assert(irField.isStatic) { "All local properties initializers should've been lowered" }
val attributes = FunctionAttributes.IS_STATIC_FIELD_INITIALIZER or FunctionAttributes.RETURNS_UNIT
val symbol = FunctionSymbol.Private(privateFunIndex++, module, -1, attributes, irField, null, takeName { "${irField.computeSymbolName()}_init" })
functionMap[irField] = symbol
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlock
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.ir.isBoxOrUnboxCall
import org.jetbrains.kotlin.backend.konan.optimizations.DevirtualizationAnalysis.irCoerce
import org.jetbrains.kotlin.backend.konan.util.IntArrayList
import org.jetbrains.kotlin.backend.konan.util.LongArrayList
import org.jetbrains.kotlin.descriptors.ClassKind
@@ -83,8 +82,8 @@ internal object DevirtualizationAnalysis {
// TODO: Are globals initializers always called whether they are actually reachable from roots or not?
// TODO: With the changed semantics of global initializers this is no longer the case - rework.
val globalInitializers =
moduleDFG.symbolTable.functionMap.values.filter { it.isTopLevelFieldInitializer } +
externalModulesDFG.functionDFGs.keys.filter { it.isTopLevelFieldInitializer }
moduleDFG.symbolTable.functionMap.values.filter { it.isStaticFieldInitializer } +
externalModulesDFG.functionDFGs.keys.filter { it.isStaticFieldInitializer }
val explicitlyExported =
moduleDFG.symbolTable.functionMap.values.filter { it.explicitlyExported } +
@@ -58,13 +58,13 @@ internal object StaticInitializersOptimization {
val callSitesRequiringGlobalInitializerCall: Set<IrFunctionAccessExpression>,
val callSitesRequiringThreadLocalInitializerCall: Set<IrFunctionAccessExpression>)
private class InitializedFiles(val fileIds: Map<IrFile, Int>) {
private class InitializedContainers(val containerIds: Map<IrDeclarationContainer, Int>) {
val afterCall = mutableMapOf<IrFunction, BitSet>()
val beforeCallGlobal = mutableMapOf<IrFunction, BitSet>()
val beforeCallThreadLocal = mutableMapOf<IrFunction, BitSet>()
}
private val invalidFileId = 0
private val invalidContainerId = 0
private class InterproceduralAnalysis(val context: Context, val callGraph: CallGraph,
val rootSet: Set<IrFunction>) {
@@ -109,20 +109,18 @@ internal object StaticInitializersOptimization {
+""
}
var fileId = invalidFileId
val fileIds = mutableMapOf<IrFile, Int>()
for (node in callGraph.directEdges.values) {
val callerFile = node.symbol.irFile
if (callerFile != null && fileIds[callerFile] == null)
fileIds[callerFile] = ++fileId
for (callSite in node.callSites) {
val calleeFile = callSite.actualCallee.irFile
if (calleeFile != null && fileIds[calleeFile] == null)
fileIds[calleeFile] = ++fileId
val functions = buildSet {
callGraph.directEdges.values.forEach {
add(it.symbol.irFunction)
it.callSites.forEach { callSite -> add(callSite.actualCallee.irFunction) }
}
}
val containers = functions
.mapNotNull { it?.calledInitializer }
.distinct()
val containerIds = containers.indices.associate { containers[it] to it + 1 }
val initializedFiles = InitializedFiles(fileIds)
val initializedFiles = InitializedContainers(containerIds)
context.log { "FIRST PHASE: compute initialized after call" }
@@ -163,11 +161,10 @@ internal object StaticInitializersOptimization {
): Set<IrFunction> {
val result = mutableSetOf<IrFunction>()
initializedFiles.forEach { (function, functionInitializedFiles) ->
val irFile = function.fileOrNull
val containter = function.calledInitializer ?: return@forEach
val backingField = (function as? IrSimpleFunction)?.correspondingPropertySymbol?.owner?.backingField
val isDefaultAccessor = backingField != null && function.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
if (irFile == null ||
(!functionInitializedFiles.get(fileIds[irFile]!!)
if ((!functionInitializedFiles.get(containerIds[containter]!!)
&& function !in functionsWhoseInitializerCallCanBeExtractedToCallSites
// Extract calls to file initializers off of default accessors to simplify their inlining.
&& (!isDefaultAccessor || function in rootSet))
@@ -194,7 +191,7 @@ internal object StaticInitializersOptimization {
}
private fun analyze(multiNode: DirectedGraphMultiNode<DataFlowIR.FunctionSymbol.Declared>,
initializedFiles: InitializedFiles,
initializedFiles: InitializedContainers,
analysisGoal: AnalysisGoal) {
val nodes = multiNode.nodes.toList()
@@ -244,12 +241,12 @@ internal object StaticInitializersOptimization {
CollectCallSites
}
private fun IrFunction.callsFileInitializer() =
(body?.statements?.get(0) as? IrCall)?.symbol?.owner?.isFileInitializer == true
private val IrFunction.calledInitializer get() =
(body?.statements?.get(0) as? IrCall)?.symbol?.owner?.takeIf { it.isStaticInitializer }?.parent as IrDeclarationContainer?
private fun intraproceduralAnalysis(
node: CallGraphNode,
initializedFiles: InitializedFiles,
initializedContainers: InitializedContainers,
analysisGoal: AnalysisGoal,
callSitesRequiringGlobalInitializerCall: MutableSet<IrFunctionAccessExpression> = dummySet,
callSitesRequiringThreadLocalInitializerCall: MutableSet<IrFunctionAccessExpression> = dummySet,
@@ -257,7 +254,7 @@ internal object StaticInitializersOptimization {
callSitesNotRequiringThreadLocalInitializerCall: MutableSet<IrFunctionAccessExpression> = dummySet
) {
val irDeclaration = node.symbol.irDeclaration ?: return
val body = if (node.symbol.isTopLevelFieldInitializer)
val body = if (node.symbol.isStaticFieldInitializer)
(irDeclaration as IrField).initializer?.expression
else {
val function = irDeclaration as IrFunction
@@ -266,11 +263,11 @@ internal object StaticInitializersOptimization {
}
if (body == null) return
val filesWithInitializedGlobals = BitSet()
val filesWithInitializedThreadLocals = BitSet()
if (!node.symbol.isTopLevelFieldInitializer) {
initializedFiles.beforeCallGlobal[irDeclaration as IrFunction]?.let { filesWithInitializedGlobals.or(it) }
initializedFiles.beforeCallThreadLocal[irDeclaration]?.let { filesWithInitializedThreadLocals.or(it) }
val containersWithInitializedGlobals = BitSet()
val containersWithInitializedThreadLocals = BitSet()
if (!node.symbol.isStaticFieldInitializer) {
initializedContainers.beforeCallGlobal[irDeclaration as IrFunction]?.let { containersWithInitializedGlobals.or(it) }
initializedContainers.beforeCallThreadLocal[irDeclaration]?.let { containersWithInitializedThreadLocals.or(it) }
}
val producerInvocations = mutableMapOf<IrExpression, IrCall>()
@@ -409,25 +406,24 @@ internal object StaticInitializersOptimization {
if (this.get(bit)) this else copy().also { it.set(bit) }
private fun getResultAfterCall(function: IrFunction, set: BitSet): BitSet {
val result = initializedFiles.afterCall[function]
val result = initializedContainers.afterCall[function]
if (result == null) {
if (!function.callsFileInitializer()) return set
val file = function.fileOrNull ?: return set
return set.withSetBit(initializedFiles.fileIds[file]!!)
val file = function.calledInitializer ?: return set
return set.withSetBit(initializedContainers.containerIds[file]!!)
}
return result.copy().also { it.or(set) }
}
private fun updateResultForFunction(function: IrFunction, globalSet: BitSet, threadLocalSet: BitSet) {
if (analysisGoal != AnalysisGoal.ComputeInitializedBeforeCall) return
intersectInitializedFiles(initializedFiles.beforeCallGlobal, function, globalSet)
intersectInitializedFiles(initializedFiles.beforeCallThreadLocal, function, threadLocalSet)
intersectInitializedFiles(initializedContainers.beforeCallGlobal, function, globalSet)
intersectInitializedFiles(initializedContainers.beforeCallThreadLocal, function, threadLocalSet)
}
private fun updateResultForFunction(function: IrFunction, set: BitSet) {
if (analysisGoal != AnalysisGoal.ComputeInitializedBeforeCall) return
intersectInitializedFiles(initializedFiles.beforeCallGlobal, function, set.copy().also { it.or(filesWithInitializedGlobals) })
intersectInitializedFiles(initializedFiles.beforeCallThreadLocal, function, set.copy().also { it.or(filesWithInitializedThreadLocals) })
intersectInitializedFiles(initializedContainers.beforeCallGlobal, function, set.copy().also { it.or(containersWithInitializedGlobals) })
intersectInitializedFiles(initializedContainers.beforeCallThreadLocal, function, set.copy().also { it.or(containersWithInitializedThreadLocals) })
}
override fun visitGetObjectValue(expression: IrGetObjectValue, data: BitSet): BitSet {
@@ -446,22 +442,22 @@ internal object StaticInitializersOptimization {
val arguments = expression.getArgumentsWithIr()
val argumentsResult = arguments.fold(data) { set, arg -> arg.second.accept(this, set) }
updateResultForFunction(actualCallee, argumentsResult)
val file = actualCallee.fileOrNull
val fileId = file?.let { initializedFiles.fileIds[it]!! } ?: invalidFileId
if (analysisGoal == AnalysisGoal.CollectCallSites && file != null
val container = actualCallee.calledInitializer
val containerId = initializedContainers.containerIds[container] ?: invalidContainerId
if (analysisGoal == AnalysisGoal.CollectCallSites &&
// Only extract initializer calls from non-virtual functions.
&& !actualCallee.isOverridable
!actualCallee.isOverridable
) {
// The initializer won't be optimized away from the function.
if (!initializedFiles.beforeCallGlobal[actualCallee]!!.get(fileId)) {
if (argumentsResult.get(fileId) || filesWithInitializedGlobals.get(fileId))
if (!initializedContainers.beforeCallGlobal[actualCallee]!!.get(containerId)) {
if (argumentsResult.get(containerId) || containersWithInitializedGlobals.get(containerId))
callSitesNotRequiringGlobalInitializerCall += expression
else
callSitesRequiringGlobalInitializerCall += expression
}
// The initializer won't be optimized away from the function.
if (!initializedFiles.beforeCallThreadLocal[actualCallee]!!.get(fileId)) {
if (argumentsResult.get(fileId) || filesWithInitializedThreadLocals.get(fileId))
if (!initializedContainers.beforeCallThreadLocal[actualCallee]!!.get(containerId)) {
if (argumentsResult.get(containerId) || containersWithInitializedThreadLocals.get(containerId))
callSitesNotRequiringThreadLocalInitializerCall += expression
else
callSitesRequiringThreadLocalInitializerCall += expression
@@ -480,7 +476,7 @@ internal object StaticInitializersOptimization {
if (analysisGoal != AnalysisGoal.CollectCallSites) {
require(!jobInvocation.isVirtualCall) { "Expected a static call but was: ${jobInvocation.render()}" }
updateResultForFunction(jobInvocation.actualCallee,
curData.copy().also { it.or(filesWithInitializedGlobals) }, // Globals (= shared) visible to other threads as well.
curData.copy().also { it.or(containersWithInitializedGlobals) }, // Globals (= shared) visible to other threads as well.
BitSet() // A new thread is about to be created - no thread locals initialized yet.
)
}
@@ -499,8 +495,8 @@ internal object StaticInitializersOptimization {
processCall(expression, expression.actualCallee, data)
override fun visitCall(expression: IrCall, data: BitSet): BitSet {
if (expression.symbol.owner.isFileInitializer)
return data.withSetBit(initializedFiles.fileIds[irDeclaration.file]!!)
if (expression.symbol.owner.isStaticInitializer)
return data.withSetBit(initializedContainers.containerIds[expression.symbol.owner.parent as IrDeclarationContainer]!!)
if (expression.symbol == executeImplSymbol)
return processExecuteImpl(expression, data)
if (expression.symbol == getContinuationSymbol)
@@ -528,8 +524,8 @@ internal object StaticInitializersOptimization {
}, BitSet())
if (analysisGoal == AnalysisGoal.ComputeInitializedAfterCall) {
if (!node.symbol.isTopLevelFieldInitializer)
initializedFiles.afterCall[irDeclaration as IrFunction] = returnTargetsInitializedFiles[irDeclaration.symbol] ?: callerResult
if (!node.symbol.isStaticFieldInitializer)
initializedContainers.afterCall[irDeclaration as IrFunction] = returnTargetsInitializedFiles[irDeclaration.symbol] ?: callerResult
}
}
}
@@ -560,10 +556,10 @@ internal object StaticInitializersOptimization {
.take(2) // The very first statements by construction.
.filter {
val calleeOrigin = (it as? IrCall)?.symbol?.owner?.origin
val isNotOptimizedAwayGlobalInitializerCall = calleeOrigin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
val isNotOptimizedAwayGlobalInitializerCall = calleeOrigin == DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER
&& callee !in analysisResult.functionsRequiringGlobalInitializerCall
val isNotOptimizedAwayThreadLocalInitializerCall = (calleeOrigin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
|| calleeOrigin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER)
val isNotOptimizedAwayThreadLocalInitializerCall = (calleeOrigin == DECLARATION_ORIGIN_STATIC_THREAD_LOCAL_INITIALIZER
|| calleeOrigin == DECLARATION_ORIGIN_STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER)
&& callee !in analysisResult.functionsRequiringThreadLocalInitializerCall
if (isNotOptimizedAwayGlobalInitializerCall)
++numberOfCallSitesToFunctionsWithGlobalInitializerCall
@@ -596,7 +592,7 @@ internal object StaticInitializersOptimization {
.take(2) // The very first statements by construction.
.indexOfFirst {
val calleeOrigin = (it as? IrCall)?.symbol?.owner?.origin
calleeOrigin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
calleeOrigin == DECLARATION_ORIGIN_STATIC_GLOBAL_INITIALIZER
}
if (globalInitializerCallIndex >= 0) {
++numberOfFunctionsWithGlobalInitializerCall
@@ -609,8 +605,8 @@ internal object StaticInitializersOptimization {
.take(2)
.indexOfFirst {
val calleeOrigin = (it as? IrCall)?.symbol?.owner?.origin
calleeOrigin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
|| calleeOrigin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
calleeOrigin == DECLARATION_ORIGIN_STATIC_THREAD_LOCAL_INITIALIZER
|| calleeOrigin == DECLARATION_ORIGIN_STATIC_STANDALONE_THREAD_LOCAL_INITIALIZER
}
if (threadLocalInitializerCallIndex >= 0) {
++numberOfFunctionsWithThreadLocalInitializerCall