From 256aaa3388528a9484150f5c4dea680732549756 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Mon, 6 Dec 2021 13:38:34 +0300 Subject: [PATCH] Minor: extract *LoopHeader classes to separate files --- .../common/lower/loops/HeaderProcessor.kt | 727 +----------------- .../lower/loops/IndexedGetLoopHeader.kt | 67 ++ .../common/lower/loops/IterableLoopHeader.kt | 71 ++ .../lower/loops/NumericForLoopHeader.kt | 211 +++++ .../lower/loops/ProgressionLoopHeader.kt | 294 +++++++ .../common/lower/loops/WithIndexLoopHeader.kt | 165 ++++ 6 files changed, 814 insertions(+), 721 deletions(-) create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/IndexedGetLoopHeader.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/IterableLoopHeader.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/NumericForLoopHeader.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionLoopHeader.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/WithIndexLoopHeader.kt diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt index 5a68ea0ed22..5e34b2a855e 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt @@ -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( - 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 `.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(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, - builder: DeclarationIrBuilder, - backendContext: CommonBackendContext, - ): List = - 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 - // - // } 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 { - 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 { - // { } - // - // } while () - // to - // do { - // { } - // - // } while ( { if (!) break; ; 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 () { - // { // FOR_LOOP_NEXT - // - // - // } - // - // } - // to: - // do { - // { // FOR_LOOP_NEXT - // if (!()) break - // - // } - // - // } while ( - // { - // - // 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(headerInfo, builder, context) { - - override val loopInitStatements = - listOfNotNull(headerInfo.objectVariable, inductionVariable, lastVariableIfCanCacheLast, stepVariable) - - override fun initializeIteration( - loopVariable: IrVariable?, - loopVariableComponents: Map, - builder: DeclarationIrBuilder, - backendContext: CommonBackendContext, - ): List = - 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::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, - builder: DeclarationIrBuilder, - backendContext: CommonBackendContext, - ): List = - 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(private val iterator: Iterator) : Iterator> { - // private var index = 0 - // override fun hasNext() = iterator.hasNext() - // override fun next() = IndexedValue(checkIndexOverflow(index++), iterator.next()) - // } - // - // IndexedValue looks like this: - // - // data class IndexedValue(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, - builder: DeclarationIrBuilder, - backendContext: CommonBackendContext, - ): List = - 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. diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/IndexedGetLoopHeader.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/IndexedGetLoopHeader.kt new file mode 100644 index 00000000000..6f7f600ce59 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/IndexedGetLoopHeader.kt @@ -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(headerInfo, builder, context) { + + override val loopInitStatements = + listOfNotNull(headerInfo.objectVariable, inductionVariable, lastVariableIfCanCacheLast, stepVariable) + + override fun initializeIteration( + loopVariable: IrVariable?, + loopVariableComponents: Map, + builder: DeclarationIrBuilder, + backendContext: CommonBackendContext, + ): List = + 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::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) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/IterableLoopHeader.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/IterableLoopHeader.kt new file mode 100644 index 00000000000..49784ebe478 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/IterableLoopHeader.kt @@ -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, + builder: DeclarationIrBuilder, + backendContext: CommonBackendContext, + ): List = + 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) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/NumericForLoopHeader.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/NumericForLoopHeader.kt new file mode 100644 index 00000000000..dbb78721551 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/NumericForLoopHeader.kt @@ -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( + 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 `.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() + ) + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionLoopHeader.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionLoopHeader.kt new file mode 100644 index 00000000000..edd054317f5 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionLoopHeader.kt @@ -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(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, + builder: DeclarationIrBuilder, + backendContext: CommonBackendContext, + ): List = + 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 + // + // } 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 { + 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 { + // { } + // + // } while () + // to + // do { + // { } + // + // } while ( { if (!) break; ; 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 () { + // { // FOR_LOOP_NEXT + // + // + // } + // + // } + // to: + // do { + // { // FOR_LOOP_NEXT + // if (!()) break + // + // } + // + // } while ( + // { + // + // 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) + ) + ) + ) + } + +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/WithIndexLoopHeader.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/WithIndexLoopHeader.kt new file mode 100644 index 00000000000..2f7d0ad2ef7 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/WithIndexLoopHeader.kt @@ -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, + builder: DeclarationIrBuilder, + backendContext: CommonBackendContext, + ): List = + 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(private val iterator: Iterator) : Iterator> { + // private var index = 0 + // override fun hasNext() = iterator.hasNext() + // override fun next() = IndexedValue(checkIndexOverflow(index++), iterator.next()) + // } + // + // IndexedValue looks like this: + // + // data class IndexedValue(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) +} \ No newline at end of file