Minor: extract *LoopHeader classes to separate files

This commit is contained in:
Dmitry Petrov
2021-12-06 13:38:34 +03:00
committed by teamcity
parent 77fd42eb27
commit 256aaa3388
6 changed files with 814 additions and 721 deletions
@@ -8,19 +8,16 @@ package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irIfThen
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.util.OperatorNameConventions
/**
* Contains the loop and expression to replace the old loop.
@@ -63,470 +60,7 @@ fun IrStatement.isInductionVariable(context: CommonBackendContext) =
origin == context.inductionVariableOrigin &&
name.asString() == inductionVariableName
abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
val headerInfo: T,
builder: DeclarationIrBuilder,
protected val context: CommonBackendContext
) : ForLoopHeader {
override val consumesLoopVariableComponents = false
val inductionVariable: IrVariable
protected val stepVariable: IrVariable?
val stepExpression: IrExpression
protected val lastVariableIfCanCacheLast: IrVariable?
protected val lastExpression: IrExpression
// If this is not `IrExpressionWithCopy`, then it is `<IrGetValue>.getSize()` built in `IndexedGetIterationHandler`.
// It is therefore safe to deep-copy as it does not contain any functions or classes.
get() = field.shallowCopyOrNull() ?: field.deepCopyWithSymbols()
protected val symbols = context.ir.symbols
init {
with(builder) {
with(headerInfo.progressionType) {
// For this loop:
//
// for (i in first()..last() step step())
//
// We need to cast first(), last(). and step() to conform to the progression type so
// that operations on the induction variable within the loop are more efficient.
//
// In the above example, if first() is a Long and last() is an Int, this creates a
// LongProgression so last() should be cast to a Long.
inductionVariable =
scope.createTmpVariable(
headerInfo.first.asElementType(),
nameHint = inductionVariableName,
isMutable = true,
origin = this@NumericForLoopHeader.context.inductionVariableOrigin,
irType = elementClass.defaultType
)
// Due to features of PSI2IR we can obtain nullable arguments here while actually
// they are non-nullable (the frontend takes care about this). So we need to cast
// them to non-nullable.
// TODO: Confirm if casting to non-nullable is still necessary
val last = headerInfo.last.asElementType()
if (headerInfo.canCacheLast) {
val (variable, expression) = createLoopTemporaryVariableIfNecessary(last, nameHint = "last")
lastVariableIfCanCacheLast = variable
lastExpression = expression.shallowCopy()
} else {
lastVariableIfCanCacheLast = null
lastExpression = last
}
val (tmpStepVar, tmpStepExpression) =
createLoopTemporaryVariableIfNecessary(
ensureNotNullable(headerInfo.step.asStepType()),
nameHint = "step",
irType = stepClass.defaultType
)
stepVariable = tmpStepVar
stepExpression = tmpStepExpression
}
}
}
private fun DeclarationIrBuilder.ensureNotNullable(expression: IrExpression) =
if (expression.type is IrSimpleType && expression.type.isNullable()) {
irImplicitCast(expression, expression.type.makeNotNull())
} else {
expression
}
/** Statement used to increment the induction variable. */
protected fun incrementInductionVariable(builder: DeclarationIrBuilder): IrStatement = with(builder) {
with(headerInfo.progressionType) {
// inductionVariable = inductionVariable + step
// NOTE: We cannot use `stepExpression.type` to match the value parameter type because it may be of type `Nothing`.
// This happens in the case of an illegal step where the "step" is actually a `throw IllegalArgumentException(...)`.
val stepType = stepClass.defaultType
val plusFun = elementClass.defaultType.getClass()!!.functions.single {
it.name == OperatorNameConventions.PLUS &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type == stepType
}
irSet(
inductionVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(inductionVariable),
stepExpression.shallowCopy(), IrStatementOrigin.PLUSEQ
), IrStatementOrigin.PLUSEQ
)
}
}
protected fun buildLoopCondition(builder: DeclarationIrBuilder): IrExpression {
with(builder) {
with(headerInfo.progressionType) {
val builtIns = context.irBuiltIns
// Bounds are signed for unsigned progressions but bound comparisons should be done as unsigned, to ensure that the
// correct comparison function is used (`UInt/ULongCompare`). Also, `compareTo` must be used for UInt/ULong;
// they don't have intrinsic comparison operators.
val intCompFun = if (headerInfo.isLastInclusive) {
builtIns.lessOrEqualFunByOperandType.getValue(builtIns.intClass)
} else {
builtIns.lessFunByOperandType.getValue(builtIns.intClass)
}
val unsignedCompareToFun = if (this is UnsignedProgressionType) {
unsignedType.getClass()!!.functions.single {
it.name == OperatorNameConventions.COMPARE_TO &&
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null &&
it.valueParameters.size == 1 && it.valueParameters[0].type == unsignedType
}
} else null
val elementCompFun =
if (headerInfo.isLastInclusive) {
builtIns.lessOrEqualFunByOperandType[elementClass.symbol]
} else {
builtIns.lessFunByOperandType[elementClass.symbol]
}
fun conditionForDecreasing(): IrExpression =
// last <= inductionVar (use `<` if last is exclusive)
if (this is UnsignedProgressionType) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(unsignedCompareToFun!!).apply {
dispatchReceiver = lastExpression.asUnsigned()
putValueArgument(0, irGet(inductionVariable).asUnsigned())
})
putValueArgument(1, irInt(0))
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, lastExpression)
putValueArgument(1, irGet(inductionVariable))
}
}
fun conditionForIncreasing(): IrExpression =
// inductionVar <= last (use `<` if last is exclusive)
if (this is UnsignedProgressionType) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(unsignedCompareToFun!!).apply {
dispatchReceiver = irGet(inductionVariable).asUnsigned()
putValueArgument(0, lastExpression.asUnsigned())
})
putValueArgument(1, irInt(0))
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, lastExpression)
}
}
// The default condition depends on the direction.
return when (headerInfo.direction) {
ProgressionDirection.DECREASING -> conditionForDecreasing()
ProgressionDirection.INCREASING -> conditionForIncreasing()
ProgressionDirection.UNKNOWN -> {
// If the direction is unknown, we check depending on the "step" value:
// // (use `<` if last is exclusive)
// (step > 0 && inductionVar <= last) || (step < 0 || last <= inductionVar)
context.oror(
context.andand(
irCall(builtIns.greaterFunByOperandType.getValue(stepClass.symbol)).apply {
putValueArgument(0, stepExpression.shallowCopy())
putValueArgument(1, zeroStepExpression())
},
conditionForIncreasing()
),
context.andand(
irCall(builtIns.lessFunByOperandType.getValue(stepClass.symbol)).apply {
putValueArgument(0, stepExpression.shallowCopy())
putValueArgument(1, zeroStepExpression())
},
conditionForDecreasing()
)
)
}
}
}
}
}
}
class ProgressionLoopHeader(
headerInfo: ProgressionHeaderInfo,
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : NumericForLoopHeader<ProgressionHeaderInfo>(headerInfo, builder, context) {
private val preferJavaLikeCounterLoop = context.preferJavaLikeCounterLoop
// For this loop:
//
// for (i in first()..last() step step())
//
// ...the functions may have side-effects so we need to call them in the following order: first() (inductionVariable), last(), step().
// Additional variables come first as they may be needed to the subsequent variables.
//
// In the case of a reversed range, the `inductionVariable` and `last` variables are swapped, therefore the declaration order must be
// swapped to preserve the correct evaluation order.
override val loopInitStatements = headerInfo.additionalStatements + (
if (headerInfo.isReversed)
listOfNotNull(lastVariableIfCanCacheLast, inductionVariable)
else
listOfNotNull(inductionVariable, lastVariableIfCanCacheLast)
) +
listOfNotNull(stepVariable)
private var loopVariable: IrVariable? = null
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
builder: DeclarationIrBuilder,
backendContext: CommonBackendContext,
): List<IrStatement> =
with(builder) {
// loopVariable is used in the loop condition if it can overflow. If no loopVariable was provided, create one.
this@ProgressionLoopHeader.loopVariable = if (headerInfo.canOverflow && loopVariable == null) {
scope.createTmpVariable(
irGet(inductionVariable),
nameHint = "loopVariable",
isMutable = true
)
} else {
loopVariable?.initializer = irGet(inductionVariable).let {
headerInfo.progressionType.run {
if (this is UnsignedProgressionType) {
// The induction variable is signed for unsigned progressions but the loop variable should be unsigned.
it.asUnsigned()
} else it
}
}
loopVariable
}
// loopVariable = inductionVariable
// inductionVariable = inductionVariable + step
listOfNotNull(this@ProgressionLoopHeader.loopVariable, incrementInductionVariable(this))
}
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
with(builder) {
if (headerInfo.canOverflow ||
preferJavaLikeCounterLoop && headerInfo.progressionType is UnsignedProgressionType && headerInfo.isLastInclusive
) {
// If the induction variable CAN overflow, we cannot use it in the loop condition.
// Loop is lowered into something like:
//
// if (inductionVar <= last) {
// // Loop is not empty
// do {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// } while (loopVar != last)
// }
//
// This loop form is also preferable for loops over unsigned progressions on JVM,
// because HotSpot doesn't recognize unsigned integer comparison as a counter loop condition.
// Unsigned integer equality is fine, though.
// See KT-49444 for performance comparison example.
val newLoopOrigin = if (preferJavaLikeCounterLoop)
this@ProgressionLoopHeader.context.doWhileCounterLoopOrigin
else
oldLoop.origin
val newLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, newLoopOrigin).apply {
val loopVariableExpression = irGet(loopVariable!!).let {
headerInfo.progressionType.run {
if (this is UnsignedProgressionType) {
// The loop variable is signed but bounds are signed for unsigned progressions.
it.asSigned()
} else it
}
}
label = oldLoop.label
condition = irNotEquals(loopVariableExpression, lastExpression)
body = newBody
}
if (preferJavaLikeCounterLoop) {
moveInductionVariableUpdateToLoopCondition(newLoop)
}
val loopCondition = buildLoopCondition(this@with)
LoopReplacement(newLoop, irIfThen(loopCondition, newLoop))
} else if (preferJavaLikeCounterLoop && !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 do-while loop:
// do {
// if ( !( inductionVariable < last ) ) break
// val loopVariable = inductionVariable
// <body>
// } while ( { inductionVariable += step; true } )
// This loop form is equivalent to the Java counter loop shown above.
val newLoopCondition = buildLoopCondition(this@with)
buildJavaLikeDoWhileCounterLoop(oldLoop, newLoopCondition, newBody)
} else {
// Use an if-guarded do-while loop (note the difference in loop condition):
//
// if (inductionVar <= last) {
// do {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// } while (inductionVar <= last)
// }
//
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))
}
}
private val booleanNot =
context.irBuiltIns.booleanClass.owner.findDeclaration<IrSimpleFunction> {
it.name == OperatorNameConventions.NOT
} ?: error("No '${OperatorNameConventions.NOT}' in ${context.irBuiltIns.booleanClass.owner.render()}")
private fun moveInductionVariableUpdateToLoopCondition(doWhileLoop: IrDoWhileLoop) {
// On JVM, it's important that induction variable update happens in the end of the loop
// (otherwise HotSpot will not treat it as a counter loop).
// Moving induction variable update to loop condition (instead of just placing it in the end of loop body)
// also allows reusing loop variable as induction variable later.
//
// Transform a loop in the form:
// do {
// { <next> }
// <body>
// } while (<condition>)
// to
// do {
// { <next'> }
// <body>
// } while ( { if (!<condition>) break; <updateInductionVar>; true } )
val doWhileBody = doWhileLoop.body as? IrContainerExpression ?: return
if (doWhileBody.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE) return
val doWhileLoopNext = doWhileBody.statements[0] as? IrContainerExpression ?: return
if (doWhileLoopNext.origin != IrStatementOrigin.FOR_LOOP_NEXT) return
val updateInductionVarIndex = doWhileLoopNext.statements
.indexOfFirst { it is IrSetValue && it.symbol.owner.isInductionVariable(context) }
if (updateInductionVarIndex < 0) return
val updateInductionVar = doWhileLoopNext.statements[updateInductionVarIndex]
doWhileLoopNext.statements.removeAt(updateInductionVarIndex)
val loopCondition = doWhileLoop.condition
val loopConditionStartOffset = loopCondition.startOffset
val loopConditionEndOffset = loopCondition.endOffset
doWhileLoop.condition = IrCompositeImpl(
loopConditionStartOffset, loopConditionEndOffset, loopCondition.type,
origin = null,
statements = listOf(
createNegatedConditionCheck(doWhileLoop.condition, doWhileLoop),
updateInductionVar,
IrConstImpl.boolean(loopConditionStartOffset, loopConditionEndOffset, context.irBuiltIns.booleanType, true)
)
)
}
private fun buildJavaLikeDoWhileCounterLoop(
oldLoop: IrLoop,
newLoopCondition: IrExpression,
newBody: IrExpression?
): LoopReplacement {
// Transform loop:
// while (<newLoopCondition>) {
// { // FOR_LOOP_NEXT
// <initializeLoopIteration>
// <inductionVariableUpdate>
// }
// <originalLoopBody>
// }
// to:
// do {
// { // FOR_LOOP_NEXT
// if (!(<newLoopCondition>)) break
// <initializeLoopIteration>
// }
// <originalLoopBody>
// } while (
// {
// <inductionVariableUpdate>
// true
// }
// )
val bodyBlock = newBody as? IrContainerExpression
?: throw AssertionError("newBody: ${newBody?.dump()}")
val forLoopNextBlock = bodyBlock.statements[0] as? IrContainerExpression
?: throw AssertionError("bodyBlock[0]: ${bodyBlock.statements[0].dump()}")
if (forLoopNextBlock.origin != IrStatementOrigin.FOR_LOOP_NEXT)
throw AssertionError("FOR_LOOP_NEXT expected: ${forLoopNextBlock.dump()}")
val inductionVariableUpdate = forLoopNextBlock.statements.last() as? IrSetValue
?: throw AssertionError("forLoopNextBlock.last: ${forLoopNextBlock.statements.last().dump()}")
val doWhileLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, context.doWhileCounterLoopOrigin)
doWhileLoop.label = oldLoop.label
bodyBlock.statements[0] = IrCompositeImpl(
forLoopNextBlock.startOffset, forLoopNextBlock.endOffset,
forLoopNextBlock.type,
forLoopNextBlock.origin,
).apply {
statements.add(createNegatedConditionCheck(newLoopCondition, doWhileLoop))
if (forLoopNextBlock.statements.size >= 2)
statements.addAll(forLoopNextBlock.statements.subList(0, forLoopNextBlock.statements.lastIndex))
}
doWhileLoop.body = bodyBlock
val stepStartOffset = inductionVariableUpdate.startOffset
val stepEndOffset = inductionVariableUpdate.endOffset
val doWhileCondition =
IrCompositeImpl(
stepStartOffset, stepEndOffset, context.irBuiltIns.booleanType, null,
listOf(
inductionVariableUpdate,
IrConstImpl.boolean(stepStartOffset, stepEndOffset, context.irBuiltIns.booleanType, true)
)
)
doWhileLoop.condition = doWhileCondition
return LoopReplacement(doWhileLoop, doWhileLoop)
}
private fun createNegatedConditionCheck(newLoopCondition: IrExpression, doWhileLoop: IrDoWhileLoop): IrWhenImpl {
val conditionStartOffset = newLoopCondition.startOffset
val conditionEndOffset = newLoopCondition.endOffset
val negatedCondition =
IrCallImpl.fromSymbolOwner(conditionStartOffset, conditionEndOffset, booleanNot.symbol).apply {
dispatchReceiver = newLoopCondition
}
return IrWhenImpl(
conditionStartOffset, conditionEndOffset, context.irBuiltIns.unitType, null,
listOf(
IrBranchImpl(
negatedCondition,
IrBreakImpl(conditionStartOffset, conditionEndOffset, context.irBuiltIns.nothingType, doWhileLoop)
)
)
)
}
}
private class InitializerCallReplacer(val replacementCall: IrCall) : IrElementTransformerVoid() {
internal class InitializerCallReplacer(private val replacementCall: IrCall) : IrElementTransformerVoid() {
var initializerCall: IrCall? = null
override fun visitCall(expression: IrCall): IrCall {
@@ -540,255 +74,6 @@ private class InitializerCallReplacer(val replacementCall: IrCall) : IrElementTr
}
}
class IndexedGetLoopHeader(
headerInfo: IndexedGetHeaderInfo,
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : NumericForLoopHeader<IndexedGetHeaderInfo>(headerInfo, builder, context) {
override val loopInitStatements =
listOfNotNull(headerInfo.objectVariable, inductionVariable, lastVariableIfCanCacheLast, stepVariable)
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
builder: DeclarationIrBuilder,
backendContext: CommonBackendContext,
): List<IrStatement> =
with(builder) {
// loopVariable = objectVariable[inductionVariable]
val indexedGetFun = with(headerInfo.expressionHandler) { headerInfo.objectVariable.type.getFunction }
// Making sure that expression type has type of the variable when it exists.
// Return type of get function can be a type parameter (for example Array<T>::get) which is not a subtype of loopVariable type.
val get = irCall(indexedGetFun.symbol, type = loopVariable?.type ?: indexedGetFun.returnType).apply {
dispatchReceiver = irGet(headerInfo.objectVariable)
putValueArgument(0, irGet(inductionVariable))
}
// The call could be wrapped in an IMPLICIT_NOTNULL type-cast (see comment in ForLoopsLowering.gatherLoopVariableInfo()).
// Find and replace the call to preserve any type-casts.
loopVariable?.initializer = loopVariable?.initializer?.transform(InitializerCallReplacer(get), null)
// Even if there is no loop variable, we always want to call `get()` as it may have side-effects.
// The un-lowered loop always calls `get()` on each iteration.
listOf(loopVariable ?: get) + incrementInductionVariable(this)
}
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement = with(builder) {
// Loop is lowered into something like:
//
// var inductionVar = 0
// var last = objectVariable.size
// while (inductionVar < last) {
// val loopVar = objectVariable.get(inductionVar)
// inductionVar++
// // 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)
}
}
class WithIndexLoopHeader(
headerInfo: WithIndexHeaderInfo,
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : ForLoopHeader {
val nestedLoopHeader: ForLoopHeader
val indexVariable: IrVariable
private val ownsIndexVariable: Boolean
private val incrementIndexStatement: IrStatement?
init {
with(builder) {
// To build the optimized/lowered `for` loop over a `withIndex()` call, we first need the header for the underlying iterable so
// so that we know how to build the loop for that iterable. More info in comments in initializeIteration().
nestedLoopHeader = when (val nestedInfo = headerInfo.nestedInfo) {
is IndexedGetHeaderInfo -> IndexedGetLoopHeader(nestedInfo, this@with, context)
is ProgressionHeaderInfo -> ProgressionLoopHeader(nestedInfo, this@with, context)
is IterableHeaderInfo -> IterableLoopHeader(nestedInfo)
is WithIndexHeaderInfo -> throw IllegalStateException("Nested WithIndexHeaderInfo not allowed for WithIndexLoopHeader")
is FloatingPointRangeHeaderInfo, is ComparableRangeInfo -> error("Unexpected ${nestedInfo::class.simpleName} for loops")
}
// Do not build own indexVariable if the nested loop header has an inductionVariable == 0 and step == 1.
// This is the case when the underlying iterable is an array, CharSequence, or a progression from 0 with step 1.
// We can use the induction variable from the underlying iterable as the index variable, since it progresses in the same way.
if (nestedLoopHeader is NumericForLoopHeader<*> &&
nestedLoopHeader.inductionVariable.type.isInt() &&
nestedLoopHeader.inductionVariable.initializer?.constLongValue == 0L &&
nestedLoopHeader.stepExpression.constLongValue == 1L
) {
indexVariable = nestedLoopHeader.inductionVariable
ownsIndexVariable = false
incrementIndexStatement = null
} else {
indexVariable = scope.createTmpVariable(
irInt(0),
nameHint = "index",
isMutable = true
)
ownsIndexVariable = true
// `index++` during iteration initialization
// TODO: KT-34665: Check for overflow for Iterable and Sequence (call to checkIndexOverflow()).
val plusFun = indexVariable.type.getClass()!!.functions.first {
it.name == OperatorNameConventions.PLUS &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type.isInt()
}
incrementIndexStatement =
irSet(
indexVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(indexVariable),
irInt(1)
)
)
}
}
}
// Add the index variable (if owned) to the statements from the nested loop header.
override val loopInitStatements = nestedLoopHeader.loopInitStatements.let { if (ownsIndexVariable) it + indexVariable else it }
override val consumesLoopVariableComponents = true
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
builder: DeclarationIrBuilder,
backendContext: CommonBackendContext,
): List<IrStatement> =
with(builder) {
// The `withIndex()` extension function returns a lazy Iterable that wraps each element of the underlying iterable (e.g., array,
// progression, Iterable, Sequence, CharSequence) into an IndexedValue containing the index of that element and the element
// itself. The iterator for this lazy Iterable looks like this:
//
// internal class IndexingIterator<out T>(private val iterator: Iterator<T>) : Iterator<IndexedValue<T>> {
// private var index = 0
// override fun hasNext() = iterator.hasNext()
// override fun next() = IndexedValue(checkIndexOverflow(index++), iterator.next())
// }
//
// IndexedValue looks like this:
//
// data class IndexedValue<out T>(val index: Int, val value: T)
//
// For example, if the `for` loop is:
//
// for ((i, v) in (1..10 step 2).withIndex()) { /* Loop body */ }
//
// ...the optimized loop for the underlying progression looks something like this:
//
// var inductionVar = 1
// val last = 10
// val step = 2
// if (inductionVar <= last) {
// do {
// val v = inductionVar
// inductionVar += step
// // Loop body
// } while (inductionVar <= last)
// }
//
// ...and the optimized loop with `withIndex()` looks something like this (see "// ADDED" statements):
//
// var inductionVar = 1
// val last = 10
// val step = 2
// var index = 0 // ADDED
// if (inductionVar <= last) {
// do {
// val i = index // ADDED
// checkIndexOverflow(index++) // ADDED
// val v = inductionVar
// inductionVar += step
// // Loop body
// } while (inductionVar <= last)
// }
//
// As another example, in a for-loop over a call to `Iterable<*>.withIndex()` or `Sequence<*>.withIndex()`, e.g.:
//
// for ((i, v) in listOf(2, 3, 5, 7, 11).withIndex()) { /* Loop body */ }
//
// For-loops over an Iterable are normally not optimized, but when getting the underlying iterable for `withIndex()` (and ONLY
// in this case), we use DefaultIterableHandler to match it and IterableLoopHeader to build the underlying loop. The optimized
// loop with `withIndex()` looks something like this:
//
// val iterator = listOf(2, 3, 5, 7, 11).iterator()
// var index = 0
// while (it.hasNext())
// val i = index
// checkIndexOverflow(index++)
// val v = it.next()
// // Loop body
// }
//
// We "wire" the 1st destructured component to index, and the 2nd to the loop variable value from the underlying iterable.
loopVariableComponents[1]?.initializer = irGet(indexVariable)
listOfNotNull(loopVariableComponents[1], incrementIndexStatement) +
nestedLoopHeader.initializeIteration(loopVariableComponents[2], linkedMapOf(), builder, backendContext)
}
// Use the nested loop header to build the loop. More info in comments in initializeIteration().
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
nestedLoopHeader.buildLoop(builder, oldLoop, newBody)
}
internal class IterableLoopHeader(
private val headerInfo: IterableHeaderInfo
) : ForLoopHeader {
override val loopInitStatements = listOf(headerInfo.iteratorVariable)
override val consumesLoopVariableComponents = false
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
builder: DeclarationIrBuilder,
backendContext: CommonBackendContext,
): List<IrStatement> =
with(builder) {
// loopVariable = iteratorVar.next()
val iteratorClass = headerInfo.iteratorVariable.type.getClass()!!
val next =
irCall(iteratorClass.functions.first {
it.name == OperatorNameConventions.NEXT && it.valueParameters.isEmpty()
}.symbol).apply {
dispatchReceiver = irGet(headerInfo.iteratorVariable)
}
// The call could be wrapped in an IMPLICIT_NOTNULL type-cast (see comment in ForLoopsLowering.gatherLoopVariableInfo()).
// Find and replace the call to preserve any type-casts.
loopVariable?.initializer = loopVariable?.initializer?.transform(InitializerCallReplacer(next), null)
// Even if there is no loop variable, we always want to call `next()` for iterables and sequences.
listOf(loopVariable ?: next.coerceToUnitIfNeeded(next.type, context.irBuiltIns, backendContext.typeSystem))
}
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement = with(builder) {
// Loop is lowered into something like:
//
// var iteratorVar = someIterable.iterator()
// while (iteratorVar.hasNext()) {
// val loopVar = iteratorVar.next()
// // Loop body
// }
val iteratorClass = headerInfo.iteratorVariable.type.getClass()!!
val hasNext =
irCall(iteratorClass.functions.first { it.name == OperatorNameConventions.HAS_NEXT && it.valueParameters.isEmpty() }).apply {
dispatchReceiver = irGet(headerInfo.iteratorVariable)
}
val newLoop = IrWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
label = oldLoop.label
condition = hasNext
body = newBody
}
LoopReplacement(newLoop, newLoop)
}
}
/**
* Given the for-loop iterator variable, extract information about the iterable subject
* and create a [ForLoopHeader] from it.
@@ -0,0 +1,67 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.expressions.impl.IrWhileLoopImpl
class IndexedGetLoopHeader(
headerInfo: IndexedGetHeaderInfo,
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : NumericForLoopHeader<IndexedGetHeaderInfo>(headerInfo, builder, context) {
override val loopInitStatements =
listOfNotNull(headerInfo.objectVariable, inductionVariable, lastVariableIfCanCacheLast, stepVariable)
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
builder: DeclarationIrBuilder,
backendContext: CommonBackendContext,
): List<IrStatement> =
with(builder) {
// loopVariable = objectVariable[inductionVariable]
val indexedGetFun = with(headerInfo.expressionHandler) { headerInfo.objectVariable.type.getFunction }
// Making sure that expression type has type of the variable when it exists.
// Return type of get function can be a type parameter (for example Array<T>::get) which is not a subtype of loopVariable type.
val get = irCall(indexedGetFun.symbol, type = loopVariable?.type ?: indexedGetFun.returnType).apply {
dispatchReceiver = irGet(headerInfo.objectVariable)
putValueArgument(0, irGet(inductionVariable))
}
// The call could be wrapped in an IMPLICIT_NOTNULL type-cast (see comment in ForLoopsLowering.gatherLoopVariableInfo()).
// Find and replace the call to preserve any type-casts.
loopVariable?.initializer = loopVariable?.initializer?.transform(InitializerCallReplacer(get), null)
// Even if there is no loop variable, we always want to call `get()` as it may have side effects.
// The un-lowered loop always calls `get()` on each iteration.
listOf(loopVariable ?: get) + incrementInductionVariable(this)
}
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement = with(builder) {
// Loop is lowered into something like:
//
// var inductionVar = 0
// var last = objectVariable.size
// while (inductionVar < last) {
// val loopVar = objectVariable.get(inductionVar)
// inductionVar++
// // 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)
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.expressions.impl.IrWhileLoopImpl
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.util.coerceToUnitIfNeeded
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.util.OperatorNameConventions
internal class IterableLoopHeader(
private val headerInfo: IterableHeaderInfo
) : ForLoopHeader {
override val loopInitStatements = listOf(headerInfo.iteratorVariable)
override val consumesLoopVariableComponents = false
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
builder: DeclarationIrBuilder,
backendContext: CommonBackendContext,
): List<IrStatement> =
with(builder) {
// loopVariable = iteratorVar.next()
val iteratorClass = headerInfo.iteratorVariable.type.getClass()!!
val next =
irCall(iteratorClass.functions.first {
it.name == OperatorNameConventions.NEXT && it.valueParameters.isEmpty()
}.symbol).apply {
dispatchReceiver = irGet(headerInfo.iteratorVariable)
}
// The call could be wrapped in an IMPLICIT_NOTNULL type-cast (see comment in ForLoopsLowering.gatherLoopVariableInfo()).
// Find and replace the call to preserve any type-casts.
loopVariable?.initializer = loopVariable?.initializer?.transform(InitializerCallReplacer(next), null)
// Even if there is no loop variable, we always want to call `next()` for iterables and sequences.
listOf(loopVariable ?: next.coerceToUnitIfNeeded(next.type, context.irBuiltIns, backendContext.typeSystem))
}
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement = with(builder) {
// Loop is lowered into something like:
//
// var iteratorVar = someIterable.iterator()
// while (iteratorVar.hasNext()) {
// val loopVar = iteratorVar.next()
// // Loop body
// }
val iteratorClass = headerInfo.iteratorVariable.type.getClass()!!
val hasNext =
irCall(iteratorClass.functions.first { it.name == OperatorNameConventions.HAS_NEXT && it.valueParameters.isEmpty() }).apply {
dispatchReceiver = irGet(headerInfo.iteratorVariable)
}
val newLoop = IrWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
label = oldLoop.label
condition = hasNext
body = newBody
}
LoopReplacement(newLoop, newLoop)
}
}
@@ -0,0 +1,211 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.types.makeNotNull
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.util.OperatorNameConventions
abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
val headerInfo: T,
builder: DeclarationIrBuilder,
protected val context: CommonBackendContext
) : ForLoopHeader {
override val consumesLoopVariableComponents = false
val inductionVariable: IrVariable
protected val stepVariable: IrVariable?
val stepExpression: IrExpression
protected val lastVariableIfCanCacheLast: IrVariable?
protected val lastExpression: IrExpression
// If this is not `IrExpressionWithCopy`, then it is `<IrGetValue>.getSize()` built in `IndexedGetIterationHandler`.
// It is therefore safe to deep-copy as it does not contain any functions or classes.
get() = field.shallowCopyOrNull() ?: field.deepCopyWithSymbols()
protected val symbols = context.ir.symbols
init {
with(builder) {
with(headerInfo.progressionType) {
// For this loop:
//
// for (i in first()..last() step step())
//
// We need to cast first(), last(). and step() to conform to the progression type so
// that operations on the induction variable within the loop are more efficient.
//
// In the above example, if first() is a Long and last() is an Int, this creates a
// LongProgression so last() should be cast to a Long.
inductionVariable =
scope.createTmpVariable(
headerInfo.first.asElementType(),
nameHint = inductionVariableName,
isMutable = true,
origin = this@NumericForLoopHeader.context.inductionVariableOrigin,
irType = elementClass.defaultType
)
// Due to features of PSI2IR we can obtain nullable arguments here while actually
// they are non-nullable (the frontend takes care about this). So we need to cast
// them to non-nullable.
// TODO: Confirm if casting to non-nullable is still necessary
val last = headerInfo.last.asElementType()
if (headerInfo.canCacheLast) {
val (variable, expression) = createLoopTemporaryVariableIfNecessary(last, nameHint = "last")
lastVariableIfCanCacheLast = variable
lastExpression = expression.shallowCopy()
} else {
lastVariableIfCanCacheLast = null
lastExpression = last
}
val (tmpStepVar, tmpStepExpression) =
createLoopTemporaryVariableIfNecessary(
ensureNotNullable(headerInfo.step.asStepType()),
nameHint = "step",
irType = stepClass.defaultType
)
stepVariable = tmpStepVar
stepExpression = tmpStepExpression
}
}
}
private fun DeclarationIrBuilder.ensureNotNullable(expression: IrExpression) =
if (expression.type is IrSimpleType && expression.type.isNullable()) {
irImplicitCast(expression, expression.type.makeNotNull())
} else {
expression
}
/** Statement used to increment the induction variable. */
protected fun incrementInductionVariable(builder: DeclarationIrBuilder): IrStatement = with(builder) {
with(headerInfo.progressionType) {
// inductionVariable = inductionVariable + step
// NOTE: We cannot use `stepExpression.type` to match the value parameter type because it may be of type `Nothing`.
// This happens in the case of an illegal step where the "step" is actually a `throw IllegalArgumentException(...)`.
val stepType = stepClass.defaultType
val plusFun = elementClass.defaultType.getClass()!!.functions.single {
it.name == OperatorNameConventions.PLUS &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type == stepType
}
irSet(
inductionVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(inductionVariable),
stepExpression.shallowCopy(), IrStatementOrigin.PLUSEQ
), IrStatementOrigin.PLUSEQ
)
}
}
protected fun buildLoopCondition(builder: DeclarationIrBuilder): IrExpression {
with(builder) {
with(headerInfo.progressionType) {
val builtIns = context.irBuiltIns
// Bounds are signed for unsigned progressions but bound comparisons should be done as unsigned, to ensure that the
// correct comparison function is used (`UInt/ULongCompare`). Also, `compareTo` must be used for UInt/ULong;
// they don't have intrinsic comparison operators.
val intCompFun = if (headerInfo.isLastInclusive) {
builtIns.lessOrEqualFunByOperandType.getValue(builtIns.intClass)
} else {
builtIns.lessFunByOperandType.getValue(builtIns.intClass)
}
val unsignedCompareToFun = if (this is UnsignedProgressionType) {
unsignedType.getClass()!!.functions.single {
it.name == OperatorNameConventions.COMPARE_TO &&
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null &&
it.valueParameters.size == 1 && it.valueParameters[0].type == unsignedType
}
} else null
val elementCompFun =
if (headerInfo.isLastInclusive) {
builtIns.lessOrEqualFunByOperandType[elementClass.symbol]
} else {
builtIns.lessFunByOperandType[elementClass.symbol]
}
fun conditionForDecreasing(): IrExpression =
// last <= inductionVar (use `<` if last is exclusive)
if (this is UnsignedProgressionType) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(unsignedCompareToFun!!).apply {
dispatchReceiver = lastExpression.asUnsigned()
putValueArgument(0, irGet(inductionVariable).asUnsigned())
})
putValueArgument(1, irInt(0))
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, lastExpression)
putValueArgument(1, irGet(inductionVariable))
}
}
fun conditionForIncreasing(): IrExpression =
// inductionVar <= last (use `<` if last is exclusive)
if (this is UnsignedProgressionType) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(unsignedCompareToFun!!).apply {
dispatchReceiver = irGet(inductionVariable).asUnsigned()
putValueArgument(0, lastExpression.asUnsigned())
})
putValueArgument(1, irInt(0))
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, lastExpression)
}
}
// The default condition depends on the direction.
return when (headerInfo.direction) {
ProgressionDirection.DECREASING -> conditionForDecreasing()
ProgressionDirection.INCREASING -> conditionForIncreasing()
ProgressionDirection.UNKNOWN -> {
// If the direction is unknown, we check depending on the "step" value:
// // (use `<` if last is exclusive)
// (step > 0 && inductionVar <= last) || (step < 0 || last <= inductionVar)
context.oror(
context.andand(
irCall(builtIns.greaterFunByOperandType.getValue(stepClass.symbol)).apply {
putValueArgument(0, stepExpression.shallowCopy())
putValueArgument(1, zeroStepExpression())
},
conditionForIncreasing()
),
context.andand(
irCall(builtIns.lessFunByOperandType.getValue(stepClass.symbol)).apply {
putValueArgument(0, stepExpression.shallowCopy())
putValueArgument(1, zeroStepExpression())
},
conditionForDecreasing()
)
)
}
}
}
}
}
}
@@ -0,0 +1,294 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irIfThen
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.createTmpVariable
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irNotEquals
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.findDeclaration
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.util.OperatorNameConventions
class ProgressionLoopHeader(
headerInfo: ProgressionHeaderInfo,
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : NumericForLoopHeader<ProgressionHeaderInfo>(headerInfo, builder, context) {
private val preferJavaLikeCounterLoop = context.preferJavaLikeCounterLoop
// For this loop:
//
// for (i in first()..last() step step())
//
// ...the functions may have side effects, so we need to call them in the following order: first() (inductionVariable), last(), step().
// Additional variables come first as they may be needed to the subsequent variables.
//
// In the case of a reversed range, the `inductionVariable` and `last` variables are swapped, therefore the declaration order must be
// swapped to preserve the correct evaluation order.
override val loopInitStatements = headerInfo.additionalStatements + (
if (headerInfo.isReversed)
listOfNotNull(lastVariableIfCanCacheLast, inductionVariable)
else
listOfNotNull(inductionVariable, lastVariableIfCanCacheLast)
) +
listOfNotNull(stepVariable)
private var loopVariable: IrVariable? = null
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
builder: DeclarationIrBuilder,
backendContext: CommonBackendContext,
): List<IrStatement> =
with(builder) {
// loopVariable is used in the loop condition if it can overflow. If no loopVariable was provided, create one.
this@ProgressionLoopHeader.loopVariable = if (headerInfo.canOverflow && loopVariable == null) {
scope.createTmpVariable(
irGet(inductionVariable),
nameHint = "loopVariable",
isMutable = true
)
} else {
loopVariable?.initializer = irGet(inductionVariable).let {
headerInfo.progressionType.run {
if (this is UnsignedProgressionType) {
// The induction variable is signed for unsigned progressions but the loop variable should be unsigned.
it.asUnsigned()
} else it
}
}
loopVariable
}
// loopVariable = inductionVariable
// inductionVariable = inductionVariable + step
listOfNotNull(this@ProgressionLoopHeader.loopVariable, incrementInductionVariable(this))
}
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
with(builder) {
if (headerInfo.canOverflow ||
preferJavaLikeCounterLoop && headerInfo.progressionType is UnsignedProgressionType && headerInfo.isLastInclusive
) {
// If the induction variable CAN overflow, we cannot use it in the loop condition.
// Loop is lowered into something like:
//
// if (inductionVar <= last) {
// // Loop is not empty
// do {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// } while (loopVar != last)
// }
//
// This loop form is also preferable for loops over unsigned progressions on JVM,
// because HotSpot doesn't recognize unsigned integer comparison as a counter loop condition.
// Unsigned integer equality is fine, though.
// See KT-49444 for performance comparison example.
val newLoopOrigin = if (preferJavaLikeCounterLoop)
this@ProgressionLoopHeader.context.doWhileCounterLoopOrigin
else
oldLoop.origin
val newLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, newLoopOrigin).apply {
val loopVariableExpression = irGet(loopVariable!!).let {
headerInfo.progressionType.run {
if (this is UnsignedProgressionType) {
// The loop variable is signed but bounds are signed for unsigned progressions.
it.asSigned()
} else it
}
}
label = oldLoop.label
condition = irNotEquals(loopVariableExpression, lastExpression)
body = newBody
}
if (preferJavaLikeCounterLoop) {
moveInductionVariableUpdateToLoopCondition(newLoop)
}
val loopCondition = buildLoopCondition(this@with)
LoopReplacement(newLoop, irIfThen(loopCondition, newLoop))
} else if (preferJavaLikeCounterLoop && !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 do-while loop:
// do {
// if ( !( inductionVariable < last ) ) break
// val loopVariable = inductionVariable
// <body>
// } while ( { inductionVariable += step; true } )
// This loop form is equivalent to the Java counter loop shown above.
val newLoopCondition = buildLoopCondition(this@with)
buildJavaLikeDoWhileCounterLoop(oldLoop, newLoopCondition, newBody)
} else {
// Use an if-guarded do-while loop (note the difference in loop condition):
//
// if (inductionVar <= last) {
// do {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// } while (inductionVar <= last)
// }
//
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))
}
}
private val booleanNot =
context.irBuiltIns.booleanClass.owner.findDeclaration<IrSimpleFunction> {
it.name == OperatorNameConventions.NOT
} ?: error("No '${OperatorNameConventions.NOT}' in ${context.irBuiltIns.booleanClass.owner.render()}")
private fun moveInductionVariableUpdateToLoopCondition(doWhileLoop: IrDoWhileLoop) {
// On JVM, it's important that induction variable update happens in the end of the loop
// (otherwise HotSpot will not treat it as a counter loop).
// Moving induction variable update to loop condition (instead of just placing it in the end of loop body)
// also allows reusing loop variable as induction variable later.
//
// Transform a loop in the form:
// do {
// { <next> }
// <body>
// } while (<condition>)
// to
// do {
// { <next'> }
// <body>
// } while ( { if (!<condition>) break; <updateInductionVar>; true } )
val doWhileBody = doWhileLoop.body as? IrContainerExpression ?: return
if (doWhileBody.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE) return
val doWhileLoopNext = doWhileBody.statements[0] as? IrContainerExpression ?: return
if (doWhileLoopNext.origin != IrStatementOrigin.FOR_LOOP_NEXT) return
val updateInductionVarIndex = doWhileLoopNext.statements
.indexOfFirst { it is IrSetValue && it.symbol.owner.isInductionVariable(context) }
if (updateInductionVarIndex < 0) return
val updateInductionVar = doWhileLoopNext.statements[updateInductionVarIndex]
doWhileLoopNext.statements.removeAt(updateInductionVarIndex)
val loopCondition = doWhileLoop.condition
val loopConditionStartOffset = loopCondition.startOffset
val loopConditionEndOffset = loopCondition.endOffset
doWhileLoop.condition = IrCompositeImpl(
loopConditionStartOffset, loopConditionEndOffset, loopCondition.type,
origin = null,
statements = listOf(
createNegatedConditionCheck(doWhileLoop.condition, doWhileLoop),
updateInductionVar,
IrConstImpl.boolean(loopConditionStartOffset, loopConditionEndOffset, context.irBuiltIns.booleanType, true)
)
)
}
private fun buildJavaLikeDoWhileCounterLoop(
oldLoop: IrLoop,
newLoopCondition: IrExpression,
newBody: IrExpression?
): LoopReplacement {
// Transform loop:
// while (<newLoopCondition>) {
// { // FOR_LOOP_NEXT
// <initializeLoopIteration>
// <inductionVariableUpdate>
// }
// <originalLoopBody>
// }
// to:
// do {
// { // FOR_LOOP_NEXT
// if (!(<newLoopCondition>)) break
// <initializeLoopIteration>
// }
// <originalLoopBody>
// } while (
// {
// <inductionVariableUpdate>
// true
// }
// )
val bodyBlock = newBody as? IrContainerExpression
?: throw AssertionError("newBody: ${newBody?.dump()}")
val forLoopNextBlock = bodyBlock.statements[0] as? IrContainerExpression
?: throw AssertionError("bodyBlock[0]: ${bodyBlock.statements[0].dump()}")
if (forLoopNextBlock.origin != IrStatementOrigin.FOR_LOOP_NEXT)
throw AssertionError("FOR_LOOP_NEXT expected: ${forLoopNextBlock.dump()}")
val inductionVariableUpdate = forLoopNextBlock.statements.last() as? IrSetValue
?: throw AssertionError("forLoopNextBlock.last: ${forLoopNextBlock.statements.last().dump()}")
val doWhileLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, context.doWhileCounterLoopOrigin)
doWhileLoop.label = oldLoop.label
bodyBlock.statements[0] = IrCompositeImpl(
forLoopNextBlock.startOffset, forLoopNextBlock.endOffset,
forLoopNextBlock.type,
forLoopNextBlock.origin,
).apply {
statements.add(createNegatedConditionCheck(newLoopCondition, doWhileLoop))
if (forLoopNextBlock.statements.size >= 2)
statements.addAll(forLoopNextBlock.statements.subList(0, forLoopNextBlock.statements.lastIndex))
}
doWhileLoop.body = bodyBlock
val stepStartOffset = inductionVariableUpdate.startOffset
val stepEndOffset = inductionVariableUpdate.endOffset
val doWhileCondition =
IrCompositeImpl(
stepStartOffset, stepEndOffset, context.irBuiltIns.booleanType, null,
listOf(
inductionVariableUpdate,
IrConstImpl.boolean(stepStartOffset, stepEndOffset, context.irBuiltIns.booleanType, true)
)
)
doWhileLoop.condition = doWhileCondition
return LoopReplacement(doWhileLoop, doWhileLoop)
}
private fun createNegatedConditionCheck(newLoopCondition: IrExpression, doWhileLoop: IrDoWhileLoop): IrWhenImpl {
val conditionStartOffset = newLoopCondition.startOffset
val conditionEndOffset = newLoopCondition.endOffset
val negatedCondition =
IrCallImpl.fromSymbolOwner(conditionStartOffset, conditionEndOffset, booleanNot.symbol).apply {
dispatchReceiver = newLoopCondition
}
return IrWhenImpl(
conditionStartOffset, conditionEndOffset, context.irBuiltIns.unitType, null,
listOf(
IrBranchImpl(
negatedCondition,
IrBreakImpl(conditionStartOffset, conditionEndOffset, context.irBuiltIns.nothingType, doWhileLoop)
)
)
)
}
}
@@ -0,0 +1,165 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.isInt
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.util.OperatorNameConventions
class WithIndexLoopHeader(
headerInfo: WithIndexHeaderInfo,
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : ForLoopHeader {
private val nestedLoopHeader: ForLoopHeader
private val indexVariable: IrVariable
private val ownsIndexVariable: Boolean
private val incrementIndexStatement: IrStatement?
init {
with(builder) {
// To build the optimized/lowered `for` loop over a `withIndex()` call, we first need the header for the underlying iterable,
// so that we know how to build the loop for that iterable. More info in comments in initializeIteration().
nestedLoopHeader = when (val nestedInfo = headerInfo.nestedInfo) {
is IndexedGetHeaderInfo -> IndexedGetLoopHeader(nestedInfo, this@with, context)
is ProgressionHeaderInfo -> ProgressionLoopHeader(nestedInfo, this@with, context)
is IterableHeaderInfo -> IterableLoopHeader(nestedInfo)
is WithIndexHeaderInfo -> throw IllegalStateException("Nested WithIndexHeaderInfo not allowed for WithIndexLoopHeader")
is FloatingPointRangeHeaderInfo, is ComparableRangeInfo -> error("Unexpected ${nestedInfo::class.simpleName} for loops")
}
// Do not build own indexVariable if the nested loop header has an inductionVariable == 0 and step == 1.
// This is the case when the underlying iterable is an array, CharSequence, or a progression from 0 with step 1.
// We can use the induction variable from the underlying iterable as the index variable, since it progresses in the same way.
if (nestedLoopHeader is NumericForLoopHeader<*> &&
nestedLoopHeader.inductionVariable.type.isInt() &&
nestedLoopHeader.inductionVariable.initializer?.constLongValue == 0L &&
nestedLoopHeader.stepExpression.constLongValue == 1L
) {
indexVariable = nestedLoopHeader.inductionVariable
ownsIndexVariable = false
incrementIndexStatement = null
} else {
indexVariable = scope.createTmpVariable(
irInt(0),
nameHint = "index",
isMutable = true
)
ownsIndexVariable = true
// `index++` during iteration initialization
// TODO: KT-34665: Check for overflow for Iterable and Sequence (call to checkIndexOverflow()).
val plusFun = indexVariable.type.getClass()!!.functions.first {
it.name == OperatorNameConventions.PLUS &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type.isInt()
}
incrementIndexStatement =
irSet(
indexVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(indexVariable),
irInt(1)
)
)
}
}
}
// Add the index variable (if owned) to the statements from the nested loop header.
override val loopInitStatements = nestedLoopHeader.loopInitStatements.let { if (ownsIndexVariable) it + indexVariable else it }
override val consumesLoopVariableComponents = true
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
builder: DeclarationIrBuilder,
backendContext: CommonBackendContext,
): List<IrStatement> =
with(builder) {
// The `withIndex()` extension function returns a lazy Iterable that wraps each element of the underlying iterable (e.g., array,
// progression, Iterable, Sequence, CharSequence) into an IndexedValue containing the index of that element and the element
// itself. The iterator for this lazy Iterable looks like this:
//
// internal class IndexingIterator<out T>(private val iterator: Iterator<T>) : Iterator<IndexedValue<T>> {
// private var index = 0
// override fun hasNext() = iterator.hasNext()
// override fun next() = IndexedValue(checkIndexOverflow(index++), iterator.next())
// }
//
// IndexedValue looks like this:
//
// data class IndexedValue<out T>(val index: Int, val value: T)
//
// For example, if the `for` loop is:
//
// for ((i, v) in (1..10 step 2).withIndex()) { /* Loop body */ }
//
// ...the optimized loop for the underlying progression looks something like this:
//
// var inductionVar = 1
// val last = 10
// val step = 2
// if (inductionVar <= last) {
// do {
// val v = inductionVar
// inductionVar += step
// // Loop body
// } while (inductionVar <= last)
// }
//
// ...and the optimized loop with `withIndex()` looks something like this (see "// ADDED" statements):
//
// var inductionVar = 1
// val last = 10
// val step = 2
// var index = 0 // ADDED
// if (inductionVar <= last) {
// do {
// val i = index // ADDED
// checkIndexOverflow(index++) // ADDED
// val v = inductionVar
// inductionVar += step
// // Loop body
// } while (inductionVar <= last)
// }
//
// As another example, in a for-loop over a call to `Iterable<*>.withIndex()` or `Sequence<*>.withIndex()`, e.g.:
//
// for ((i, v) in listOf(2, 3, 5, 7, 11).withIndex()) { /* Loop body */ }
//
// For-loops over an Iterable are normally not optimized, but when getting the underlying iterable for `withIndex()` (and ONLY
// in this case), we use DefaultIterableHandler to match it and IterableLoopHeader to build the underlying loop. The optimized
// loop with `withIndex()` looks something like this:
//
// val iterator = listOf(2, 3, 5, 7, 11).iterator()
// var index = 0
// while (it.hasNext())
// val i = index
// checkIndexOverflow(index++)
// val v = it.next()
// // Loop body
// }
//
// We "wire" the 1st destructured component to index, and the 2nd to the loop variable value from the underlying iterable.
loopVariableComponents[1]?.initializer = irGet(indexVariable)
listOfNotNull(loopVariableComponents[1], incrementIndexStatement) +
nestedLoopHeader.initializeIteration(loopVariableComponents[2], linkedMapOf(), builder, backendContext)
}
// Use the nested loop header to build the loop. More info in comments in initializeIteration().
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
nestedLoopHeader.buildLoop(builder, oldLoop, newBody)
}