@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.backend.konan.util.IntArrayList
1717import org.jetbrains.kotlin.backend.konan.util.LongArrayList
1818import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction
1919import org.jetbrains.kotlin.descriptors.ClassKind
20- import org.jetbrains.kotlin.descriptors.explicitParameters
2120import org.jetbrains.kotlin.ir.IrElement
2221import org.jetbrains.kotlin.ir.builders.*
2322import org.jetbrains.kotlin.ir.declarations.*
@@ -1546,6 +1545,57 @@ internal object DevirtualizationAnalysis {
15461545 }
15471546
15481547 else -> irBlock(expression) {
1548+ /*
1549+ * More than one possible callee - need to select the proper one.
1550+ * There are two major cases here:
1551+ * - there is only one possible receiver type, and all what is needed is just compare the type infos
1552+ * - otherwise, there are multiple receiver types (meaning the actual callee has not been overridden in
1553+ * the inheritors), and a full type check operation is required.
1554+ * These checks cannot be performed in arbitrary order - the check for a derived type must be
1555+ * performed before the check for the base type.
1556+ * To improve performance, we try to perform these checks in the following order: first, those with only one
1557+ * receiver, then classes type checks, and finally interface type checks.
1558+ * Note: performing the slowest check last allows to place it to else clause and skip it improving performance.
1559+ * The actual order in which perform these checks is found by a simple back tracking algorithm
1560+ * (since the number of possible callees is small, it is ok in terms of performance).
1561+ */
1562+
1563+ data class Target (val actualCallee : DataFlowIR .FunctionSymbol .Declared , val possibleReceivers : List <DataFlowIR .Type .Declared >) {
1564+ val declType = actualCallee.irFunction!! .parentAsClass
1565+ val weight = when {
1566+ possibleReceivers.size == 1 -> 0 // The fastest.
1567+ declType.isInterface -> 2 // The slowest.
1568+ else -> 1 // In between.
1569+ }
1570+ var used = false
1571+ }
1572+
1573+ val targets = possibleCallees.map { Target (it.first, it.second) }
1574+ var bestOrder: List <Target >? = null
1575+ var bestLexOrder = Int .MAX_VALUE
1576+ fun backTrack (order : List <Target >, lexOrder : Int ) {
1577+ if (order.size == targets.size) {
1578+ if (lexOrder < bestLexOrder) {
1579+ bestOrder = order
1580+ bestLexOrder = lexOrder
1581+ }
1582+ return
1583+ }
1584+ for (target in targets.filterNot { it.used }) {
1585+ val fitsAsNext = order.none { target.declType.isSubclassOf(it.declType) }
1586+ if (! fitsAsNext) continue
1587+ val nextOrder = order + target
1588+ // Don't count the last one since it will be in the else clause.
1589+ val nextLexOrder = if (nextOrder.size == targets.size) lexOrder else lexOrder * 3 + target.weight
1590+ target.used = true
1591+ backTrack(nextOrder, nextLexOrder)
1592+ target.used = false
1593+ }
1594+ }
1595+
1596+ backTrack(emptyList(), 0 )
1597+ require(bestLexOrder != Int .MAX_VALUE ) // Should never happen since there are no cycles in a type hierarchy.
1598+
15491599 val arguments = expression.getArgumentsWithIr().mapIndexed { index, arg ->
15501600 irSplitCoercion(caller, arg.second, " arg$index " , arg.first.type)
15511601 }
@@ -1555,45 +1605,41 @@ internal object DevirtualizationAnalysis {
15551605 putValueArgument(0 , irGet(receiver))
15561606 })
15571607 }
1558-
15591608 val branches = mutableListOf<IrBranchImpl >()
1560- possibleCallees
1561- // Try to leave the most complicated case for the last,
1562- // and, hopefully, place it in the else clause.
1563- .sortedBy { it.second.size }
1564- .mapIndexedTo(branches) { index, devirtualizedCallee ->
1565- val (actualCallee, receiverTypes) = devirtualizedCallee
1566- val condition =
1567- if (optimize && index == possibleCallees.size - 1 )
1568- irTrue() // Don't check last type in optimize mode.
1569- else {
1570- if (receiverTypes.size == 1 ) {
1571- // It is faster to just compare type infos instead of a full type check.
1572- val receiverType = receiverTypes[0 ]
1573- val expectedTypeInfo = IrClassReferenceImpl (
1574- startOffset, endOffset,
1575- symbols.nativePtrType,
1576- receiverType.irClass!! .symbol,
1577- receiverType.irClass.defaultType
1578- )
1579- irCall(nativePtrEqualityOperatorSymbol).apply {
1580- putValueArgument(0 , irGet(typeInfo))
1581- putValueArgument(1 , expectedTypeInfo)
1582- }
1583- } else {
1584- val receiverType = actualCallee.irFunction!! .parentAsClass
1585- irCall(isSubtype, listOf (receiverType.defaultType)).apply {
1586- putValueArgument(0 , irGet(typeInfo))
1587- }
1588- }
1589- }
1590- IrBranchImpl (
1591- startOffset = startOffset,
1592- endOffset = endOffset,
1593- condition = condition,
1594- result = irDevirtualizedCall(expression, type, actualCallee, arguments)
1609+ bestOrder!! .mapIndexedTo(branches) { index, target ->
1610+ val (actualCallee, receiverTypes) = target
1611+ val condition = when {
1612+ optimize && index == possibleCallees.size - 1 -> {
1613+ // Don't check the last type in optimize mode.
1614+ irTrue()
1615+ }
1616+ receiverTypes.size == 1 -> {
1617+ // It is faster to just compare type infos instead of a full type check.
1618+ val receiverType = receiverTypes[0 ]
1619+ val expectedTypeInfo = IrClassReferenceImpl (
1620+ startOffset, endOffset,
1621+ symbols.nativePtrType,
1622+ receiverType.irClass!! .symbol,
1623+ receiverType.irClass.defaultType
15951624 )
1625+ irCall(nativePtrEqualityOperatorSymbol).apply {
1626+ putValueArgument(0 , irGet(typeInfo))
1627+ putValueArgument(1 , expectedTypeInfo)
1628+ }
1629+ }
1630+ else -> {
1631+ irCall(isSubtype, listOf (target.declType.defaultType)).apply {
1632+ putValueArgument(0 , irGet(typeInfo))
1633+ }
15961634 }
1635+ }
1636+ IrBranchImpl (
1637+ startOffset = startOffset,
1638+ endOffset = endOffset,
1639+ condition = condition,
1640+ result = irDevirtualizedCall(expression, type, actualCallee, arguments)
1641+ )
1642+ }
15971643 if (! optimize) { // Add else branch throwing exception for debug purposes.
15981644 branches.add(IrBranchImpl (
15991645 startOffset = startOffset,
0 commit comments