Skip to content

Commit 392181d

Browse files
homurollSpace Team
authored and
Space Team
committed
[K/N] Devirtualization: fixed the problem with type checks
... during selection of proper callee #KT-67218 Fixed
1 parent 9ac91e4 commit 392181d

File tree

1 file changed

+83
-37
lines changed
  • kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations

1 file changed

+83
-37
lines changed

kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DevirtualizationAnalysis.kt

+83-37
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.backend.konan.util.IntArrayList
1717
import org.jetbrains.kotlin.backend.konan.util.LongArrayList
1818
import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction
1919
import org.jetbrains.kotlin.descriptors.ClassKind
20-
import org.jetbrains.kotlin.descriptors.explicitParameters
2120
import org.jetbrains.kotlin.ir.IrElement
2221
import org.jetbrains.kotlin.ir.builders.*
2322
import 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

Comments
 (0)