JVM_IR generate range loops as counter loops when possible

This commit is contained in:
Dmitry Petrov
2021-07-28 16:21:01 +03:00
committed by teamcityserver
parent d0f207071c
commit 38d6c8ded0
35 changed files with 167 additions and 223 deletions
@@ -80,7 +80,8 @@ class ProgressionHeaderInfo(
isReversed: Boolean = false,
canOverflow: Boolean? = null,
direction: ProgressionDirection,
val additionalStatements: List<IrStatement> = listOf()
val additionalStatements: List<IrStatement> = listOf(),
val originalLastInclusive: IrExpression? = null
) : NumericHeaderInfo(
progressionType, first, last, step, isLastInclusive,
canCacheLast = true,
@@ -88,6 +89,22 @@ class ProgressionHeaderInfo(
direction = direction
) {
fun revertToLastInclusive(): ProgressionHeaderInfo? =
originalLastInclusive?.let {
ProgressionHeaderInfo(
progressionType = progressionType,
first = first,
last = originalLastInclusive,
step = step,
isLastInclusive = true,
isReversed = isReversed,
canOverflow = canOverflow,
direction = direction,
additionalStatements = additionalStatements,
originalLastInclusive = null
)
}
val canOverflow: Boolean by lazy {
if (canOverflow != null) return@lazy canOverflow
@@ -148,21 +165,23 @@ class ProgressionHeaderInfo(
}
}
override fun asReversed() = if (isLastInclusive) {
ProgressionHeaderInfo(
progressionType = progressionType,
first = last,
last = first,
step = step.negate(),
isReversed = !isReversed,
direction = direction.asReversed(),
additionalStatements = additionalStatements
)
} else {
// If reversed, we would have a "first-exclusive" loop. We are currently not supporting this since it would add more complexity
// due to possible overflow when pre-incrementing the loop variable (see KT-42533).
null
}
override fun asReversed(): HeaderInfo? =
if (isLastInclusive) {
ProgressionHeaderInfo(
progressionType = progressionType,
first = last,
last = first,
step = step.negate(),
isReversed = !isReversed,
direction = direction.asReversed(),
additionalStatements = additionalStatements
)
} else {
// If reversed, we would have a "first-exclusive" loop. We are currently not supporting this since it would add more complexity
// due to possible overflow when pre-incrementing the loop variable (see KT-42533).
revertToLastInclusive()?.asReversed()
}
}
/**
@@ -304,7 +304,7 @@ class ProgressionLoopHeader(
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
with(builder) {
val newLoop = if (headerInfo.canOverflow) {
if (headerInfo.canOverflow) {
// If the induction variable CAN overflow, we cannot use it in the loop condition. Loop is lowered into something like:
//
// if (inductionVar <= last) {
@@ -315,7 +315,7 @@ class ProgressionLoopHeader(
// // Loop body
// } while (loopVar != last)
// }
IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
val newLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
val loopVariableExpression = irGet(loopVariable!!).let {
headerInfo.progressionType.run {
if (this is UnsignedProgressionType) {
@@ -328,8 +328,30 @@ class ProgressionLoopHeader(
condition = irNotEquals(loopVariableExpression, lastExpression)
body = newBody
}
val loopCondition = buildLoopCondition(this@with)
LoopReplacement(newLoop, irIfThen(loopCondition, newLoop))
} else if (!headerInfo.isLastInclusive) {
// It is critically important for loop code performance on JVM to "look like" a simple counter loop in Java when possible
// (`for (int i = first; i < lastExclusive; ++i) { ... }`).
// Otherwise loop-related optimizations will not kick in, resulting in significant performance degradation.
//
// Use a simple while loop:
//
// while (inductionVar < last) {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// }
//
val newLoop = IrWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
label = oldLoop.label
condition = buildLoopCondition(this@with)
body = newBody
}
LoopReplacement(newLoop, newLoop)
} else {
// If the induction variable can NOT overflow, use a do-while loop. Loop is lowered into something like:
// Use an if-guarded do-while loop (note the difference in loop condition):
//
// if (inductionVar <= last) {
// do {
@@ -339,17 +361,14 @@ class ProgressionLoopHeader(
// } while (inductionVar <= last)
// }
//
// Even though this can be simplified into a simpler while loop, using if + do-while (i.e., doing a loop inversion)
// performs better in benchmarks. In cases where `last` is a constant, the `if` may be optimized away.
IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
val newLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
label = oldLoop.label
condition = buildLoopCondition(this@with)
body = newBody
}
val loopCondition = buildLoopCondition(this@with)
LoopReplacement(newLoop, irIfThen(loopCondition, newLoop))
}
val loopCondition = buildLoopCondition(this@with)
LoopReplacement(newLoop, irIfThen(loopCondition, newLoop))
}
}
@@ -26,17 +26,18 @@ internal abstract class IndicesHandler(protected val context: CommonBackendConte
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// `last = array.size - 1` (last is inclusive) for the loop `for (i in array.indices)`.
val last = irCall(expression.symbol.owner.extensionReceiverParameter!!.type.sizePropertyGetter).apply {
dispatchReceiver = expression.extensionReceiver!!
}.decrement()
val last = irCall(expression.symbol.owner.extensionReceiverParameter!!.type.sizePropertyGetter)
.apply { dispatchReceiver = expression.extensionReceiver!! }
ProgressionHeaderInfo(
data,
first = irInt(0),
last = last,
step = irInt(1),
isLastInclusive = false,
canOverflow = false,
direction = ProgressionDirection.INCREASING
direction = ProgressionDirection.INCREASING,
originalLastInclusive = last.decrement()
)
}
@@ -15,8 +15,11 @@ import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionType
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstKind
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.util.OperatorNameConventions
/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */
@@ -34,12 +37,61 @@ internal class RangeToHandler(private val context: CommonBackendContext) :
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol) =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
val last = expression.getValueArgument(0)!!
// Convert range with inclusive upper bound to exclusive upper bound if possible.
// This affects loop code performance on JVM.
if (canUseExclusiveUpperBound(last, data)) {
val lastExclusive = last.convertToExclusiveUpperBound()
if (lastExclusive != null) {
return@with ProgressionHeaderInfo(
data,
first = expression.dispatchReceiver!!,
last = lastExclusive,
step = irInt(1),
direction = ProgressionDirection.INCREASING,
isLastInclusive = false,
originalLastInclusive = last
)
}
}
ProgressionHeaderInfo(
data,
first = expression.dispatchReceiver!!,
last = expression.getValueArgument(0)!!,
last = last,
step = irInt(1),
direction = ProgressionDirection.INCREASING
)
}
private fun canUseExclusiveUpperBound(last: IrExpression, progressionType: ProgressionType): Boolean {
val lastLongValue = last.constLongValue
?: return false
return if (progressionType is UnsignedProgressionType) {
lastLongValue != -1L
} else {
lastLongValue != progressionType.maxValueAsLong
}
}
private fun IrExpression.convertToExclusiveUpperBound(): IrConstImpl<out Any>? {
val irConst = this as? IrConst<*> ?: return null
return when (irConst.kind) {
IrConstKind.Char ->
IrConstImpl.char(startOffset, endOffset, type, IrConstKind.Char.valueOf(irConst).inc())
IrConstKind.Byte ->
IrConstImpl.byte(startOffset, endOffset, type, IrConstKind.Byte.valueOf(irConst).inc())
IrConstKind.Short ->
IrConstImpl.short(startOffset, endOffset, type, IrConstKind.Short.valueOf(irConst).inc())
IrConstKind.Int ->
IrConstImpl.int(startOffset, endOffset, type, IrConstKind.Int.valueOf(irConst).inc())
IrConstKind.Long ->
IrConstImpl.long(startOffset, endOffset, type, IrConstKind.Long.valueOf(irConst).inc())
else ->
null
}
}
}
@@ -43,7 +43,7 @@ internal class StepHandler(
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// Retrieve the HeaderInfo from the underlying progression (if any).
val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) as? ProgressionHeaderInfo
var nestedInfo = expression.extensionReceiver!!.accept(visitor, null) as? ProgressionHeaderInfo
?: return null
if (!nestedInfo.isLastInclusive) {
@@ -51,7 +51,8 @@ internal class StepHandler(
// underlying progression is last-exclusive, we must decrement the nested "last" by the step. However, this can cause
// underflow if "last" is MIN_VALUE. We will not support fully optimizing this scenario (e.g., `for (i in A until B step C`)
// for now. It will be partly optimized via DefaultProgressionHandler.
return null
nestedInfo = nestedInfo.revertToLastInclusive()
?: return null
}
val stepArg = expression.getValueArgument(0)!!