Minor: extract *LoopHeader classes to separate files
This commit is contained in:
+6
-721
@@ -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.CommonBackendContext
|
||||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
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.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
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.declarations.IrVariable
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
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.symbols.IrSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the loop and expression to replace the old loop.
|
* Contains the loop and expression to replace the old loop.
|
||||||
@@ -63,470 +60,7 @@ fun IrStatement.isInductionVariable(context: CommonBackendContext) =
|
|||||||
origin == context.inductionVariableOrigin &&
|
origin == context.inductionVariableOrigin &&
|
||||||
name.asString() == inductionVariableName
|
name.asString() == inductionVariableName
|
||||||
|
|
||||||
abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
|
internal class InitializerCallReplacer(private val replacementCall: IrCall) : IrElementTransformerVoid() {
|
||||||
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() {
|
|
||||||
var initializerCall: IrCall? = null
|
var initializerCall: IrCall? = null
|
||||||
|
|
||||||
override fun visitCall(expression: IrCall): IrCall {
|
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
|
* Given the for-loop iterator variable, extract information about the iterable subject
|
||||||
* and create a [ForLoopHeader] from it.
|
* and create a [ForLoopHeader] from it.
|
||||||
|
|||||||
+67
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+211
@@ -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()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+294
@@ -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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+165
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user