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.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.
|
||||
|
||||
+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