@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.backend.konan.util.IntArrayList
17
17
import org.jetbrains.kotlin.backend.konan.util.LongArrayList
18
18
import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction
19
19
import org.jetbrains.kotlin.descriptors.ClassKind
20
- import org.jetbrains.kotlin.descriptors.explicitParameters
21
20
import org.jetbrains.kotlin.ir.IrElement
22
21
import org.jetbrains.kotlin.ir.builders.*
23
22
import org.jetbrains.kotlin.ir.declarations.*
@@ -1546,6 +1545,57 @@ internal object DevirtualizationAnalysis {
1546
1545
}
1547
1546
1548
1547
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
+
1549
1599
val arguments = expression.getArgumentsWithIr().mapIndexed { index, arg ->
1550
1600
irSplitCoercion(caller, arg.second, " arg$index " , arg.first.type)
1551
1601
}
@@ -1555,45 +1605,41 @@ internal object DevirtualizationAnalysis {
1555
1605
putValueArgument(0 , irGet(receiver))
1556
1606
})
1557
1607
}
1558
-
1559
1608
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
1595
1624
)
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
+ }
1596
1634
}
1635
+ }
1636
+ IrBranchImpl (
1637
+ startOffset = startOffset,
1638
+ endOffset = endOffset,
1639
+ condition = condition,
1640
+ result = irDevirtualizedCall(expression, type, actualCallee, arguments)
1641
+ )
1642
+ }
1597
1643
if (! optimize) { // Add else branch throwing exception for debug purposes.
1598
1644
branches.add(IrBranchImpl (
1599
1645
startOffset = startOffset,
0 commit comments