[K/N][IR][optmz] Global analysis for top-level initializers
The analysis' goal is to remove redundant calls to initializers
This commit is contained in:
+17
-4
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.konan.ir
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.atMostOne
|
import org.jetbrains.kotlin.backend.common.atMostOne
|
||||||
import org.jetbrains.kotlin.backend.konan.DECLARATION_ORIGIN_INLINE_CLASS_SPECIAL_FUNCTION
|
import org.jetbrains.kotlin.backend.konan.DECLARATION_ORIGIN_INLINE_CLASS_SPECIAL_FUNCTION
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInteropLibrary
|
import org.jetbrains.kotlin.backend.konan.descriptors.isInteropLibrary
|
||||||
import org.jetbrains.kotlin.backend.konan.llvm.KonanMetadata
|
import org.jetbrains.kotlin.backend.konan.llvm.KonanMetadata
|
||||||
import org.jetbrains.kotlin.backend.konan.serialization.KonanFileMetadataSource
|
import org.jetbrains.kotlin.backend.konan.serialization.KonanFileMetadataSource
|
||||||
@@ -19,10 +20,7 @@ import org.jetbrains.kotlin.descriptors.konan.klibModuleOrigin
|
|||||||
import org.jetbrains.kotlin.ir.IrBuiltIns
|
import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyDeclarationBase
|
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyDeclarationBase
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
@@ -107,6 +105,21 @@ fun buildSimpleAnnotation(irBuiltIns: IrBuiltIns, startOffset: Int, endOffset: I
|
|||||||
internal fun IrExpression.isBoxOrUnboxCall() =
|
internal fun IrExpression.isBoxOrUnboxCall() =
|
||||||
(this is IrCall && symbol.owner.origin == DECLARATION_ORIGIN_INLINE_CLASS_SPECIAL_FUNCTION)
|
(this is IrCall && symbol.owner.origin == DECLARATION_ORIGIN_INLINE_CLASS_SPECIAL_FUNCTION)
|
||||||
|
|
||||||
|
internal fun IrBranch.isUnconditional(): Boolean = (condition as? IrConst<*>)?.value == true
|
||||||
|
|
||||||
|
internal val IrFunctionAccessExpression.actualCallee: IrFunction
|
||||||
|
get() {
|
||||||
|
val callee = symbol.owner
|
||||||
|
return ((this as? IrCall)?.superQualifierSymbol?.owner?.getOverridingOf(callee) ?: callee).target
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val IrFunctionAccessExpression.isVirtualCall: Boolean
|
||||||
|
get() = this is IrCall && this.superQualifierSymbol == null && this.symbol.owner.isOverridable
|
||||||
|
|
||||||
|
private fun IrClass.getOverridingOf(function: IrFunction) = (function as? IrSimpleFunction)?.let {
|
||||||
|
it.allOverriddenFunctions.atMostOne { it.parent == this }
|
||||||
|
}
|
||||||
|
|
||||||
val ModuleDescriptor.konanLibrary get() = (this.klibModuleOrigin as? DeserializedKlibModuleOrigin)?.library
|
val ModuleDescriptor.konanLibrary get() = (this.klibModuleOrigin as? DeserializedKlibModuleOrigin)?.library
|
||||||
val IrModuleFragment.konanLibrary get() =
|
val IrModuleFragment.konanLibrary get() =
|
||||||
(this as? KonanIrModuleFragmentImpl)?.konanLibrary ?: descriptor.konanLibrary
|
(this as? KonanIrModuleFragmentImpl)?.konanLibrary ?: descriptor.konanLibrary
|
||||||
|
|||||||
+8
-32
@@ -12,18 +12,16 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaserState
|
|||||||
import org.jetbrains.kotlin.backend.common.phaser.namedUnitPhase
|
import org.jetbrains.kotlin.backend.common.phaser.namedUnitPhase
|
||||||
import org.jetbrains.kotlin.backend.konan.*
|
import org.jetbrains.kotlin.backend.konan.*
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.GlobalHierarchyAnalysis
|
import org.jetbrains.kotlin.backend.konan.descriptors.GlobalHierarchyAnalysis
|
||||||
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.RedundantCoercionsCleaner
|
import org.jetbrains.kotlin.backend.konan.lower.RedundantCoercionsCleaner
|
||||||
import org.jetbrains.kotlin.backend.konan.lower.ReturnsInsertionLowering
|
import org.jetbrains.kotlin.backend.konan.lower.ReturnsInsertionLowering
|
||||||
import org.jetbrains.kotlin.backend.konan.optimizations.*
|
import org.jetbrains.kotlin.backend.konan.optimizations.*
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.IrStatement
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
|
import org.jetbrains.kotlin.ir.util.defaultType
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
import org.jetbrains.kotlin.ir.util.isFunction
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.isReal
|
||||||
|
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||||
import org.jetbrains.kotlin.ir.visitors.*
|
import org.jetbrains.kotlin.ir.visitors.*
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||||
@@ -248,33 +246,11 @@ internal val removeRedundantCallsToFileInitializersPhase = makeKonanModuleOpPhas
|
|||||||
nonDevirtualizedCallSitesUnfoldFactor = Int.MAX_VALUE
|
nonDevirtualizedCallSitesUnfoldFactor = Int.MAX_VALUE
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
val functionsBeingCalledFromOtherFiles = mutableSetOf<IrFunction>()
|
val rootSet = DevirtualizationAnalysis.computeRootSet(context, moduleDFG, externalModulesDFG)
|
||||||
for (node in callGraph.directEdges.values) {
|
.mapNotNull { it.irFunction }
|
||||||
val callerFile = node.symbol.irFile
|
.toSet()
|
||||||
node.callSites.forEach {
|
|
||||||
require(!it.isVirtual) { "There should be no virtual calls in the call graph, but was: ${it.actualCallee}" }
|
|
||||||
val calleeFile = it.actualCallee.irFile
|
|
||||||
if (callerFile == null || callerFile != calleeFile)
|
|
||||||
functionsBeingCalledFromOtherFiles.add(it.actualCallee.irFunction ?: error("No IR for: ${it.actualCallee}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val rootSet = DevirtualizationAnalysis.computeRootSet(context, moduleDFG, externalModulesDFG).toSet()
|
FileInitializersOptimization.removeRedundantCalls(context, callGraph, rootSet)
|
||||||
context.irModule!!.transformChildrenVoid(object : IrElementTransformerVoid() {
|
|
||||||
override fun visitFunction(declaration: IrFunction): IrStatement {
|
|
||||||
declaration.transformChildrenVoid(this)
|
|
||||||
if (declaration in functionsBeingCalledFromOtherFiles
|
|
||||||
|| moduleDFG.symbolTable.mapFunction(declaration) in rootSet) return declaration
|
|
||||||
val body = declaration.body ?: return declaration
|
|
||||||
(body as IrBlockBody).statements.removeAll {
|
|
||||||
val calleeOrigin = (it as? IrCall)?.symbol?.owner?.origin
|
|
||||||
calleeOrigin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
|
|
||||||
|| calleeOrigin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
|
|
||||||
|| calleeOrigin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER
|
|
||||||
}
|
|
||||||
return declaration
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-9
@@ -1271,7 +1271,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
* we cannot determine if the result of when is assigned or not.
|
* we cannot determine if the result of when is assigned or not.
|
||||||
*/
|
*/
|
||||||
private inner class WhenEmittingContext(val expression: IrWhen) {
|
private inner class WhenEmittingContext(val expression: IrWhen) {
|
||||||
val needsPhi = isUnconditional(expression.branches.last()) && !expression.type.isUnit()
|
val needsPhi = expression.branches.last().isUnconditional() && !expression.type.isUnit()
|
||||||
val llvmType = codegen.getLLVMType(expression.type)
|
val llvmType = codegen.getLLVMType(expression.type)
|
||||||
|
|
||||||
val bbExit = lazy { functionGenerationContext.basicBlock("when_exit", expression.endLocation) }
|
val bbExit = lazy { functionGenerationContext.basicBlock("when_exit", expression.endLocation) }
|
||||||
@@ -1318,7 +1318,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateWhenCase(whenEmittingContext: WhenEmittingContext, branch: IrBranch, bbNext: LLVMBasicBlockRef?) {
|
private fun generateWhenCase(whenEmittingContext: WhenEmittingContext, branch: IrBranch, bbNext: LLVMBasicBlockRef?) {
|
||||||
val brResult = if (isUnconditional(branch))
|
val brResult = if (branch.isUnconditional())
|
||||||
evaluateExpression(branch.result)
|
evaluateExpression(branch.result)
|
||||||
else {
|
else {
|
||||||
val bbCase = functionGenerationContext.basicBlock("when_case", branch.startLocation, branch.endLocation)
|
val bbCase = functionGenerationContext.basicBlock("when_case", branch.startLocation, branch.endLocation)
|
||||||
@@ -1336,13 +1336,6 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
functionGenerationContext.positionAtEnd(bbNext)
|
functionGenerationContext.positionAtEnd(bbNext)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------//
|
|
||||||
// Checks if the branch is unconditional
|
|
||||||
|
|
||||||
private fun isUnconditional(branch: IrBranch): Boolean =
|
|
||||||
branch.condition is IrConst<*> // If branch condition is constant.
|
|
||||||
&& (branch.condition as IrConst<*>).value as Boolean // If condition is "true"
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------------//
|
//-------------------------------------------------------------------------//
|
||||||
|
|
||||||
private fun evaluateWhileLoop(loop: IrWhileLoop): LLVMValueRef {
|
private fun evaluateWhileLoop(loop: IrWhileLoop): LLVMValueRef {
|
||||||
|
|||||||
+1
-2
@@ -133,8 +133,7 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val IrCall.callTarget: IrFunction
|
private val IrCall.callTarget: IrFunction
|
||||||
get() = if (superQualifierSymbol == null && symbol.owner.isOverridable) {
|
get() = if (this.isVirtualCall) {
|
||||||
// A virtual call.
|
|
||||||
symbol.owner
|
symbol.owner
|
||||||
} else {
|
} else {
|
||||||
symbol.owner.target
|
symbol.owner.target
|
||||||
|
|||||||
+5
@@ -31,6 +31,11 @@ internal object DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER : IrDeclarationOrigin
|
|||||||
internal object DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("FILE_THREAD_LOCAL_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_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER : IrDeclarationOriginImpl("FILE_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 fun IrBuilderWithScope.irCallFileInitializer(initializer: IrFunctionSymbol) =
|
internal fun IrBuilderWithScope.irCallFileInitializer(initializer: IrFunctionSymbol) =
|
||||||
irCall(initializer).apply { putValueArgument(0, irFalse()) }
|
irCall(initializer).apply { putValueArgument(0, irFalse()) }
|
||||||
|
|
||||||
|
|||||||
+10
-13
@@ -42,9 +42,8 @@ internal class ExternalModulesDFG(val allTypes: List<DataFlowIR.Type.Declared>,
|
|||||||
val publicFunctions: Map<Long, DataFlowIR.FunctionSymbol.Public>,
|
val publicFunctions: Map<Long, DataFlowIR.FunctionSymbol.Public>,
|
||||||
val functionDFGs: Map<DataFlowIR.FunctionSymbol, DataFlowIR.Function>)
|
val functionDFGs: Map<DataFlowIR.FunctionSymbol, DataFlowIR.Function>)
|
||||||
|
|
||||||
private fun IrClass.getOverridingOf(function: IrFunction) = (function as? IrSimpleFunction)?.let {
|
internal object STATEMENT_ORIGIN_PRODUCER_INVOCATION : IrStatementOriginImpl("PRODUCER_INVOCATION")
|
||||||
it.allOverriddenFunctions.atMostOne { it.parent == this }
|
internal object STATEMENT_ORIGIN_JOB_INVOCATION : IrStatementOriginImpl("JOB_INVOCATION")
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrTypeOperator.isCast() =
|
private fun IrTypeOperator.isCast() =
|
||||||
this == IrTypeOperator.CAST || this == IrTypeOperator.IMPLICIT_CAST || this == IrTypeOperator.SAFE_CAST
|
this == IrTypeOperator.CAST || this == IrTypeOperator.IMPLICIT_CAST || this == IrTypeOperator.SAFE_CAST
|
||||||
@@ -336,7 +335,8 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
executeImplProducerInvoke.returnType,
|
executeImplProducerInvoke.returnType,
|
||||||
executeImplProducerInvoke.symbol,
|
executeImplProducerInvoke.symbol,
|
||||||
executeImplProducerInvoke.symbol.owner.typeParameters.size,
|
executeImplProducerInvoke.symbol.owner.typeParameters.size,
|
||||||
executeImplProducerInvoke.symbol.owner.valueParameters.size)
|
executeImplProducerInvoke.symbol.owner.valueParameters.size,
|
||||||
|
STATEMENT_ORIGIN_PRODUCER_INVOCATION)
|
||||||
producerInvocation.dispatchReceiver = expression.getValueArgument(2)
|
producerInvocation.dispatchReceiver = expression.getValueArgument(2)
|
||||||
|
|
||||||
expressions += producerInvocation to currentLoop
|
expressions += producerInvocation to currentLoop
|
||||||
@@ -347,7 +347,8 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
jobFunctionReference.symbol.owner.returnType,
|
jobFunctionReference.symbol.owner.returnType,
|
||||||
jobFunctionReference.symbol as IrSimpleFunctionSymbol,
|
jobFunctionReference.symbol as IrSimpleFunctionSymbol,
|
||||||
jobFunctionReference.symbol.owner.typeParameters.size,
|
jobFunctionReference.symbol.owner.typeParameters.size,
|
||||||
jobFunctionReference.symbol.owner.valueParameters.size)
|
jobFunctionReference.symbol.owner.valueParameters.size,
|
||||||
|
STATEMENT_ORIGIN_JOB_INVOCATION)
|
||||||
jobInvocation.putValueArgument(0, producerInvocation)
|
jobInvocation.putValueArgument(0, producerInvocation)
|
||||||
|
|
||||||
expressions += jobInvocation to currentLoop
|
expressions += jobInvocation to currentLoop
|
||||||
@@ -706,9 +707,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
getContinuationSymbol -> getContinuation().value
|
getContinuationSymbol -> getContinuation().value
|
||||||
|
|
||||||
in arrayGetSymbols -> {
|
in arrayGetSymbols -> {
|
||||||
val callee = value.symbol.owner
|
val actualCallee = value.actualCallee
|
||||||
val actualCallee = (value.superQualifierSymbol?.owner?.getOverridingOf(callee)
|
|
||||||
?: callee).target
|
|
||||||
|
|
||||||
DataFlowIR.Node.ArrayRead(
|
DataFlowIR.Node.ArrayRead(
|
||||||
symbolTable.mapFunction(actualCallee),
|
symbolTable.mapFunction(actualCallee),
|
||||||
@@ -719,9 +718,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
}
|
}
|
||||||
|
|
||||||
in arraySetSymbols -> {
|
in arraySetSymbols -> {
|
||||||
val callee = value.symbol.owner
|
val actualCallee = value.actualCallee
|
||||||
val actualCallee = (value.superQualifierSymbol?.owner?.getOverridingOf(callee)
|
|
||||||
?: callee).target
|
|
||||||
DataFlowIR.Node.ArrayWrite(
|
DataFlowIR.Node.ArrayWrite(
|
||||||
symbolTable.mapFunction(actualCallee),
|
symbolTable.mapFunction(actualCallee),
|
||||||
array = expressionToEdge(value.dispatchReceiver!!),
|
array = expressionToEdge(value.dispatchReceiver!!),
|
||||||
@@ -764,7 +761,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
if (callee is IrConstructor) {
|
if (callee is IrConstructor) {
|
||||||
error("Constructor call should be done with IrConstructorCall")
|
error("Constructor call should be done with IrConstructorCall")
|
||||||
} else {
|
} else {
|
||||||
if (callee.isOverridable && value.superQualifierSymbol == null) {
|
if (value.isVirtualCall) {
|
||||||
val owner = callee.parentAsClass
|
val owner = callee.parentAsClass
|
||||||
val actualReceiverType = value.dispatchReceiver!!.type
|
val actualReceiverType = value.dispatchReceiver!!.type
|
||||||
val actualReceiverClassifier = actualReceiverType.classifierOrFail
|
val actualReceiverClassifier = actualReceiverType.classifierOrFail
|
||||||
@@ -813,7 +810,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val actualCallee = (value.superQualifierSymbol?.owner?.getOverridingOf(callee) ?: callee).target
|
val actualCallee = value.actualCallee
|
||||||
DataFlowIR.Node.StaticCall(
|
DataFlowIR.Node.StaticCall(
|
||||||
symbolTable.mapFunction(actualCallee),
|
symbolTable.mapFunction(actualCallee),
|
||||||
arguments,
|
arguments,
|
||||||
|
|||||||
+16
-13
@@ -124,7 +124,7 @@ internal object DataFlowIR {
|
|||||||
|
|
||||||
class FunctionParameter(val type: Type, val boxFunction: FunctionSymbol?, val unboxFunction: FunctionSymbol?)
|
class FunctionParameter(val type: Type, val boxFunction: FunctionSymbol?, val unboxFunction: FunctionSymbol?)
|
||||||
|
|
||||||
abstract class FunctionSymbol(val attributes: Int, val irFile: IrFile?, val irFunction: IrFunction?, val name: String?) {
|
abstract class FunctionSymbol(val attributes: Int, val irDeclaration: IrDeclaration?, val name: String?) {
|
||||||
lateinit var parameters: Array<FunctionParameter>
|
lateinit var parameters: Array<FunctionParameter>
|
||||||
lateinit var returnParameter: FunctionParameter
|
lateinit var returnParameter: FunctionParameter
|
||||||
|
|
||||||
@@ -134,11 +134,14 @@ internal object DataFlowIR {
|
|||||||
val returnsNothing = attributes.and(FunctionAttributes.RETURNS_NOTHING) != 0
|
val returnsNothing = attributes.and(FunctionAttributes.RETURNS_NOTHING) != 0
|
||||||
val explicitlyExported = attributes.and(FunctionAttributes.EXPLICITLY_EXPORTED) != 0
|
val explicitlyExported = attributes.and(FunctionAttributes.EXPLICITLY_EXPORTED) != 0
|
||||||
|
|
||||||
|
val irFunction: IrFunction? get() = irDeclaration as? IrFunction
|
||||||
|
val irFile: IrFile? get() = irDeclaration?.fileOrNull
|
||||||
|
|
||||||
var escapes: Int? = null
|
var escapes: Int? = null
|
||||||
var pointsTo: IntArray? = null
|
var pointsTo: IntArray? = null
|
||||||
|
|
||||||
class External(val hash: Long, attributes: Int, irFile: IrFile?, irFunction: IrFunction?, name: String? = null, val isExported: Boolean)
|
class External(val hash: Long, attributes: Int, irDeclaration: IrDeclaration?, name: String? = null, val isExported: Boolean)
|
||||||
: FunctionSymbol(attributes, irFile, irFunction, name) {
|
: FunctionSymbol(attributes, irDeclaration, name) {
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@@ -157,14 +160,14 @@ internal object DataFlowIR {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class Declared(val module: Module, val symbolTableIndex: Int,
|
abstract class Declared(val module: Module, val symbolTableIndex: Int,
|
||||||
attributes: Int, irFile: IrFile?, irFunction: IrFunction?, var bridgeTarget: FunctionSymbol?, name: String?)
|
attributes: Int, irDeclaration: IrDeclaration?, var bridgeTarget: FunctionSymbol?, name: String?)
|
||||||
: FunctionSymbol(attributes, irFile, irFunction, name) {
|
: FunctionSymbol(attributes, irDeclaration, name) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Public(val hash: Long, module: Module, symbolTableIndex: Int,
|
class Public(val hash: Long, module: Module, symbolTableIndex: Int,
|
||||||
attributes: Int, irFile: IrFile?, irFunction: IrFunction?, bridgeTarget: FunctionSymbol?, name: String? = null)
|
attributes: Int, irDeclaration: IrDeclaration?, bridgeTarget: FunctionSymbol?, name: String? = null)
|
||||||
: Declared(module, symbolTableIndex, attributes, irFile, irFunction, bridgeTarget, name) {
|
: Declared(module, symbolTableIndex, attributes, irDeclaration, bridgeTarget, name) {
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@@ -183,8 +186,8 @@ internal object DataFlowIR {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Private(val index: Int, module: Module, symbolTableIndex: Int,
|
class Private(val index: Int, module: Module, symbolTableIndex: Int,
|
||||||
attributes: Int, irFile: IrFile?, irFunction: IrFunction?, bridgeTarget: FunctionSymbol?, name: String? = null)
|
attributes: Int, irDeclaration: IrDeclaration?, bridgeTarget: FunctionSymbol?, name: String? = null)
|
||||||
: Declared(module, symbolTableIndex, attributes, irFile, irFunction, bridgeTarget, name) {
|
: Declared(module, symbolTableIndex, attributes, irDeclaration, bridgeTarget, name) {
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
@@ -595,7 +598,7 @@ internal object DataFlowIR {
|
|||||||
val escapesBitMask = (escapesAnnotation?.getValueArgument(0) as? IrConst<Int>)?.value
|
val escapesBitMask = (escapesAnnotation?.getValueArgument(0) as? IrConst<Int>)?.value
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val pointsToBitMask = (pointsToAnnotation?.getValueArgument(0) as? IrVararg)?.elements?.map { (it as IrConst<Int>).value }
|
val pointsToBitMask = (pointsToAnnotation?.getValueArgument(0) as? IrVararg)?.elements?.map { (it as IrConst<Int>).value }
|
||||||
FunctionSymbol.External(name.localHash.value, attributes, it.fileOrNull, it, takeName { name }, it.isExported()).apply {
|
FunctionSymbol.External(name.localHash.value, attributes, it, takeName { name }, it.isExported()).apply {
|
||||||
escapes = escapesBitMask
|
escapes = escapesBitMask
|
||||||
pointsTo = pointsToBitMask?.toIntArray()
|
pointsTo = pointsToBitMask?.toIntArray()
|
||||||
}
|
}
|
||||||
@@ -615,9 +618,9 @@ internal object DataFlowIR {
|
|||||||
val symbolTableIndex = if (placeToFunctionsTable) module.numberOfFunctions++ else -1
|
val symbolTableIndex = if (placeToFunctionsTable) module.numberOfFunctions++ else -1
|
||||||
val frozen = it is IrConstructor && irClass!!.annotations.findAnnotation(KonanFqNames.frozen) != null
|
val frozen = it is IrConstructor && irClass!!.annotations.findAnnotation(KonanFqNames.frozen) != null
|
||||||
val functionSymbol = if (it.isExported())
|
val functionSymbol = if (it.isExported())
|
||||||
FunctionSymbol.Public(name.localHash.value, module, symbolTableIndex, attributes, it.fileOrNull, it, bridgeTargetSymbol, takeName { name })
|
FunctionSymbol.Public(name.localHash.value, module, symbolTableIndex, attributes, it, bridgeTargetSymbol, takeName { name })
|
||||||
else
|
else
|
||||||
FunctionSymbol.Private(privateFunIndex++, module, symbolTableIndex, attributes, it.fileOrNull, it, bridgeTargetSymbol, takeName { name })
|
FunctionSymbol.Private(privateFunIndex++, module, symbolTableIndex, attributes, it, bridgeTargetSymbol, takeName { name })
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
functionSymbol.escapes = 0b1 // Assume instances of frozen classes escape.
|
functionSymbol.escapes = 0b1 // Assume instances of frozen classes escape.
|
||||||
}
|
}
|
||||||
@@ -647,7 +650,7 @@ internal object DataFlowIR {
|
|||||||
|
|
||||||
assert(irField.parent !is IrClass) { "All local properties initializers should've been lowered" }
|
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
|
val attributes = FunctionAttributes.IS_TOP_LEVEL_FIELD_INITIALIZER or FunctionAttributes.RETURNS_UNIT
|
||||||
val symbol = FunctionSymbol.Private(privateFunIndex++, module, -1, attributes, irField.fileOrNull, null, null, takeName { "${irField.computeSymbolName()}_init" })
|
val symbol = FunctionSymbol.Private(privateFunIndex++, module, -1, attributes, irField, null, takeName { "${irField.computeSymbolName()}_init" })
|
||||||
|
|
||||||
functionMap[irField] = symbol
|
functionMap[irField] = symbol
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -45,6 +45,8 @@ inline fun BitSet.forEachBit(block: (Int) -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun BitSet.copy() = BitSet(this.size()).apply { this.or(this@copy) }
|
||||||
|
|
||||||
// Devirtualization analysis is performed using Variable Type Analysis algorithm.
|
// Devirtualization analysis is performed using Variable Type Analysis algorithm.
|
||||||
// See http://web.cs.ucla.edu/~palsberg/tba/papers/sundaresan-et-al-oopsla00.pdf for details.
|
// See http://web.cs.ucla.edu/~palsberg/tba/papers/sundaresan-et-al-oopsla00.pdf for details.
|
||||||
internal object DevirtualizationAnalysis {
|
internal object DevirtualizationAnalysis {
|
||||||
@@ -264,8 +266,6 @@ internal object DevirtualizationAnalysis {
|
|||||||
else -> error("Unreachable")
|
else -> error("Unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BitSet.copy() = BitSet(this.size()).apply { this.or(this@copy) }
|
|
||||||
|
|
||||||
fun logPathToType(reversedEdges: IntArray, node: Node, type: Int) {
|
fun logPathToType(reversedEdges: IntArray, node: Node, type: Int) {
|
||||||
val nodes = constraintGraph.nodes
|
val nodes = constraintGraph.nodes
|
||||||
val visited = BitSet()
|
val visited = BitSet()
|
||||||
|
|||||||
+625
@@ -0,0 +1,625 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.backend.konan.optimizations
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.atMostOne
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
||||||
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
|
import org.jetbrains.kotlin.backend.konan.DirectedGraphCondensationBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.konan.DirectedGraphMultiNode
|
||||||
|
import org.jetbrains.kotlin.backend.konan.ir.actualCallee
|
||||||
|
import org.jetbrains.kotlin.backend.konan.ir.isOverridable
|
||||||
|
import org.jetbrains.kotlin.backend.konan.ir.isUnconditional
|
||||||
|
import org.jetbrains.kotlin.backend.konan.ir.isVirtualCall
|
||||||
|
import org.jetbrains.kotlin.backend.konan.logMultiple
|
||||||
|
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.ir.IrElement
|
||||||
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
|
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irBlock
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyClass
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrReturnTargetSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A data flow analysis to remove or move around calls to file initializers.
|
||||||
|
* The goal is to find for each function and for each call site the set of
|
||||||
|
* definitely initialized files before the corresponding call.
|
||||||
|
*
|
||||||
|
* This is done in three quite similar steps using global interprocedural analysis:
|
||||||
|
* 1. For each function find the set of definitely initialized files after returning from the function.
|
||||||
|
* Handle all the functions in the reverse topological order.
|
||||||
|
* 2. For each function find the set of definitely initialized files before executing the function's body.
|
||||||
|
* Handle all the functions in the topological order and use the results of the first step
|
||||||
|
* for updating the result after some function call.
|
||||||
|
* 3. For each call site find the set of definitely initialized files before the actual call is made.
|
||||||
|
* Handle all the functions in the arbitrary order and use the results of the first step
|
||||||
|
* for updating the result. Then use the results from the second step to see if the initializer call
|
||||||
|
* could be extracted from the callee to the call site.
|
||||||
|
*
|
||||||
|
* All three steps use similar local intraprocedural data flow analysis on IR using an IR visitor
|
||||||
|
* taking the set of already initialized files before evaluating some expression and returning the modified
|
||||||
|
* set after evaluating that expression.
|
||||||
|
*/
|
||||||
|
|
||||||
|
internal object FileInitializersOptimization {
|
||||||
|
private class AnalysisResult(val functionsRequiringGlobalInitializerCall: Set<IrFunction>,
|
||||||
|
val functionsRequiringThreadLocalInitializerCall: Set<IrFunction>,
|
||||||
|
val callSitesRequiringGlobalInitializerCall: Set<IrFunctionAccessExpression>,
|
||||||
|
val callSitesRequiringThreadLocalInitializerCall: Set<IrFunctionAccessExpression>)
|
||||||
|
|
||||||
|
private class InitializedFiles(val fileIds: Map<IrFile, Int>) {
|
||||||
|
val afterCall = mutableMapOf<IrFunction, BitSet>()
|
||||||
|
val beforeCallGlobal = mutableMapOf<IrFunction, BitSet>()
|
||||||
|
val beforeCallThreadLocal = mutableMapOf<IrFunction, BitSet>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val invalidFileId = 0
|
||||||
|
|
||||||
|
private class InterproceduralAnalysis(val context: Context, val callGraph: CallGraph,
|
||||||
|
val rootSet: Set<IrFunction>) {
|
||||||
|
fun analyze(): AnalysisResult {
|
||||||
|
context.logMultiple {
|
||||||
|
+"CALL GRAPH"
|
||||||
|
callGraph.directEdges.forEach { (t, u) ->
|
||||||
|
+" FUN $t"
|
||||||
|
u.callSites.forEach {
|
||||||
|
val label = when {
|
||||||
|
it.isVirtual -> "VIRTUAL"
|
||||||
|
callGraph.directEdges.containsKey(it.actualCallee) -> "LOCAL"
|
||||||
|
else -> "EXTERNAL"
|
||||||
|
}
|
||||||
|
+" CALLS $label ${it.actualCallee}"
|
||||||
|
}
|
||||||
|
callGraph.reversedEdges[t]!!.forEach { +" CALLED BY $it" }
|
||||||
|
}
|
||||||
|
+""
|
||||||
|
}
|
||||||
|
|
||||||
|
val condensation = DirectedGraphCondensationBuilder(callGraph).build()
|
||||||
|
|
||||||
|
context.logMultiple {
|
||||||
|
+"CONDENSATION"
|
||||||
|
condensation.topologicalOrder.forEach { multiNode ->
|
||||||
|
+" MULTI-NODE"
|
||||||
|
multiNode.nodes.forEach { +" $it" }
|
||||||
|
}
|
||||||
|
+""
|
||||||
|
+"CONDENSATION(DETAILED)"
|
||||||
|
condensation.topologicalOrder.forEach { multiNode ->
|
||||||
|
+" MULTI-NODE"
|
||||||
|
multiNode.nodes.forEach {
|
||||||
|
+" $it"
|
||||||
|
callGraph.directEdges[it]!!.callSites
|
||||||
|
.filter { callGraph.directEdges.containsKey(it.actualCallee) }
|
||||||
|
.forEach { +" CALLS ${it.actualCallee}" }
|
||||||
|
callGraph.reversedEdges[it]!!.forEach { +" CALLED BY $it" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+""
|
||||||
|
}
|
||||||
|
|
||||||
|
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 initializedFiles = InitializedFiles(fileIds)
|
||||||
|
|
||||||
|
context.log { "FIRST PHASE: compute initialized after call" }
|
||||||
|
|
||||||
|
for (multiNode in condensation.topologicalOrder.reversed())
|
||||||
|
analyze(multiNode, initializedFiles, AnalysisGoal.ComputeInitializedAfterCall)
|
||||||
|
|
||||||
|
context.log { "SECOND PHASE: compute initialized before call" }
|
||||||
|
|
||||||
|
// Each function from the root set can be called as the first one, so pessimistically assume that
|
||||||
|
// none of the files has been initialized yet.
|
||||||
|
for (node in callGraph.directEdges.values) {
|
||||||
|
val function = node.symbol.irFunction ?: continue
|
||||||
|
if (function in rootSet) {
|
||||||
|
initializedFiles.beforeCallGlobal[function] = BitSet()
|
||||||
|
initializedFiles.beforeCallThreadLocal[function] = BitSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (multiNode in condensation.topologicalOrder)
|
||||||
|
analyze(multiNode, initializedFiles, AnalysisGoal.ComputeInitializedBeforeCall)
|
||||||
|
|
||||||
|
context.log { "THIRD PHASE: collect call sites" }
|
||||||
|
|
||||||
|
val callSitesRequiringGlobalInitializerCall = mutableSetOf<IrFunctionAccessExpression>()
|
||||||
|
val callSitesRequiringThreadLocalInitializerCall = mutableSetOf<IrFunctionAccessExpression>()
|
||||||
|
val callSitesNotRequiringGlobalInitializerCall = mutableSetOf<IrFunctionAccessExpression>()
|
||||||
|
val callSitesNotRequiringThreadLocalInitializerCall = mutableSetOf<IrFunctionAccessExpression>()
|
||||||
|
|
||||||
|
for (node in callGraph.directEdges.values) {
|
||||||
|
intraproceduralAnalysis(node, initializedFiles, AnalysisGoal.CollectCallSites,
|
||||||
|
callSitesRequiringGlobalInitializerCall, callSitesRequiringThreadLocalInitializerCall,
|
||||||
|
callSitesNotRequiringGlobalInitializerCall, callSitesNotRequiringThreadLocalInitializerCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun collectFunctionsRequiringInitializerCall(
|
||||||
|
initializedFiles: Map<IrFunction, BitSet>,
|
||||||
|
functionsWhoseInitializerCallCanBeExtractedToCallSites: Set<IrFunction>
|
||||||
|
): Set<IrFunction> {
|
||||||
|
val result = mutableSetOf<IrFunction>()
|
||||||
|
initializedFiles.forEach { (function, functionInitializedFiles) ->
|
||||||
|
val irFile = function.fileOrNull
|
||||||
|
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]!!)
|
||||||
|
&& function !in functionsWhoseInitializerCallCanBeExtractedToCallSites
|
||||||
|
// Extract calls to file initializers off of default accessors to simplify their inlining.
|
||||||
|
&& (!isDefaultAccessor || function in rootSet))
|
||||||
|
) {
|
||||||
|
result += function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
val functionsRequiringGlobalInitializerCall = collectFunctionsRequiringInitializerCall(
|
||||||
|
initializedFiles.beforeCallGlobal,
|
||||||
|
callSitesRequiringGlobalInitializerCall.map { it.actualCallee }
|
||||||
|
.toMutableSet().intersect(callSitesNotRequiringGlobalInitializerCall.map { it.actualCallee })
|
||||||
|
)
|
||||||
|
val functionsRequiringThreadLocalInitializerCall = collectFunctionsRequiringInitializerCall(
|
||||||
|
initializedFiles.beforeCallThreadLocal,
|
||||||
|
callSitesRequiringThreadLocalInitializerCall.map { it.actualCallee }
|
||||||
|
.toMutableSet().intersect(callSitesNotRequiringThreadLocalInitializerCall.map { it.actualCallee })
|
||||||
|
)
|
||||||
|
|
||||||
|
return AnalysisResult(functionsRequiringGlobalInitializerCall, functionsRequiringThreadLocalInitializerCall,
|
||||||
|
callSitesRequiringGlobalInitializerCall, callSitesRequiringThreadLocalInitializerCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun analyze(multiNode: DirectedGraphMultiNode<DataFlowIR.FunctionSymbol.Declared>,
|
||||||
|
initializedFiles: InitializedFiles,
|
||||||
|
analysisGoal: AnalysisGoal) {
|
||||||
|
val nodes = multiNode.nodes.toList()
|
||||||
|
|
||||||
|
context.logMultiple {
|
||||||
|
+"Analyzing multiNode:\n ${nodes.joinToString("\n ") { it.toString() }}"
|
||||||
|
nodes.forEach { from ->
|
||||||
|
+"IR"
|
||||||
|
+(from.irFunction?.dump() ?: "")
|
||||||
|
callGraph.directEdges[from]!!.callSites.forEach { to ->
|
||||||
|
+"CALL"
|
||||||
|
+" from $from"
|
||||||
|
+" to ${to.actualCallee}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.size == 1)
|
||||||
|
intraproceduralAnalysis(callGraph.directEdges[nodes[0]] ?: return, initializedFiles, analysisGoal)
|
||||||
|
else {
|
||||||
|
nodes.forEach { intraproceduralAnalysis(callGraph.directEdges[it]!!, initializedFiles, analysisGoal) }
|
||||||
|
// The process is convergent since files can only be removed from the sets.
|
||||||
|
var sum = nodes.sumOf {
|
||||||
|
(initializedFiles.beforeCallGlobal[it.irFunction!!]?.cardinality() ?: 0) +
|
||||||
|
(initializedFiles.beforeCallThreadLocal[it.irFunction!!]?.cardinality() ?: 0) +
|
||||||
|
(initializedFiles.afterCall[it.irFunction!!]?.cardinality() ?: 0)
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
val prevSum = sum
|
||||||
|
nodes.forEach { intraproceduralAnalysis(callGraph.directEdges[it]!!, initializedFiles, analysisGoal) }
|
||||||
|
sum = nodes.sumOf {
|
||||||
|
(initializedFiles.beforeCallGlobal[it.irFunction!!]?.cardinality() ?: 0) +
|
||||||
|
(initializedFiles.beforeCallThreadLocal[it.irFunction!!]?.cardinality() ?: 0) +
|
||||||
|
(initializedFiles.afterCall[it.irFunction!!]?.cardinality() ?: 0)
|
||||||
|
}
|
||||||
|
} while (sum != prevSum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val executeImplSymbol = context.ir.symbols.executeImpl
|
||||||
|
private val getContinuationSymbol = context.ir.symbols.getContinuation
|
||||||
|
|
||||||
|
private var dummySet = mutableSetOf<IrFunctionAccessExpression>()
|
||||||
|
|
||||||
|
private enum class AnalysisGoal {
|
||||||
|
ComputeInitializedAfterCall,
|
||||||
|
ComputeInitializedBeforeCall,
|
||||||
|
CollectCallSites
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrFunction.callsFileInitializer() =
|
||||||
|
(body?.statements?.get(0) as? IrCall)?.symbol?.owner?.isFileInitializer == true
|
||||||
|
|
||||||
|
private fun intraproceduralAnalysis(
|
||||||
|
node: CallGraphNode,
|
||||||
|
initializedFiles: InitializedFiles,
|
||||||
|
analysisGoal: AnalysisGoal,
|
||||||
|
callSitesRequiringGlobalInitializerCall: MutableSet<IrFunctionAccessExpression> = dummySet,
|
||||||
|
callSitesRequiringThreadLocalInitializerCall: MutableSet<IrFunctionAccessExpression> = dummySet,
|
||||||
|
callSitesNotRequiringGlobalInitializerCall: MutableSet<IrFunctionAccessExpression> = dummySet,
|
||||||
|
callSitesNotRequiringThreadLocalInitializerCall: MutableSet<IrFunctionAccessExpression> = dummySet
|
||||||
|
) {
|
||||||
|
val irDeclaration = node.symbol.irDeclaration ?: return
|
||||||
|
val body = if (node.symbol.isTopLevelFieldInitializer)
|
||||||
|
(irDeclaration as IrField).initializer?.expression
|
||||||
|
else {
|
||||||
|
val function = irDeclaration as IrFunction
|
||||||
|
val builder = context.createIrBuilder(function.symbol)
|
||||||
|
function.body?.let { body -> builder.irBlock { (body as IrBlockBody).statements.forEach { +it } } }
|
||||||
|
}
|
||||||
|
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 producerInvocations = mutableMapOf<IrExpression, IrCall>()
|
||||||
|
val jobInvocations = mutableMapOf<IrCall, IrCall>()
|
||||||
|
val virtualCallSites = mutableMapOf<IrCall, MutableList<CallGraphNode.CallSite>>()
|
||||||
|
for (callSite in node.callSites) {
|
||||||
|
val call = callSite.call
|
||||||
|
val irCall = call.irCallSite as? IrCall ?: continue
|
||||||
|
if (irCall.origin == STATEMENT_ORIGIN_PRODUCER_INVOCATION)
|
||||||
|
producerInvocations[irCall.dispatchReceiver!!] = irCall
|
||||||
|
else if (irCall.origin == STATEMENT_ORIGIN_JOB_INVOCATION)
|
||||||
|
jobInvocations[irCall.getValueArgument(0) as IrCall] = irCall
|
||||||
|
if (call !is DataFlowIR.Node.VirtualCall) continue
|
||||||
|
virtualCallSites.getOrPut(irCall) { mutableListOf() }.add(callSite)
|
||||||
|
}
|
||||||
|
val returnTargetsInitializedFiles = mutableMapOf<IrReturnTargetSymbol, BitSet>()
|
||||||
|
val initializedFilesAtLoopsBreaks = mutableMapOf<IrLoop, BitSet>()
|
||||||
|
val initializedFilesAtLoopsContinues = mutableMapOf<IrLoop, BitSet>()
|
||||||
|
// Each visitXXX function gets as [data] parameter the set of initialized files before evaluating
|
||||||
|
// current element and returns the set of initialized files after evaluating this element.
|
||||||
|
val callerResult = body.accept(object : IrElementVisitor<BitSet, BitSet> {
|
||||||
|
private fun intersectInitializedFiles(previous: BitSet?, current: BitSet) =
|
||||||
|
previous?.copy()?.also { it.and(current) } ?: current
|
||||||
|
|
||||||
|
private fun <K> intersectInitializedFiles(map: MutableMap<K, BitSet>, key: K, set: BitSet) {
|
||||||
|
val previous = map[key]
|
||||||
|
if (previous == null)
|
||||||
|
map[key] = set.copy()
|
||||||
|
else
|
||||||
|
previous.and(set)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitElement(element: IrElement, data: BitSet): BitSet = TODO(element.render())
|
||||||
|
override fun visitExpression(expression: IrExpression, data: BitSet): BitSet = TODO(expression.render())
|
||||||
|
override fun visitDeclaration(declaration: IrDeclarationBase, data: BitSet): BitSet = TODO(declaration.render())
|
||||||
|
|
||||||
|
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: BitSet) = expression.argument.accept(this, data)
|
||||||
|
override fun <T> visitConst(expression: IrConst<T>, data: BitSet) = data
|
||||||
|
override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, data: BitSet) = data
|
||||||
|
|
||||||
|
override fun visitGetValue(expression: IrGetValue, data: BitSet) = data
|
||||||
|
override fun visitSetValue(expression: IrSetValue, data: BitSet) = expression.value.accept(this, data)
|
||||||
|
override fun visitVariable(declaration: IrVariable, data: BitSet) = declaration.initializer?.accept(this, data) ?: data
|
||||||
|
|
||||||
|
override fun visitSuspendableExpression(expression: IrSuspendableExpression, data: BitSet) = expression.result.accept(this, data)
|
||||||
|
override fun visitSuspensionPoint(expression: IrSuspensionPoint, data: BitSet) = expression.result.accept(this, data)
|
||||||
|
|
||||||
|
override fun visitGetField(expression: IrGetField, data: BitSet) = expression.receiver?.accept(this, data) ?: data
|
||||||
|
override fun visitSetField(expression: IrSetField, data: BitSet) =
|
||||||
|
expression.value.accept(this, expression.receiver?.accept(this, data) ?: data)
|
||||||
|
|
||||||
|
override fun visitFunctionReference(expression: IrFunctionReference, data: BitSet) = data
|
||||||
|
override fun visitVararg(expression: IrVararg, data: BitSet) = data
|
||||||
|
|
||||||
|
override fun visitBreak(jump: IrBreak, data: BitSet): BitSet {
|
||||||
|
intersectInitializedFiles(initializedFilesAtLoopsBreaks, jump.loop, data)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
override fun visitContinue(jump: IrContinue, data: BitSet): BitSet {
|
||||||
|
intersectInitializedFiles(initializedFilesAtLoopsContinues, jump.loop, data)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
// A while loop might not execute even a single iteration.
|
||||||
|
override fun visitWhileLoop(loop: IrWhileLoop, data: BitSet) =
|
||||||
|
loop.condition.accept(this, data).also { loop.body?.accept(this, it) }
|
||||||
|
override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: BitSet): BitSet {
|
||||||
|
val bodyFallThroughResult = loop.body?.accept(this, data) ?: data
|
||||||
|
val continuesResult = initializedFilesAtLoopsContinues[loop]
|
||||||
|
// We can end up in the condition part either by falling through the entire body or by executing one of the continue clauses.
|
||||||
|
val bodyResult = intersectInitializedFiles(continuesResult, bodyFallThroughResult)
|
||||||
|
val conditionResult = loop.condition.accept(this, bodyResult)
|
||||||
|
val breaksResult = initializedFilesAtLoopsBreaks[loop]
|
||||||
|
// A loop can be finished either by checking the condition or by executing a break clause.
|
||||||
|
return intersectInitializedFiles(breaksResult, conditionResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateResultForReturnTarget(symbol: IrReturnTargetSymbol, set: BitSet) =
|
||||||
|
intersectInitializedFiles(returnTargetsInitializedFiles, symbol, set)
|
||||||
|
|
||||||
|
override fun visitReturn(expression: IrReturn, data: BitSet) =
|
||||||
|
expression.value.accept(this, data).also {
|
||||||
|
updateResultForReturnTarget(expression.returnTargetSymbol, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitContainerExpression(expression: IrContainerExpression, data: BitSet): BitSet {
|
||||||
|
val result = expression.statements.fold(data) { set, statement -> statement.accept(this, set) }
|
||||||
|
return if (expression !is IrReturnableBlock)
|
||||||
|
result
|
||||||
|
else {
|
||||||
|
updateResultForReturnTarget(expression.symbol, result)
|
||||||
|
returnTargetsInitializedFiles[expression.symbol]!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitWhen(expression: IrWhen, data: BitSet): BitSet {
|
||||||
|
val firstBranch = expression.branches.first()
|
||||||
|
val firstConditionResult = firstBranch.condition.accept(this, data)
|
||||||
|
val bodiesResult = firstBranch.result.accept(this, firstConditionResult)
|
||||||
|
var conditionsResult = firstConditionResult
|
||||||
|
for (i in 1 until expression.branches.size) {
|
||||||
|
val branch = expression.branches[i]
|
||||||
|
conditionsResult = branch.condition.accept(this, conditionsResult)
|
||||||
|
val branchResult = branch.result.accept(this, conditionsResult)
|
||||||
|
bodiesResult.and(branchResult)
|
||||||
|
}
|
||||||
|
val isExhaustive = expression.branches.last().isUnconditional()
|
||||||
|
return if (isExhaustive) {
|
||||||
|
// One of the branches must have been executed.
|
||||||
|
bodiesResult
|
||||||
|
} else {
|
||||||
|
// The first condition is always executed.
|
||||||
|
firstConditionResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitThrow(expression: IrThrow, data: BitSet): BitSet {
|
||||||
|
expression.value.accept(this, data)
|
||||||
|
return data // Conservative but correct.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitTry(aTry: IrTry, data: BitSet): BitSet {
|
||||||
|
require(aTry.finallyExpression == null)
|
||||||
|
aTry.tryResult.accept(this, data)
|
||||||
|
// Catch blocks can't assume that the try part has been executed entirely,
|
||||||
|
// so only take what was known at the beginning of the try block.
|
||||||
|
aTry.catches.forEach { it.result.accept(this, data) }
|
||||||
|
// Since the try part could have been executed with an exception which then could've been caught by
|
||||||
|
// some of the catch clauses, it is incorrect to take the try block's result,
|
||||||
|
// so conservatively don't change the result.
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BitSet.withSetBit(bit: Int): BitSet =
|
||||||
|
if (this.get(bit)) this else copy().also { it.set(bit) }
|
||||||
|
|
||||||
|
private fun getResultAfterCall(function: IrFunction, set: BitSet): BitSet {
|
||||||
|
val result = initializedFiles.afterCall[function]
|
||||||
|
if (result == null) {
|
||||||
|
if (!function.callsFileInitializer()) return set
|
||||||
|
val file = function.fileOrNull ?: return set
|
||||||
|
return set.withSetBit(initializedFiles.fileIds[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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitGetObjectValue(expression: IrGetObjectValue, data: BitSet): BitSet {
|
||||||
|
val objectClass = expression.symbol.owner
|
||||||
|
val constructor = objectClass.constructors.toList().atMostOne()
|
||||||
|
if (constructor != null) {
|
||||||
|
updateResultForFunction(constructor, data)
|
||||||
|
} else {
|
||||||
|
require(objectClass.isExternal || objectClass is IrLazyClass) { "No constructor for ${objectClass.render()}" }
|
||||||
|
}
|
||||||
|
val file = objectClass.fileOrNull ?: return data
|
||||||
|
val fileId = initializedFiles.fileIds[file]!!
|
||||||
|
if (data.get(fileId)) return data
|
||||||
|
return data.copy().also { it.set(fileId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processCall(expression: IrFunctionAccessExpression, actualCallee: IrFunction, data: BitSet): BitSet {
|
||||||
|
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
|
||||||
|
// Only extract initializer calls from non-virtual functions.
|
||||||
|
&& !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))
|
||||||
|
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))
|
||||||
|
callSitesNotRequiringThreadLocalInitializerCall += expression
|
||||||
|
else
|
||||||
|
callSitesRequiringThreadLocalInitializerCall += expression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getResultAfterCall(actualCallee, argumentsResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processExecuteImpl(expression: IrCall, data: BitSet): BitSet {
|
||||||
|
var curData = processCall(expression, expression.symbol.owner, data)
|
||||||
|
val producerInvocation = producerInvocations[expression.getValueArgument(2)!!]!!
|
||||||
|
// Producer is invoked right here in the same thread, so can update the result.
|
||||||
|
// Albeit this call site is a fictitious one, it is always a virtual one, which aren't optimized for now.
|
||||||
|
curData = visitCall(producerInvocation, curData)
|
||||||
|
val jobInvocation = jobInvocations[producerInvocation]!!
|
||||||
|
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.
|
||||||
|
BitSet() // A new thread is about to be created - no thread locals initialized yet.
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Actual job could be invoked on another thread, thus can't take the result from that call.
|
||||||
|
return curData
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BitSet) =
|
||||||
|
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 == executeImplSymbol)
|
||||||
|
return processExecuteImpl(expression, data)
|
||||||
|
if (expression.symbol == getContinuationSymbol)
|
||||||
|
return data
|
||||||
|
if (!expression.isVirtualCall)
|
||||||
|
return processCall(expression, expression.actualCallee, data)
|
||||||
|
val devirtualizedCallSite = virtualCallSites[expression] ?: return data
|
||||||
|
val arguments = expression.getArgumentsWithIr()
|
||||||
|
val argumentsResult = arguments.fold(data) { set, arg -> arg.second.accept(this, set) }
|
||||||
|
var callResult = BitSet()
|
||||||
|
var first = true
|
||||||
|
for (callSite in devirtualizedCallSite) {
|
||||||
|
val callee = callSite.actualCallee.irFunction ?: error("No IR for: ${callSite.actualCallee}")
|
||||||
|
updateResultForFunction(callee, argumentsResult)
|
||||||
|
if (first) {
|
||||||
|
callResult = getResultAfterCall(callee, BitSet())
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
val otherSet = getResultAfterCall(callee, BitSet())
|
||||||
|
callResult.and(otherSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return argumentsResult.copy().also { it.or(callResult) }
|
||||||
|
}
|
||||||
|
}, BitSet())
|
||||||
|
|
||||||
|
if (analysisGoal == AnalysisGoal.ComputeInitializedAfterCall) {
|
||||||
|
if (!node.symbol.isTopLevelFieldInitializer)
|
||||||
|
initializedFiles.afterCall[irDeclaration as IrFunction] = returnTargetsInitializedFiles[irDeclaration.symbol] ?: callerResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeRedundantCalls(context: Context, callGraph: CallGraph, rootSet: Set<IrFunction>) {
|
||||||
|
val analysisResult = InterproceduralAnalysis(context, callGraph, rootSet).analyze()
|
||||||
|
|
||||||
|
var numberOfFunctionsWithGlobalInitializerCall = 0
|
||||||
|
var numberOfFunctionsWithThreadLocalInitializerCall = 0
|
||||||
|
var numberOfRemovedGlobalInitializerCalls = 0
|
||||||
|
var numberOfRemovedThreadLocalInitializerCalls = 0
|
||||||
|
var numberOfCallSitesToFunctionsWithGlobalInitializerCall = 0
|
||||||
|
var numberOfCallSitesToFunctionsWithThreadLocalInitializerCall = 0
|
||||||
|
var numberOfCallSitesWithExtractedGlobalInitializerCall = 0
|
||||||
|
var numberOfCallSitesWithExtractedThreadLocalInitializerCall = 0
|
||||||
|
|
||||||
|
context.irModule!!.transformChildren(object : IrElementTransformer<IrBuilderWithScope?> {
|
||||||
|
override fun visitDeclaration(declaration: IrDeclarationBase, data: IrBuilderWithScope?): IrStatement {
|
||||||
|
return super.visitDeclaration(declaration, context.createIrBuilder(declaration.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: IrBuilderWithScope?): IrExpression {
|
||||||
|
expression.transformChildren(this, data)
|
||||||
|
|
||||||
|
val callee = expression.actualCallee
|
||||||
|
val body = callee.body ?: return expression
|
||||||
|
val initializerCalls = (body as IrBlockBody).statements
|
||||||
|
.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
|
||||||
|
&& callee !in analysisResult.functionsRequiringGlobalInitializerCall
|
||||||
|
val isNotOptimizedAwayThreadLocalInitializerCall = (calleeOrigin == DECLARATION_ORIGIN_FILE_THREAD_LOCAL_INITIALIZER
|
||||||
|
|| calleeOrigin == DECLARATION_ORIGIN_FILE_STANDALONE_THREAD_LOCAL_INITIALIZER)
|
||||||
|
&& callee !in analysisResult.functionsRequiringThreadLocalInitializerCall
|
||||||
|
if (isNotOptimizedAwayGlobalInitializerCall)
|
||||||
|
++numberOfCallSitesToFunctionsWithGlobalInitializerCall
|
||||||
|
if (isNotOptimizedAwayThreadLocalInitializerCall)
|
||||||
|
++numberOfCallSitesToFunctionsWithThreadLocalInitializerCall
|
||||||
|
val canExtractGlobalInitializerCall = isNotOptimizedAwayGlobalInitializerCall
|
||||||
|
&& expression in analysisResult.callSitesRequiringGlobalInitializerCall
|
||||||
|
val canExtractThreadLocalInitializerCall = isNotOptimizedAwayThreadLocalInitializerCall
|
||||||
|
&& expression in analysisResult.callSitesRequiringThreadLocalInitializerCall
|
||||||
|
if (canExtractGlobalInitializerCall)
|
||||||
|
++numberOfCallSitesWithExtractedGlobalInitializerCall
|
||||||
|
if (canExtractThreadLocalInitializerCall)
|
||||||
|
++numberOfCallSitesWithExtractedThreadLocalInitializerCall
|
||||||
|
canExtractGlobalInitializerCall || canExtractThreadLocalInitializerCall
|
||||||
|
}
|
||||||
|
if (initializerCalls.isEmpty()) return expression
|
||||||
|
|
||||||
|
return data!!.irBlock(expression) {
|
||||||
|
initializerCalls.forEach { +irCallFileInitializer((it as IrCall).symbol) }
|
||||||
|
+expression
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, data = null)
|
||||||
|
|
||||||
|
context.irModule!!.transformChildrenVoid(object : IrElementTransformerVoid() {
|
||||||
|
override fun visitFunction(declaration: IrFunction): IrStatement {
|
||||||
|
val body = declaration.body ?: return declaration
|
||||||
|
val statements = (body as IrBlockBody).statements
|
||||||
|
val globalInitializerCallIndex = statements
|
||||||
|
.take(2) // The very first statements by construction.
|
||||||
|
.indexOfFirst {
|
||||||
|
val calleeOrigin = (it as? IrCall)?.symbol?.owner?.origin
|
||||||
|
calleeOrigin == DECLARATION_ORIGIN_FILE_GLOBAL_INITIALIZER
|
||||||
|
}
|
||||||
|
if (globalInitializerCallIndex >= 0) {
|
||||||
|
++numberOfFunctionsWithGlobalInitializerCall
|
||||||
|
if (declaration !in analysisResult.functionsRequiringGlobalInitializerCall) {
|
||||||
|
++numberOfRemovedGlobalInitializerCalls
|
||||||
|
statements.removeAt(globalInitializerCallIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val threadLocalInitializerCallIndex = statements
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
if (threadLocalInitializerCallIndex >= 0) {
|
||||||
|
++numberOfFunctionsWithThreadLocalInitializerCall
|
||||||
|
if (declaration !in analysisResult.functionsRequiringThreadLocalInitializerCall) {
|
||||||
|
++numberOfRemovedThreadLocalInitializerCalls
|
||||||
|
statements.removeAt(threadLocalInitializerCallIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return declaration
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
context.log { "Removed ${numberOfRemovedGlobalInitializerCalls * 100.0 / numberOfFunctionsWithGlobalInitializerCall}% global initializers" }
|
||||||
|
context.log { "Removed ${numberOfRemovedThreadLocalInitializerCalls * 100.0 / numberOfFunctionsWithThreadLocalInitializerCall}% thread local initializers" }
|
||||||
|
context.log { "Removed ${(numberOfCallSitesWithExtractedGlobalInitializerCall) * 100.0 / numberOfCallSitesToFunctionsWithGlobalInitializerCall}% global initializer calls" }
|
||||||
|
context.log { "Removed ${(numberOfCallSitesWithExtractedThreadLocalInitializerCall) * 100.0 / numberOfCallSitesToFunctionsWithThreadLocalInitializerCall}% thread local initializer calls" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1424,6 +1424,51 @@ standaloneTest("initializers_failInInitializer4") {
|
|||||||
flags = ['-Xir-property-lazy-initialization']
|
flags = ['-Xir-property-lazy-initialization']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_when1") {
|
||||||
|
source = "codegen/initializers/when1.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_when2") {
|
||||||
|
source = "codegen/initializers/when2.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_throw1") {
|
||||||
|
source = "codegen/initializers/throw1.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_throw2") {
|
||||||
|
source = "codegen/initializers/throw2.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_while1") {
|
||||||
|
source = "codegen/initializers/while1.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_while2") {
|
||||||
|
source = "codegen/initializers/while2.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_while3") {
|
||||||
|
source = "codegen/initializers/while3.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_return1") {
|
||||||
|
source = "codegen/initializers/return1.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("initializers_return2") {
|
||||||
|
source = "codegen/initializers/return2.kt"
|
||||||
|
goldValue = "42\n"
|
||||||
|
}
|
||||||
|
|
||||||
linkTest("initializers_linkTest1") {
|
linkTest("initializers_linkTest1") {
|
||||||
goldValue = "1200\n"
|
goldValue = "1200\n"
|
||||||
source = "codegen/initializers/linkTest1_main.kt"
|
source = "codegen/initializers/linkTest1_main.kt"
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val y = getY()
|
||||||
|
|
||||||
|
private fun getY() = 42
|
||||||
|
|
||||||
|
fun bar(x: Int) = x == 0
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
fun foo(x: Int) {
|
||||||
|
if (x <= 0) return
|
||||||
|
bar(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(0)
|
||||||
|
println(y)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val y = getY()
|
||||||
|
|
||||||
|
private fun getY() = 42
|
||||||
|
|
||||||
|
fun bar(x: Int) = x == 0
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
inline fun foo(x: Int) {
|
||||||
|
if (x <= 0) return
|
||||||
|
bar(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(0)
|
||||||
|
println(y)
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val x = computeX()
|
||||||
|
|
||||||
|
private fun computeX() = 42
|
||||||
|
|
||||||
|
fun baz1() { }
|
||||||
|
|
||||||
|
fun baz2() { }
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
fun bar(x: Int) = if (x == 0) error("") else x
|
||||||
|
|
||||||
|
fun foo(x: Int) {
|
||||||
|
try {
|
||||||
|
bar(x)
|
||||||
|
baz1()
|
||||||
|
} catch (t: Throwable) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(0)
|
||||||
|
println(x)
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val y = computeY()
|
||||||
|
|
||||||
|
private fun computeY() = 42
|
||||||
|
|
||||||
|
fun baz1() { }
|
||||||
|
|
||||||
|
fun baz2() { }
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
fun bar(x: Int) = if (x == 0) error("") else x
|
||||||
|
|
||||||
|
fun foo(x: Int) {
|
||||||
|
try {
|
||||||
|
bar(x)
|
||||||
|
baz1()
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
println(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(0)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val y = getY()
|
||||||
|
|
||||||
|
private fun getY() = 42
|
||||||
|
|
||||||
|
fun bar(x: Int) = x == 0
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
fun foo(x: Int) = when {
|
||||||
|
x > 0 -> 42
|
||||||
|
bar(x) -> 117
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(123)
|
||||||
|
println(y)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val y = getY()
|
||||||
|
|
||||||
|
private fun getY() = 42
|
||||||
|
|
||||||
|
fun bar(x: Int) = x == 0
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
fun foo(x: Int) {
|
||||||
|
if (x > 0) bar(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(-1)
|
||||||
|
println(y)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val y = getY()
|
||||||
|
|
||||||
|
private fun getY() = 42
|
||||||
|
|
||||||
|
fun bar(x: Int) = x == 0
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
fun foo(x: Int) {
|
||||||
|
var i = 0
|
||||||
|
while (i < x) {
|
||||||
|
bar(i)
|
||||||
|
++i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(0)
|
||||||
|
println(y)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
val y = getY()
|
||||||
|
|
||||||
|
private fun getY() = 42
|
||||||
|
|
||||||
|
fun bar(x: Int) = x == 0
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
fun foo(x: Int) {
|
||||||
|
var i = 0
|
||||||
|
do {
|
||||||
|
if (i == x) break
|
||||||
|
++i
|
||||||
|
} while (bar(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(0)
|
||||||
|
println(y)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
val y = getY()
|
||||||
|
|
||||||
|
private fun getY() = 42
|
||||||
|
|
||||||
|
fun bar(x: Int) = x == 0
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
fun foo(x: Int) {
|
||||||
|
var i = 0
|
||||||
|
do {
|
||||||
|
++i
|
||||||
|
if (i > 0) continue
|
||||||
|
bar(i)
|
||||||
|
} while (i < x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
foo(0)
|
||||||
|
println(y)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user