[K/N] Slight devirtualization improvement

Make devirtualization not by an actual receiver type but rather
by an actual virtual method implementation (which sometimes can be better)
This commit is contained in:
Igor Chevdar
2023-05-09 21:10:39 +03:00
committed by Space Team
parent 5b474bc5d3
commit 96c142569d
@@ -1361,6 +1361,7 @@ internal object DevirtualizationAnalysis {
fun devirtualize(irModule: IrModuleFragment, context: Context, externalModulesDFG: ExternalModulesDFG, fun devirtualize(irModule: IrModuleFragment, context: Context, externalModulesDFG: ExternalModulesDFG,
devirtualizedCallSites: Map<IrCall, DevirtualizedCallSite>) { devirtualizedCallSites: Map<IrCall, DevirtualizedCallSite>) {
val symbols = context.ir.symbols val symbols = context.ir.symbols
val irBuiltIns = context.irBuiltIns
val nativePtrEqualityOperatorSymbol = symbols.areEqualByValue[PrimitiveBinaryType.POINTER]!! val nativePtrEqualityOperatorSymbol = symbols.areEqualByValue[PrimitiveBinaryType.POINTER]!!
val optimize = context.shouldOptimize() val optimize = context.shouldOptimize()
@@ -1456,9 +1457,8 @@ internal object DevirtualizationAnalysis {
} }
fun IrBuilderWithScope.irDevirtualizedCall(callSite: IrCall, actualType: IrType, fun IrBuilderWithScope.irDevirtualizedCall(callSite: IrCall, actualType: IrType,
devirtualizedCallee: DevirtualizedCallee, actualCallee: DataFlowIR.FunctionSymbol.Declared,
arguments: List<PossiblyCoercedValue>): IrExpression { arguments: List<PossiblyCoercedValue>): IrExpression {
val actualCallee = devirtualizedCallee.callee as DataFlowIR.FunctionSymbol.Declared
return actualCallee.bridgeTarget.let { bridgeTarget -> return actualCallee.bridgeTarget.let { bridgeTarget ->
if (bridgeTarget == null) if (bridgeTarget == null)
irDevirtualizedCall(callSite, actualType, irDevirtualizedCall(callSite, actualType,
@@ -1491,12 +1491,13 @@ internal object DevirtualizationAnalysis {
if (expression.superQualifierSymbol == null && expression.symbol.owner.isOverridable) if (expression.superQualifierSymbol == null && expression.symbol.owner.isOverridable)
++callSitesCount ++callSitesCount
val devirtualizedCallSite = devirtualizedCallSites[expression] val devirtualizedCallSite = devirtualizedCallSites[expression] ?: return expression
val possibleCallees = devirtualizedCallSite?.possibleCallees val possibleCallees = devirtualizedCallSite.possibleCallees.groupBy {
if (possibleCallees == null if (it.receiverType is DataFlowIR.Type.External) return expression
|| possibleCallees.any { it.callee is DataFlowIR.FunctionSymbol.External } it.callee as? DataFlowIR.FunctionSymbol.Declared ?: return expression
|| possibleCallees.any { it.receiverType is DataFlowIR.Type.External }) }.entries.map { entry ->
return expression entry.key to entry.value.map { it.receiverType as DataFlowIR.Type.Declared }.distinct()
}
val caller = data ?: error("At this point code is expected to have been moved to a declaration: ${expression.render()}") val caller = data ?: error("At this point code is expected to have been moved to a declaration: ${expression.render()}")
val callee = expression.symbol.owner val callee = expression.symbol.owner
@@ -1540,7 +1541,7 @@ internal object DevirtualizationAnalysis {
// Temporary val is not required here for a parameter, since each one is used for only one devirtualized callsite // Temporary val is not required here for a parameter, since each one is used for only one devirtualized callsite
irSplitCoercion(caller, arg.second, tempName = null, arg.first.owner.type) irSplitCoercion(caller, arg.second, tempName = null, arg.first.owner.type)
} }
+irDevirtualizedCall(expression, type, possibleCallees[0], parameters) +irDevirtualizedCall(expression, type, possibleCallees[0].first, parameters)
} }
} }
@@ -1548,34 +1549,56 @@ internal object DevirtualizationAnalysis {
val arguments = expression.getArgumentsWithSymbols().mapIndexed { index, arg -> val arguments = expression.getArgumentsWithSymbols().mapIndexed { index, arg ->
irSplitCoercion(caller, arg.second, "arg$index", arg.first.owner.type) irSplitCoercion(caller, arg.second, "arg$index", arg.first.owner.type)
} }
val typeInfo = irTemporary(irCall(symbols.getObjectTypeInfo).apply { val receiver = irTemporary(arguments[0].getFullValue(this@irBlock))
putValueArgument(0, arguments[0].getFullValue(this@irBlock)) val typeInfo by lazy {
}) irTemporary(irCall(symbols.getObjectTypeInfo).apply {
putValueArgument(0, irGet(receiver))
})
}
val branches = mutableListOf<IrBranchImpl>() val branches = mutableListOf<IrBranchImpl>()
possibleCallees.mapIndexedTo(branches) { index, devirtualizedCallee -> possibleCallees
val actualReceiverType = devirtualizedCallee.receiverType as DataFlowIR.Type.Declared // Try to leave the most complicated case for the last,
val expectedTypeInfo = IrClassReferenceImpl( // and, hopefully, place it in the else clause.
startOffset, endOffset, .sortedBy { it.second.size }
symbols.nativePtrType, .mapIndexedTo(branches) { index, devirtualizedCallee ->
actualReceiverType.irClass!!.symbol, val (actualCallee, receiverTypes) = devirtualizedCallee
actualReceiverType.irClass.defaultType val condition =
) if (optimize && index == possibleCallees.size - 1)
val condition = irTrue() // Don't check last type in optimize mode.
if (optimize && index == possibleCallees.size - 1) else {
irTrue() // Don't check last type in optimize mode. if (receiverTypes.size == 1) {
else // It is faster to just compare type infos instead of a full type check.
irCall(nativePtrEqualityOperatorSymbol).apply { val receiverType = receiverTypes[0]
putValueArgument(0, irGet(typeInfo)) val expectedTypeInfo = IrClassReferenceImpl(
putValueArgument(1, expectedTypeInfo) startOffset, endOffset,
} symbols.nativePtrType,
IrBranchImpl( receiverType.irClass!!.symbol,
startOffset = startOffset, receiverType.irClass.defaultType
endOffset = endOffset, )
condition = condition, irCall(nativePtrEqualityOperatorSymbol).apply {
result = irDevirtualizedCall(expression, type, devirtualizedCallee, arguments) putValueArgument(0, irGet(typeInfo))
) putValueArgument(1, expectedTypeInfo)
} }
} else {
val receiverType = actualCallee.irFunction!!.parentAsClass
IrTypeOperatorCallImpl(
startOffset = startOffset,
endOffset = endOffset,
type = irBuiltIns.booleanType,
operator = IrTypeOperator.INSTANCEOF,
typeOperand = receiverType.defaultType,
argument = irGet(receiver)
)
}
}
IrBranchImpl(
startOffset = startOffset,
endOffset = endOffset,
condition = condition,
result = irDevirtualizedCall(expression, type, actualCallee, arguments)
)
}
if (!optimize) { // Add else branch throwing exception for debug purposes. if (!optimize) { // Add else branch throwing exception for debug purposes.
branches.add(IrBranchImpl( branches.add(IrBranchImpl(
startOffset = startOffset, startOffset = startOffset,