From 291d62f6536c2b7b0ce4c07cc6327f2fdf4ad2e4 Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Thu, 9 Jul 2020 17:06:51 -0700 Subject: [PATCH] ForLoopsLowering: Move handlers to sub-package and separate files. --- .../backend/common/lower/loops/HeaderInfo.kt | 25 +- .../common/lower/loops/ProgressionHandlers.kt | 767 ------------------ .../loops/handlers/DefaultIterableHandler.kt | 38 + .../handlers/DefaultProgressionHandler.kt | 53 ++ .../lower/loops/handlers/DownToHandler.kt | 39 + .../handlers/IndexedGetIterationHandlers.kt | 129 +++ .../lower/loops/handlers/IndicesHandlers.kt | 81 ++ .../lower/loops/handlers/RangeToHandler.kt | 42 + .../lower/loops/handlers/ReversedHandler.kt | 42 + .../lower/loops/handlers/StepHandler.kt | 298 +++++++ .../lower/loops/handlers/UntilHandler.kt | 177 ++++ .../lower/loops/handlers/WithIndexHandler.kt | 58 ++ 12 files changed, 980 insertions(+), 769 deletions(-) delete mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DefaultIterableHandler.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DefaultProgressionHandler.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DownToHandler.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/IndexedGetIterationHandlers.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/IndicesHandlers.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/RangeToHandler.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/ReversedHandler.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/StepHandler.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/UntilHandler.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/WithIndexHandler.kt diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt index f0923fa888d..d78c1906f4d 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt @@ -9,6 +9,21 @@ package org.jetbrains.kotlin.backend.common.lower.loops import org.jetbrains.kotlin.backend.common.CommonBackendContext import org.jetbrains.kotlin.backend.common.ir.Symbols +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.ArrayIndicesHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.ArrayIterationHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.CharSequenceIndicesHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.CharSequenceIterationHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.CollectionIndicesHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.DefaultIterableHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.DefaultProgressionHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.DownToHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.IndexedGetIterationHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.RangeToHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.ReversedHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.StepHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.StringIterationHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.UntilHandler +import org.jetbrains.kotlin.backend.common.lower.loops.handlers.WithIndexHandler import org.jetbrains.kotlin.backend.common.lower.matchers.IrCallMatcher import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.IrVariable @@ -188,7 +203,7 @@ internal class IndexedGetHeaderInfo( */ internal class WithIndexHeaderInfo(val nestedInfo: HeaderInfo) : HeaderInfo() { // We cannot easily reverse `withIndex()` so we do not attempt to handle it. We would have to start from the last value of the index, - // easily calculable (or even impossible) in most cases. + // which is not easily calculable (or even impossible) in most cases. override fun asReversed(): HeaderInfo? = null } @@ -295,7 +310,13 @@ internal class DefaultHeaderInfoBuilder(context: CommonBackendContext, scopeOwne HeaderInfoBuilder(context, scopeOwnerSymbol) { override val callHandlers = listOf( ReversedHandler(context, this), - WithIndexHandler(context, NestedHeaderInfoBuilderForWithIndex(context, scopeOwnerSymbol)) + WithIndexHandler( + context, + NestedHeaderInfoBuilderForWithIndex( + context, + scopeOwnerSymbol + ) + ) ) // NOTE: StringIterationHandler MUST come before CharSequenceIterationHandler. diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt deleted file mode 100644 index 1cc05078261..00000000000 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt +++ /dev/null @@ -1,767 +0,0 @@ -/* - * Copyright 2010-2019 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.createIrBuilder -import org.jetbrains.kotlin.backend.common.lower.matchers.Quantifier -import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher -import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher -import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension -import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI -import org.jetbrains.kotlin.ir.builders.* -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction -import org.jetbrains.kotlin.ir.declarations.IrVariable -import org.jetbrains.kotlin.ir.expressions.IrCall -import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.symbols.IrSymbol -import org.jetbrains.kotlin.ir.types.* -import org.jetbrains.kotlin.ir.util.* -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.util.OperatorNameConventions -import kotlin.math.absoluteValue - -/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */ -internal class RangeToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection) : - ProgressionHandler { - - override val matcher = SimpleCalleeMatcher { - dispatchReceiver { it != null && it.type in progressionElementTypes } - fqName { it.pathSegments().last() == OperatorNameConventions.RANGE_TO } - parameterCount { it == 1 } - parameter(0) { it.type in progressionElementTypes } - } - - override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol) = - with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { - ProgressionHeaderInfo( - data, - first = expression.dispatchReceiver!!, - last = expression.getValueArgument(0)!!, - step = irInt(1), - direction = ProgressionDirection.INCREASING - ) - } -} - -/** Builds a [HeaderInfo] for progressions built using the `downTo` extension function. */ -internal class DownToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection) : - ProgressionHandler { - - override val matcher = SimpleCalleeMatcher { - singleArgumentExtension(FqName("kotlin.ranges.downTo"), progressionElementTypes) - parameterCount { it == 1 } - parameter(0) { it.type in progressionElementTypes } - } - - override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = - with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { - ProgressionHeaderInfo( - data, - first = expression.extensionReceiver!!, - last = expression.getValueArgument(0)!!, - step = irInt(-1), - direction = ProgressionDirection.DECREASING - ) - } -} - -/** Builds a [HeaderInfo] for progressions built using the `until` extension function. */ -internal class UntilHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection) : - ProgressionHandler { - - private val symbols = context.ir.symbols - private val uByteType = symbols.uByte?.defaultType - private val uShortType = symbols.uShort?.defaultType - private val uIntType = symbols.uInt?.defaultType - private val uLongType = symbols.uLong?.defaultType - - override val matcher = SimpleCalleeMatcher { - singleArgumentExtension(FqName("kotlin.ranges.until"), progressionElementTypes) - parameterCount { it == 1 } - parameter(0) { it.type in progressionElementTypes } - } - - override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = - with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { - with(data) { - // `A until B` is essentially the same as `A .. (B-1)`. However, B could be MIN_VALUE and hence `(B-1)` could underflow. - // If B is MIN_VALUE, then `A until B` is an empty range. We handle this special case be adding an additional "not empty" - // condition in the lowered for-loop. Therefore the following for-loop: - // - // for (i in A until B) { // Loop body } - // - // is lowered into: - // - // var inductionVar = A - // val last = B - 1 - // if (inductionVar <= last && B != MIN_VALUE) { - // // Loop is not empty - // do { - // val i = inductionVar - // inductionVar++ - // // Loop body - // } while (inductionVar <= last) - // } - // - // However, `B` may be an expression with side-effects that should only be evaluated once, and `A` may also have - // side-effects. They are evaluated once and in the correct order (`A` then `B`), the final lowered form is: - // - // // Additional variables - // val untilReceiverValue = A - // val untilArg = B - // // Standard form of loop over progression - // var inductionVar = untilReceiverValue - // val last = untilArg - 1 - // if (inductionVar <= last && untilArg != MIN_VALUE) { - // // Loop is not empty - // do { - // val i = inductionVar - // inductionVar++ - // // Loop body - // } while (inductionVar <= last) - // } - val receiverValue = expression.extensionReceiver!! - val untilArg = expression.getValueArgument(0)!! - - // Ensure that the argument conforms to the progression type before we decrement. - val untilArgCasted = untilArg.asElementType() - - // To reduce local variable usage, we create and use temporary variables only if necessary. - var receiverValueVar: IrVariable? = null - var untilArgVar: IrVariable? = null - var additionalVariables = emptyList() - if (untilArg.canHaveSideEffects) { - if (receiverValue.canHaveSideEffects) { - receiverValueVar = scope.createTmpVariable(receiverValue, nameHint = "untilReceiverValue") - } - untilArgVar = scope.createTmpVariable(untilArgCasted, nameHint = "untilArg") - additionalVariables = listOfNotNull(receiverValueVar, untilArgVar) - } - - val first = if (receiverValueVar == null) receiverValue else irGet(receiverValueVar) - val untilArgExpression = if (untilArgVar == null) untilArgCasted else irGet(untilArgVar) - val last = untilArgExpression.decrement() - - // Type of MIN_VALUE constant is signed even for unsigned progressions since the bounds are signed. - val additionalNotEmptyCondition = untilArg.constLongValue.let { - when { - it == null && isAdditionalNotEmptyConditionNeeded(receiverValue.type, untilArg.type) -> - // Condition is needed and untilArg is non-const. - // Build the additional "not empty" condition: `untilArg != MIN_VALUE`. - // Make sure to copy untilArgExpression as it is also used in `last`. - irNotEquals(untilArgExpression.deepCopyWithSymbols(), minValueExpression()) - it == data.minValueAsLong -> - // Hardcode "false" as additional condition so that the progression is considered empty. - // The entire lowered loop becomes a candidate for dead code elimination, depending on backend. - irFalse() - else -> - // We know that untilArg != MIN_VALUE, so the additional condition is not necessary. - null - } - } - - ProgressionHeaderInfo( - data, - first = first, - last = last, - step = irInt(1), - canOverflow = false, - additionalVariables = additionalVariables, - additionalNotEmptyCondition = additionalNotEmptyCondition, - direction = ProgressionDirection.INCREASING - ) - } - } - - private fun isAdditionalNotEmptyConditionNeeded(receiverType: IrType, argType: IrType): Boolean { - // Here are the available `until` extension functions: - // - // infix fun Char.until(to: Char): CharRange - // infix fun Byte.until(to: Byte): IntRange - // infix fun Byte.until(to: Short): IntRange - // infix fun Byte.until(to: Int): IntRange - // infix fun Byte.until(to: Long): LongRange - // infix fun Short.until(to: Byte): IntRange - // infix fun Short.until(to: Short): IntRange - // infix fun Short.until(to: Int): IntRange - // infix fun Short.until(to: Long): LongRange - // infix fun Int.until(to: Byte): IntRange - // infix fun Int.until(to: Short): IntRange - // infix fun Int.until(to: Int): IntRange - // infix fun Int.until(to: Long): LongRange - // infix fun Long.until(to: Byte): LongRange - // infix fun Long.until(to: Short): LongRange - // infix fun Long.until(to: Int): LongRange - // infix fun Long.until(to: Long): LongRange - // infix fun UByte.until(to: UByte): UIntRange - // infix fun UShort.until(to: UShort): UIntRange - // infix fun UInt.until(to: UInt): UIntRange - // infix fun ULong.until(to: ULong): ULongRange - // - // The combinations where the range element type is strictly larger than the argument type do NOT need the additional condition. - // In such combinations, there is no possibility of underflow when the argument (casted to the range element type) is decremented. - // For unexpected combinations that currently don't exist (e.g., Int until Char), we assume the check is needed to be safe. - return with(context.irBuiltIns) { - when (receiverType) { - charType -> true - byteType, shortType, intType -> when (argType) { - byteType, shortType -> false - else -> true - } - longType -> when (argType) { - byteType, shortType, intType -> false - else -> true - } - uByteType -> false - uShortType -> false - uIntType -> true - uLongType -> true - else -> true // Default in case a new `until` overload is added to stdlib and this function was not updated. - } - } - } -} - -/** Builds a [HeaderInfo] for progressions built using the `step` extension function. */ -internal class StepHandler( - private val context: CommonBackendContext, - private val visitor: HeaderInfoBuilder -) : ProgressionHandler { - - private val symbols = context.ir.symbols - - override val matcher = SimpleCalleeMatcher { - singleArgumentExtension(FqName("kotlin.ranges.step"), symbols.progressionClasses.map { it.defaultType }) - parameter(0) { it.type.isInt() || it.type.isLong() } - } - - override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = - with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { - // Retrieve the HeaderInfo from the underlying progression (if any). - val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) as? ProgressionHeaderInfo - ?: return null - - val stepArg = expression.getValueArgument(0)!! - // We can return the nested info if its step is constant and its absolute value is the same as the step argument. Examples: - // - // 1..10 step 1 // Nested step is 1, argument is 1. Equivalent to `1..10`. - // 10 downTo 1 step 1 // Nested step is -1, argument is 1. Equivalent to `10 downTo 1`. - // 10 downTo 1 step 2 step 2 // Nested step is -2, argument is 2. Equivalent to `10 downTo 1 step 2`. - if (stepArg.constLongValue != null && nestedInfo.step.constLongValue?.absoluteValue == stepArg.constLongValue) { - return nestedInfo - } - - // To reduce local variable usage, we create and use temporary variables only if necessary. - val (stepArgVar, stepArgExpression) = createTemporaryVariableIfNecessary(stepArg, "stepArg") - - // The `step` standard library function only accepts positive values, and performs the following check: - // - // if (step > 0) step else throw IllegalArgumentException("Step must be positive, was: $step.") - // - // We insert this check in the lowered form only if necessary. - val stepType = data.stepClass.defaultType - val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType.getValue(data.stepClass.symbol) - val zeroStep = data.run { zeroStepExpression() } - val throwIllegalStepExceptionCall = { - irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply { - val exceptionMessage = irConcat() - exceptionMessage.addArgument(irString("Step must be positive, was: ")) - exceptionMessage.addArgument(stepArgExpression.deepCopyWithSymbols()) - exceptionMessage.addArgument(irString(".")) - putValueArgument(0, exceptionMessage) - } - } - val stepArgValueAsLong = stepArgExpression.constLongValue - val checkedStepExpression = when { - stepArgValueAsLong == null -> { - // Step argument is not a constant. - val stepPositiveCheck = irCall(stepGreaterFun).apply { - putValueArgument(0, stepArgExpression.deepCopyWithSymbols()) - putValueArgument(1, zeroStep.deepCopyWithSymbols()) - } - irIfThenElse( - stepType, - stepPositiveCheck, - stepArgExpression.deepCopyWithSymbols(), - throwIllegalStepExceptionCall() - ) - } - stepArgValueAsLong > 0L -> - // Step argument is a positive constant and is valid. - stepArgExpression.deepCopyWithSymbols() - else -> - // Step argument is a non-positive constant and is invalid, directly throw the exception. - throwIllegalStepExceptionCall() - } - - // While the `step` function accepts positive values, the "step" value in the progression depends on the direction of the - // nested progression. For example, in `10 downTo 1 step 2`, the nested progression is `10 downTo 1` which is decreasing, - // therefore the step used should be negated (-2). - // - // If we don't know the direction of the nested progression (e.g., `someProgression() step 2`) then we have to check its value - // first to determine whether to negate. - var nestedStepVar: IrVariable? = null - var checkedStepVar: IrVariable? = null - val checkedAndMaybeNegatedStep = when (nestedInfo.direction) { - ProgressionDirection.INCREASING -> checkedStepExpression - ProgressionDirection.DECREASING -> checkedStepExpression.negate() - ProgressionDirection.UNKNOWN -> { - // Check value of nested step and negate step arg if needed: `if (nestedStep > 0) nestedStep else -nestedStep` - // A temporary variable is created only if necessary, so we can preserve the evaluation order. - val nestedStep = nestedInfo.step - val (tmpNestedStepVar, nestedStepExpression) = createTemporaryVariableIfNecessary(nestedStep, "nestedStep") - nestedStepVar = tmpNestedStepVar - val nestedStepPositiveCheck = irCall(stepGreaterFun).apply { - putValueArgument(0, nestedStepExpression) - putValueArgument(1, zeroStep.deepCopyWithSymbols()) - } - - val (tmpCheckedStepVar, checkedStepOrGet) = createTemporaryVariableIfNecessary(checkedStepExpression, "checkedStep") - checkedStepVar = tmpCheckedStepVar - irIfThenElse(stepType, nestedStepPositiveCheck, checkedStepOrGet, checkedStepOrGet.deepCopyWithSymbols().negate()) - } - } - - // Store the nested "first" and "last" and final "step" in temporary variables only if necessary, so we can preserve the - // evaluation order. - val (nestedFirstVar, nestedFirstExpression) = createTemporaryVariableIfNecessary(nestedInfo.first, "nestedFirst") - val (nestedLastVar, nestedLastExpression) = createTemporaryVariableIfNecessary(nestedInfo.last, "nestedLast") - val (newStepVar, newStepExpression) = createTemporaryVariableIfNecessary(checkedAndMaybeNegatedStep, "newStep") - - // Creating a progression with a step value != 1 may result in a "last" value that is smaller than the given "last". The new - // "last" value is such that iterating over the progression (by incrementing by "step") does not go over the "last" value. - // - // For example, in `1..10 step 2`, the values in the progression are [1, 3, 5, 7, 9]. Therefore the "last" value used in the - // stepped progression should be 9 even though the "last" in the nested progression is 10. Conversely, in `1..10 step 3`, the - // values in the progression are [1, 4, 7, 10], therefore the "last" value in the stepped progression is still 10. On the other - // hand, in `1..10 step 10`, the only value in the progression is 1, therefore the "last" value in the progression should be 1. - // In all cases, the "first" value is unchanged and the nested "first" can be used. - // - // The standard library calculates the correct "last" value by calling the internal getProgressionLastElement() function and we - // do the same when lowering to keep the behavior. - // - // In the case of multiple nested steps such as `1..10 step 2 step 3 step 2`, the recalculation happens 3 times: - // - In the innermost stepped progression `1..10 step 2`, the values are [1, 3, 5, 7, 9], the new "last" value is 9. (The - // return value of `getProgressionLastElement(1, 10, 2)` is 9.) - // - For `...step 3`, the values are [1, 4, 7]. It is NOT [1, 4, 7, 10] because the innermost progression stopped at 9. (The - // return value of `getProgressionLastElement(1, 9, 3)` is 7.) - // - For `...step 2`, the original "last" value of 10 is NOT restored, because the previous step already reduced "last" to 7. - // The values are [1, 3, 5, 7], the new "last" value is 7. (The return value of `getProgressionLastElement(1, 7, 2)` is 7.) - // - Therefore the final values are: first = 1, last = 7, step = 2. The final "last" is calculated as: - // getProgressionLastElement(1, - // getProgressionLastElement(1, - // getProgressionLastElement(1, 10, 2), - // 3), - // 2) - val recalculatedLast = - callGetProgressionLastElementIfNecessary(data, nestedFirstExpression, nestedLastExpression, newStepExpression) - - // Consider the following for-loop: - // - // for (i in A..B step C step D) { // Loop body } - // - // ...where `A`, `B`, `C`, `D` may have side-effects. Variables will be created for those expressions where necessary, and we - // must preserve the evaluation order when adding these variables. If all the above expressions can have side-effects (e.g., - // function calls), the final lowered form is something like: - // - // // Additional variables for inner step progression `A..B step C`: - // val innerNestedFirst = A - // val innerNestedLast = B - // // No nested step var because step for `A..B` is a constant 1 - // val innerStepArg = C - // val innerNewStep = if (innerStepArg > 0) innerStepArg - // else throw IllegalArgumentException("Step must be positive, was: $innerStepArg.") - // - // // Additional variables for outer step progression `(A..B step C) step D`: - // // No nested first var because `innerNestedFirst` is a local variable get (cannot have side-effects) - // val outerNestedLast = // "last" for `A..B step C` - // getProgressionLastElement(innerNestedFirst, innerNestedLast, innerNewStep) - // // No nested step var because nested step `innerNewStep` is a local variable get (cannot have side-effects) - // val outerStepArg = D - // val outerNewStep = if (outerStepArg > 0) outerStepArg - // else throw IllegalArgumentException("Step must be positive, was: $outerStepArg.") - // - // // Standard form of loop over progression - // var inductionVar = innerNestedFirst - // val last = // "last" for `(A..B step C) step D` - // getProgressionLastElement(innerNestedFirst, // "Passed through" from inner step progression - // outerNestedLast, outerNewStep) - // val step = outerNewStep - // if (inductionVar <= last) { - // // Loop is not empty - // do { - // val i = inductionVar - // inductionVar += step - // // Loop body - // } while (i != last) - // } - // - // Another example (`step` on non-literal progression expression): - // - // for (i in P step C) { // Loop body } - // - // ...where `P` and `C` have side-effects. The final lowered form is something like: - // - // // Additional variables: - // val progression = P - // val nestedFirst = progression.first - // val nestedLast = progression.last - // val nestedStep = progression.step - // val stepArg = C - // val checkedStep = if (stepArg > 0) stepArg - // else throw IllegalArgumentException("Step must be positive, was: $stepArg.") - // val newStep = // Direction of P is unknown so we check its step to determine whether to negate - // if (nestedStep > 0) checkedStep else -checkedStep - // - // // Standard form of loop over progression - // var inductionVar = nestedFirst - // val last = getProgressionLastElement(nestedFirst, nestedLast, newStep) - // val step = newStep - // if ((step > 0 && inductionVar <= last) || (step < 0 && last <= inductionVar)) { - // // Loop is not empty - // do { - // val i = inductionVar - // inductionVar += step - // // Loop body - // } while (i != last) - // } - // - // If the nested progression is reversed, e.g.: - // - // for (i in (A..B).reversed() step C) { // Loop body } - // - // ...in the nested HeaderInfo, "first" is `B` and "last" is `A` (the progression goes from `B` to `A`). Therefore in this case, - // the nested "last" variable must come before the nested "first" variable (if both variables are necessary). - val additionalVariables = nestedInfo.additionalVariables + if (nestedInfo.isReversed) { - listOfNotNull(nestedLastVar, nestedFirstVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar) - } else { - listOfNotNull(nestedFirstVar, nestedLastVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar) - } - - return ProgressionHeaderInfo( - data, - first = nestedFirstExpression, - last = recalculatedLast, - step = newStepExpression, - isReversed = nestedInfo.isReversed, - additionalVariables = additionalVariables, - additionalNotEmptyCondition = nestedInfo.additionalNotEmptyCondition, - direction = nestedInfo.direction - ) - } - - private fun DeclarationIrBuilder.callGetProgressionLastElementIfNecessary( - progressionType: ProgressionType, - first: IrExpression, - last: IrExpression, - step: IrExpression - ): IrExpression { - // Calling getProgressionLastElement() is not needed if step == 1 or -1; the "last" value is unchanged in such cases. - if (step.constLongValue?.absoluteValue == 1L) { - return last - } - - // Call `getProgressionLastElement(first, last, step)`. The following overloads are present in the stdlib: - // - getProgressionLastElement(Int, Int, Int): Int // Used by IntProgression and CharProgression (uses Int step) - // - getProgressionLastElement(Long, Long, Long): Long // Used by LongProgression - // - getProgressionLastElement(UInt, UInt, Int): UInt // Used by UIntProgression (uses Int step) - // - getProgressionLastElement(ULong, ULong, Long): ULong // Used by ULongProgression (uses Long step) - with(progressionType) { - val getProgressionLastElementFun = getProgressionLastElementFunction - ?: error("No `getProgressionLastElement` for progression type ${progressionType::class.simpleName}") - return if (this is UnsignedProgressionType) { - // Bounds are signed for unsigned progressions but `getProgressionLastElement` expects unsigned. - // The return value is finally converted back to signed since it will be assigned back to `last`. - irCall(getProgressionLastElementFun).apply { - putValueArgument(0, first.deepCopyWithSymbols().asElementType().asUnsigned()) - putValueArgument(1, last.deepCopyWithSymbols().asElementType().asUnsigned()) - putValueArgument(2, step.deepCopyWithSymbols().asStepType()) - }.asSigned() - } else { - irCall(getProgressionLastElementFun).apply { - // Step type is used for casting because it works for all signed progressions. In particular, - // getProgressionLastElement(Int, Int, Int) is called for CharProgression, which uses an Int step. - putValueArgument(0, first.deepCopyWithSymbols().asStepType()) - putValueArgument(1, last.deepCopyWithSymbols().asStepType()) - putValueArgument(2, step.deepCopyWithSymbols().asStepType()) - } - } - } - } -} - -/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */ -internal abstract class IndicesHandler(protected val context: CommonBackendContext) : ProgressionHandler { - - override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = - with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { - // `last = array.size - 1` (last is inclusive) for the loop `for (i in array.indices)`. - val receiver = expression.extensionReceiver!! - val last = irCall(receiver.type.sizePropertyGetter).apply { - dispatchReceiver = receiver - }.decrement() - - ProgressionHeaderInfo( - data, - first = irInt(0), - last = last, - step = irInt(1), - canOverflow = false, - direction = ProgressionDirection.INCREASING - ) - } - - abstract val IrType.sizePropertyGetter: IrSimpleFunction -} - -internal class CollectionIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) { - - override val matcher = SimpleCalleeMatcher { - extensionReceiver { it?.type?.isCollection() == true } - fqName { it == FqName("kotlin.collections.") } - parameterCount { it == 0 } - } - - override val IrType.sizePropertyGetter: IrSimpleFunction - get() = context.ir.symbols.collection.getPropertyGetter("size")!!.owner -} - -internal class ArrayIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) { - - override val matcher = SimpleCalleeMatcher { - extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() } } - fqName { it == FqName("kotlin.collections.") } - parameterCount { it == 0 } - } - - override val IrType.sizePropertyGetter: IrSimpleFunction - get() = getClass()!!.getPropertyGetter("size")!!.owner -} - -internal class CharSequenceIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) { - - override val matcher = SimpleCalleeMatcher { - extensionReceiver { it != null && it.type.run { isCharSequence() } } - fqName { it == FqName("kotlin.text.") } - parameterCount { it == 0 } - } - - override val IrType.sizePropertyGetter: IrSimpleFunction - get() = context.ir.symbols.charSequence.getPropertyGetter("length")!!.owner -} - -/** Builds a [HeaderInfo] for calls to reverse an iterable. */ -@OptIn(ObsoleteDescriptorBasedAPI::class) -internal class ReversedHandler(context: CommonBackendContext, private val visitor: HeaderInfoBuilder) : - HeaderInfoFromCallHandler { - - private val symbols = context.ir.symbols - - // Use Quantifier.ANY so we can handle all reversed iterables in the same manner. - override val matcher = createIrCallMatcher(Quantifier.ANY) { - // Matcher for reversed progression. - callee { - fqName { it == FqName("kotlin.ranges.reversed") } - extensionReceiver { it != null && it.type.toKotlinType() in symbols.progressionClassesTypes } - parameterCount { it == 0 } - } - - // TODO: Handle reversed String, Progression.withIndex(), etc. - } - - // Reverse the HeaderInfo from the underlying progression or array (if any). - override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol) = - expression.extensionReceiver!!.accept(visitor, null)?.asReversed() -} - -/** Builds a [HeaderInfo] for progressions not handled by more specialized handlers. */ -internal class DefaultProgressionHandler(private val context: CommonBackendContext) : ExpressionHandler { - - private val symbols = context.ir.symbols - - override fun matchIterable(expression: IrExpression) = ProgressionType.fromIrType(expression.type, symbols) != null - - override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? = - with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { - // Directly use the `first/last/step` properties of the progression. - val (progressionVar, progressionExpression) = createTemporaryVariableIfNecessary(expression, nameHint = "progression") - val progressionClass = progressionExpression.type.getClass()!! - val first = irCall(progressionClass.symbol.getPropertyGetter("first")!!).apply { - dispatchReceiver = progressionExpression - } - val last = irCall(progressionClass.symbol.getPropertyGetter("last")!!).apply { - dispatchReceiver = progressionExpression.deepCopyWithSymbols() - } - val step = irCall(progressionClass.symbol.getPropertyGetter("step")!!).apply { - dispatchReceiver = progressionExpression.deepCopyWithSymbols() - } - - ProgressionHeaderInfo( - ProgressionType.fromIrType(progressionExpression.type, symbols)!!, - first, - last, - step, - additionalVariables = listOfNotNull(progressionVar), - direction = ProgressionDirection.UNKNOWN - ) - } -} - -internal abstract class IndexedGetIterationHandler( - protected val context: CommonBackendContext, - private val canCacheLast: Boolean -) : ExpressionHandler { - override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? = - with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { - // Consider the case like: - // - // for (elem in A) { f(elem) }` - // - // If we lower it to: - // - // for (i in A.indices) { f(A[i]) } - // - // ...then we will break program behaviour if `A` is an expression with side-effect. Instead, we lower it to: - // - // val a = A - // for (i in a.indices) { f(a[i]) } - // - // This also ensures that the semantics of re-assignment of array variables used in the loop is consistent with the semantics - // proposed in https://youtrack.jetbrains.com/issue/KT-21354. - val objectVariable = scope.createTmpVariable( - expression, nameHint = "indexedObject" - ) - - val last = irCall(expression.type.sizePropertyGetter).apply { - dispatchReceiver = irGet(objectVariable) - } - - IndexedGetHeaderInfo( - this@IndexedGetIterationHandler.context.ir.symbols, - first = irInt(0), - last = last, - step = irInt(1), - canCacheLast = canCacheLast, - objectVariable = objectVariable, - expressionHandler = this@IndexedGetIterationHandler - ) - } - - abstract val IrType.sizePropertyGetter: IrSimpleFunction - - abstract val IrType.getFunction: IrSimpleFunction -} - -/** Builds a [HeaderInfo] for arrays. */ -internal class ArrayIterationHandler(context: CommonBackendContext) : IndexedGetIterationHandler(context, canCacheLast = true) { - override fun matchIterable(expression: IrExpression) = expression.type.run { isArray() || isPrimitiveArray() } - - override val IrType.sizePropertyGetter - get() = getClass()!!.getPropertyGetter("size")!!.owner - - override val IrType.getFunction - get() = getClass()!!.functions.single { - it.name == OperatorNameConventions.GET && - it.valueParameters.size == 1 && - it.valueParameters[0].type.isInt() - } -} - -/** - * Builds a [HeaderInfo] for iteration over characters in a [CharSequence]. - * - * Note: The value for "last" can NOT be cached (i.e., stored in a variable) because the size/length can change within the loop. This means - * that "last" is re-evaluated with each iteration of the loop. - */ -internal open class CharSequenceIterationHandler(context: CommonBackendContext, canCacheLast: Boolean = false) : - IndexedGetIterationHandler(context, canCacheLast) { - override fun matchIterable(expression: IrExpression) = expression.type.isSubtypeOfClass(context.ir.symbols.charSequence) - - // We only want to handle the known extension function for CharSequence in the standard library (top level `kotlin.text.iterator`). - // The behavior of this iterator is well-defined and can be lowered. CharSequences can have their own iterators, either as a member or - // extension function, and the behavior of those custom iterators is unknown. - override val iteratorCallMatcher = SimpleCalleeMatcher { - extensionReceiver { it != null && it.type.run { isCharSequence() } } - fqName { it == FqName("kotlin.text.${OperatorNameConventions.ITERATOR}") } - parameterCount { it == 0 } - } - - override val IrType.sizePropertyGetter: IrSimpleFunction - get() = context.ir.symbols.charSequence.getPropertyGetter("length")!!.owner - - override val IrType.getFunction: IrSimpleFunction - get() = context.ir.symbols.charSequence.getSimpleFunction(OperatorNameConventions.GET.asString())!!.owner -} - -/** - * Builds a [HeaderInfo] for iteration over characters in a [String]. - * - * Note: The value for "last" CAN be cached for Strings as they are immutable and the size/length cannot change. - */ -internal class StringIterationHandler(context: CommonBackendContext) : CharSequenceIterationHandler(context, canCacheLast = true) { - override fun matchIterable(expression: IrExpression) = expression.type.isString() - - override val IrType.sizePropertyGetter: IrSimpleFunction - get() = context.ir.symbols.string.getPropertyGetter("length")!!.owner - - override val IrType.getFunction: IrSimpleFunction - get() = context.ir.symbols.string.getSimpleFunction(OperatorNameConventions.GET.asString())!!.owner -} - -/** Builds a [HeaderInfo] for calls to `withIndex()`. */ -internal class WithIndexHandler(context: CommonBackendContext, private val visitor: NestedHeaderInfoBuilderForWithIndex) : - HeaderInfoFromCallHandler { - - // Use Quantifier.ANY so we can handle all `withIndex()` calls in the same manner. - override val matcher = createIrCallMatcher(Quantifier.ANY) { - callee { - fqName { it == FqName("kotlin.collections.withIndex") } - extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isIterable() } } - parameterCount { it == 0 } - } - callee { - fqName { it == FqName("kotlin.text.withIndex") } - extensionReceiver { it != null && it.type.isSubtypeOfClass(context.ir.symbols.charSequence) } - parameterCount { it == 0 } - } - callee { - fqName { it == FqName("kotlin.sequences.withIndex") } - extensionReceiver { it != null && it.type.run { isSequence() } } - parameterCount { it == 0 } - } - } - - override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol): HeaderInfo? { - // WithIndexHeaderInfo is a composite that contains the HeaderInfo for the underlying iterable (if any). - val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) ?: return null - - // We cannot lower `iterable.withIndex().withIndex()`. - // NestedHeaderInfoBuilderForWithIndex should not be yielding a WithIndexHeaderInfo, hence the assert. - assert(nestedInfo !is WithIndexHeaderInfo) - - return WithIndexHeaderInfo(nestedInfo) - } -} - -/** Builds a [HeaderInfo] for Iterables not handled by more specialized handlers. */ -internal open class DefaultIterableHandler(private val context: CommonBackendContext) : ExpressionHandler { - - protected open val iterableClassSymbol = context.ir.symbols.iterable - - override fun matchIterable(expression: IrExpression) = expression.type.isSubtypeOfClass(iterableClassSymbol) - - override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? = - with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { - val iteratorFun = iterableClassSymbol.getSimpleFunction(OperatorNameConventions.ITERATOR.asString())!!.owner - IterableHeaderInfo( - scope.createTmpVariable(irCall(iteratorFun).apply { dispatchReceiver = expression }, nameHint = "iterator") - ) - } -} - -// TODO: Handle Sequences by extending DefaultIterableHandler. diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DefaultIterableHandler.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DefaultIterableHandler.kt new file mode 100644 index 00000000000..499efd633d8 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DefaultIterableHandler.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.loops.ExpressionHandler +import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfo +import org.jetbrains.kotlin.backend.common.lower.loops.IterableHeaderInfo +import org.jetbrains.kotlin.ir.builders.createTmpVariable +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.isSubtypeOfClass +import org.jetbrains.kotlin.ir.util.getSimpleFunction + +/** Builds a [HeaderInfo] for Iterables not handled by more specialized handlers. */ +internal open class DefaultIterableHandler(private val context: CommonBackendContext) : + ExpressionHandler { + + protected open val iterableClassSymbol = context.ir.symbols.iterable + + override fun matchIterable(expression: IrExpression) = expression.type.isSubtypeOfClass(iterableClassSymbol) + + override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + val iteratorFun = + iterableClassSymbol.getSimpleFunction(org.jetbrains.kotlin.util.OperatorNameConventions.ITERATOR.asString())!!.owner + IterableHeaderInfo( + scope.createTmpVariable(irCall(iteratorFun).apply { dispatchReceiver = expression }, nameHint = "iterator") + ) + } +} + +// TODO: Handle Sequences by extending DefaultIterableHandler. \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DefaultProgressionHandler.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DefaultProgressionHandler.kt new file mode 100644 index 00000000000..417371f9cb7 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DefaultProgressionHandler.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.loops.* +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.getClass +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols +import org.jetbrains.kotlin.ir.util.getPropertyGetter + +/** Builds a [HeaderInfo] for progressions not handled by more specialized handlers. */ +internal class DefaultProgressionHandler(private val context: CommonBackendContext) : + ExpressionHandler { + + private val symbols = context.ir.symbols + + override fun matchIterable(expression: IrExpression) = ProgressionType.fromIrType( + expression.type, + symbols + ) != null + + override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + // Directly use the `first/last/step` properties of the progression. + val (progressionVar, progressionExpression) = createTemporaryVariableIfNecessary(expression, nameHint = "progression") + val progressionClass = progressionExpression.type.getClass()!! + val first = irCall(progressionClass.symbol.getPropertyGetter("first")!!).apply { + dispatchReceiver = progressionExpression + } + val last = irCall(progressionClass.symbol.getPropertyGetter("last")!!).apply { + dispatchReceiver = progressionExpression.deepCopyWithSymbols() + } + val step = irCall(progressionClass.symbol.getPropertyGetter("step")!!).apply { + dispatchReceiver = progressionExpression.deepCopyWithSymbols() + } + + ProgressionHeaderInfo( + ProgressionType.fromIrType(progressionExpression.type, symbols)!!, + first, + last, + step, + additionalVariables = listOfNotNull(progressionVar), + direction = ProgressionDirection.UNKNOWN + ) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DownToHandler.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DownToHandler.kt new file mode 100644 index 00000000000..31826b43d94 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/DownToHandler.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.loops.* +import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher +import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension +import org.jetbrains.kotlin.ir.builders.irInt +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.name.FqName + +/** Builds a [HeaderInfo] for progressions built using the `downTo` extension function. */ +internal class DownToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection) : + ProgressionHandler { + + override val matcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.downTo"), progressionElementTypes) + parameterCount { it == 1 } + parameter(0) { it.type in progressionElementTypes } + } + + override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + ProgressionHeaderInfo( + data, + first = expression.extensionReceiver!!, + last = expression.getValueArgument(0)!!, + step = irInt(-1), + direction = ProgressionDirection.DECREASING + ) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/IndexedGetIterationHandlers.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/IndexedGetIterationHandlers.kt new file mode 100644 index 00000000000..69c50a682cf --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/IndexedGetIterationHandlers.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.loops.ExpressionHandler +import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfo +import org.jetbrains.kotlin.backend.common.lower.loops.IndexedGetHeaderInfo +import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher +import org.jetbrains.kotlin.ir.builders.createTmpVariable +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.builders.irInt +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.functions +import org.jetbrains.kotlin.ir.util.getPropertyGetter +import org.jetbrains.kotlin.ir.util.getSimpleFunction +import org.jetbrains.kotlin.ir.util.isPrimitiveArray +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.util.OperatorNameConventions + +/** Builds a [HeaderInfo] for iteration over iterables using the `get / []` operator and an index. */ +internal abstract class IndexedGetIterationHandler( + protected val context: CommonBackendContext, + private val canCacheLast: Boolean +) : ExpressionHandler { + override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + // Consider the case like: + // + // for (elem in A) { f(elem) }` + // + // If we lower it to: + // + // for (i in A.indices) { f(A[i]) } + // + // ...then we will break program behaviour if `A` is an expression with side-effect. Instead, we lower it to: + // + // val a = A + // for (i in a.indices) { f(a[i]) } + // + // This also ensures that the semantics of re-assignment of array variables used in the loop is consistent with the semantics + // proposed in https://youtrack.jetbrains.com/issue/KT-21354. + val objectVariable = scope.createTmpVariable( + expression, nameHint = "indexedObject" + ) + + val last = irCall(expression.type.sizePropertyGetter).apply { + dispatchReceiver = irGet(objectVariable) + } + + IndexedGetHeaderInfo( + this@IndexedGetIterationHandler.context.ir.symbols, + first = irInt(0), + last = last, + step = irInt(1), + canCacheLast = canCacheLast, + objectVariable = objectVariable, + expressionHandler = this@IndexedGetIterationHandler + ) + } + + abstract val IrType.sizePropertyGetter: IrSimpleFunction + + abstract val IrType.getFunction: IrSimpleFunction +} + +/** Builds a [HeaderInfo] for arrays. */ +internal class ArrayIterationHandler(context: CommonBackendContext) : IndexedGetIterationHandler(context, canCacheLast = true) { + override fun matchIterable(expression: IrExpression) = expression.type.run { isArray() || isPrimitiveArray() } + + override val IrType.sizePropertyGetter + get() = getClass()!!.getPropertyGetter("size")!!.owner + + override val IrType.getFunction + get() = getClass()!!.functions.single { + it.name == OperatorNameConventions.GET && + it.valueParameters.size == 1 && + it.valueParameters[0].type.isInt() + } +} + +/** + * Builds a [HeaderInfo] for iteration over characters in a [CharSequence]. + * + * Note: The value for "last" can NOT be cached (i.e., stored in a variable) because the size/length can change within the loop. This means + * that "last" is re-evaluated with each iteration of the loop. + */ +internal open class CharSequenceIterationHandler(context: CommonBackendContext, canCacheLast: Boolean = false) : + IndexedGetIterationHandler(context, canCacheLast) { + override fun matchIterable(expression: IrExpression) = expression.type.isSubtypeOfClass(context.ir.symbols.charSequence) + + // We only want to handle the known extension function for CharSequence in the standard library (top level `kotlin.text.iterator`). + // The behavior of this iterator is well-defined and can be lowered. CharSequences can have their own iterators, either as a member or + // extension function, and the behavior of those custom iterators is unknown. + override val iteratorCallMatcher = SimpleCalleeMatcher { + extensionReceiver { it != null && it.type.run { isCharSequence() } } + fqName { it == FqName("kotlin.text.${OperatorNameConventions.ITERATOR}") } + parameterCount { it == 0 } + } + + override val IrType.sizePropertyGetter: IrSimpleFunction + get() = context.ir.symbols.charSequence.getPropertyGetter("length")!!.owner + + override val IrType.getFunction: IrSimpleFunction + get() = context.ir.symbols.charSequence.getSimpleFunction(OperatorNameConventions.GET.asString())!!.owner +} + +/** + * Builds a [HeaderInfo] for iteration over characters in a [String]. + * + * Note: The value for "last" CAN be cached for Strings as they are immutable and the size/length cannot change. + */ +internal class StringIterationHandler(context: CommonBackendContext) : CharSequenceIterationHandler(context, canCacheLast = true) { + override fun matchIterable(expression: IrExpression) = expression.type.isString() + + override val IrType.sizePropertyGetter: IrSimpleFunction + get() = context.ir.symbols.string.getPropertyGetter("length")!!.owner + + override val IrType.getFunction: IrSimpleFunction + get() = context.ir.symbols.string.getSimpleFunction(OperatorNameConventions.GET.asString())!!.owner +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/IndicesHandlers.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/IndicesHandlers.kt new file mode 100644 index 00000000000..fa010712af7 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/IndicesHandlers.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.loops.* +import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irInt +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.getPropertyGetter +import org.jetbrains.kotlin.ir.util.isPrimitiveArray +import org.jetbrains.kotlin.name.FqName + +/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */ +internal abstract class IndicesHandler(protected val context: CommonBackendContext) : + ProgressionHandler { + + override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + // `last = array.size - 1` (last is inclusive) for the loop `for (i in array.indices)`. + val receiver = expression.extensionReceiver!! + val last = irCall(receiver.type.sizePropertyGetter).apply { + dispatchReceiver = receiver + }.decrement() + + ProgressionHeaderInfo( + data, + first = irInt(0), + last = last, + step = irInt(1), + canOverflow = false, + direction = ProgressionDirection.INCREASING + ) + } + + abstract val IrType.sizePropertyGetter: IrSimpleFunction +} + +internal class CollectionIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) { + + override val matcher = SimpleCalleeMatcher { + extensionReceiver { it?.type?.isCollection() == true } + fqName { it == FqName("kotlin.collections.") } + parameterCount { it == 0 } + } + + override val IrType.sizePropertyGetter: IrSimpleFunction + get() = context.ir.symbols.collection.getPropertyGetter("size")!!.owner +} + +internal class ArrayIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) { + + override val matcher = SimpleCalleeMatcher { + extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() } } + fqName { it == FqName("kotlin.collections.") } + parameterCount { it == 0 } + } + + override val IrType.sizePropertyGetter: IrSimpleFunction + get() = getClass()!!.getPropertyGetter("size")!!.owner +} + +internal class CharSequenceIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) { + + override val matcher = SimpleCalleeMatcher { + extensionReceiver { it != null && it.type.run { isCharSequence() } } + fqName { it == FqName("kotlin.text.") } + parameterCount { it == 0 } + } + + override val IrType.sizePropertyGetter: IrSimpleFunction + get() = context.ir.symbols.charSequence.getPropertyGetter("length")!!.owner +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/RangeToHandler.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/RangeToHandler.kt new file mode 100644 index 00000000000..65fa0e77457 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/RangeToHandler.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionDirection +import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionHandler +import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionHeaderInfo +import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionType +import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher +import org.jetbrains.kotlin.ir.builders.irInt +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.util.OperatorNameConventions + +/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */ +internal class RangeToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection) : + ProgressionHandler { + + override val matcher = SimpleCalleeMatcher { + dispatchReceiver { it != null && it.type in progressionElementTypes } + fqName { it.pathSegments().last() == OperatorNameConventions.RANGE_TO } + parameterCount { it == 1 } + parameter(0) { it.type in progressionElementTypes } + } + + override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol) = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + ProgressionHeaderInfo( + data, + first = expression.dispatchReceiver!!, + last = expression.getValueArgument(0)!!, + step = irInt(1), + direction = ProgressionDirection.INCREASING + ) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/ReversedHandler.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/ReversedHandler.kt new file mode 100644 index 00000000000..3c9e18faa0e --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/ReversedHandler.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfoBuilder +import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfoFromCallHandler +import org.jetbrains.kotlin.backend.common.lower.matchers.Quantifier +import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher +import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.toKotlinType +import org.jetbrains.kotlin.name.FqName + +/** Builds a [HeaderInfo] for calls to reverse an iterable. */ +@OptIn(ObsoleteDescriptorBasedAPI::class) +internal class ReversedHandler(context: CommonBackendContext, private val visitor: HeaderInfoBuilder) : + HeaderInfoFromCallHandler { + + private val symbols = context.ir.symbols + + // Use Quantifier.ANY so we can handle all reversed iterables in the same manner. + override val matcher = + createIrCallMatcher(Quantifier.ANY) { + // Matcher for reversed progression. + callee { + fqName { it == FqName("kotlin.ranges.reversed") } + extensionReceiver { it != null && it.type.toKotlinType() in symbols.progressionClassesTypes } + parameterCount { it == 0 } + } + + // TODO: Handle reversed String, Progression.withIndex(), etc. + } + + // Reverse the HeaderInfo from the underlying progression or array (if any). + override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol) = + expression.extensionReceiver!!.accept(visitor, null)?.asReversed() +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/StepHandler.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/StepHandler.kt new file mode 100644 index 00000000000..c15e2dcff92 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/StepHandler.kt @@ -0,0 +1,298 @@ +/* + * Copyright 2010-2020 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.handlers + +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.loops.* +import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher +import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irConcat +import org.jetbrains.kotlin.ir.builders.irIfThenElse +import org.jetbrains.kotlin.ir.builders.irString +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.defaultType +import org.jetbrains.kotlin.ir.types.isInt +import org.jetbrains.kotlin.ir.types.isLong +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols +import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.name.FqName +import kotlin.math.absoluteValue + +/** Builds a [HeaderInfo] for progressions built using the `step` extension function. */ +internal class StepHandler( + private val context: CommonBackendContext, + private val visitor: HeaderInfoBuilder +) : ProgressionHandler { + + private val symbols = context.ir.symbols + + override val matcher = SimpleCalleeMatcher { + singleArgumentExtension( + FqName("kotlin.ranges.step"), + symbols.progressionClasses.map { it.defaultType }) + parameter(0) { it.type.isInt() || it.type.isLong() } + } + + override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + // Retrieve the HeaderInfo from the underlying progression (if any). + val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) as? ProgressionHeaderInfo + ?: return null + + val stepArg = expression.getValueArgument(0)!! + // We can return the nested info if its step is constant and its absolute value is the same as the step argument. Examples: + // + // 1..10 step 1 // Nested step is 1, argument is 1. Equivalent to `1..10`. + // 10 downTo 1 step 1 // Nested step is -1, argument is 1. Equivalent to `10 downTo 1`. + // 10 downTo 1 step 2 step 2 // Nested step is -2, argument is 2. Equivalent to `10 downTo 1 step 2`. + if (stepArg.constLongValue != null && nestedInfo.step.constLongValue?.absoluteValue == stepArg.constLongValue) { + return nestedInfo + } + + // To reduce local variable usage, we create and use temporary variables only if necessary. + val (stepArgVar, stepArgExpression) = createTemporaryVariableIfNecessary(stepArg, "stepArg") + + // The `step` standard library function only accepts positive values, and performs the following check: + // + // if (step > 0) step else throw IllegalArgumentException("Step must be positive, was: $step.") + // + // We insert this check in the lowered form only if necessary. + val stepType = data.stepClass.defaultType + val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType.getValue(data.stepClass.symbol) + val zeroStep = data.run { zeroStepExpression() } + val throwIllegalStepExceptionCall = { + irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply { + val exceptionMessage = irConcat() + exceptionMessage.addArgument(irString("Step must be positive, was: ")) + exceptionMessage.addArgument(stepArgExpression.deepCopyWithSymbols()) + exceptionMessage.addArgument(irString(".")) + putValueArgument(0, exceptionMessage) + } + } + val stepArgValueAsLong = stepArgExpression.constLongValue + val checkedStepExpression = when { + stepArgValueAsLong == null -> { + // Step argument is not a constant. + val stepPositiveCheck = irCall(stepGreaterFun).apply { + putValueArgument(0, stepArgExpression.deepCopyWithSymbols()) + putValueArgument(1, zeroStep.deepCopyWithSymbols()) + } + irIfThenElse( + stepType, + stepPositiveCheck, + stepArgExpression.deepCopyWithSymbols(), + throwIllegalStepExceptionCall() + ) + } + stepArgValueAsLong > 0L -> + // Step argument is a positive constant and is valid. + stepArgExpression.deepCopyWithSymbols() + else -> + // Step argument is a non-positive constant and is invalid, directly throw the exception. + throwIllegalStepExceptionCall() + } + + // While the `step` function accepts positive values, the "step" value in the progression depends on the direction of the + // nested progression. For example, in `10 downTo 1 step 2`, the nested progression is `10 downTo 1` which is decreasing, + // therefore the step used should be negated (-2). + // + // If we don't know the direction of the nested progression (e.g., `someProgression() step 2`) then we have to check its value + // first to determine whether to negate. + var nestedStepVar: IrVariable? = null + var checkedStepVar: IrVariable? = null + val checkedAndMaybeNegatedStep = when (nestedInfo.direction) { + ProgressionDirection.INCREASING -> checkedStepExpression + ProgressionDirection.DECREASING -> checkedStepExpression.negate() + ProgressionDirection.UNKNOWN -> { + // Check value of nested step and negate step arg if needed: `if (nestedStep > 0) checkedStep else -checkedStep` + // A temporary variable is created only if necessary, so we can preserve the evaluation order. + val nestedStep = nestedInfo.step + val (tmpNestedStepVar, nestedStepExpression) = createTemporaryVariableIfNecessary(nestedStep, "nestedStep") + nestedStepVar = tmpNestedStepVar + val nestedStepPositiveCheck = irCall(stepGreaterFun).apply { + putValueArgument(0, nestedStepExpression) + putValueArgument(1, zeroStep.deepCopyWithSymbols()) + } + + val (tmpCheckedStepVar, checkedStepOrGet) = createTemporaryVariableIfNecessary(checkedStepExpression, "checkedStep") + checkedStepVar = tmpCheckedStepVar + irIfThenElse(stepType, nestedStepPositiveCheck, checkedStepOrGet, checkedStepOrGet.deepCopyWithSymbols().negate()) + } + } + + // Store the nested "first" and "last" and final "step" in temporary variables only if necessary, so we can preserve the + // evaluation order. + val (nestedFirstVar, nestedFirstExpression) = createTemporaryVariableIfNecessary(nestedInfo.first, "nestedFirst") + val (nestedLastVar, nestedLastExpression) = createTemporaryVariableIfNecessary(nestedInfo.last, "nestedLast") + val (newStepVar, newStepExpression) = createTemporaryVariableIfNecessary(checkedAndMaybeNegatedStep, "newStep") + + // Creating a progression with a step value != 1 may result in a "last" value that is smaller than the given "last". The new + // "last" value is such that iterating over the progression (by incrementing by "step") does not go over the "last" value. + // + // For example, in `1..10 step 2`, the values in the progression are [1, 3, 5, 7, 9]. Therefore the "last" value used in the + // stepped progression should be 9 even though the "last" in the nested progression is 10. Conversely, in `1..10 step 3`, the + // values in the progression are [1, 4, 7, 10], therefore the "last" value in the stepped progression is still 10. On the other + // hand, in `1..10 step 10`, the only value in the progression is 1, therefore the "last" value in the progression should be 1. + // In all cases, the "first" value is unchanged and the nested "first" can be used. + // + // The standard library calculates the correct "last" value by calling the internal getProgressionLastElement() function and we + // do the same when lowering to keep the behavior. + // + // In the case of multiple nested steps such as `1..10 step 2 step 3 step 2`, the recalculation happens 3 times: + // - In the innermost stepped progression `1..10 step 2`, the values are [1, 3, 5, 7, 9], the new "last" value is 9. (The + // return value of `getProgressionLastElement(1, 10, 2)` is 9.) + // - For `...step 3`, the values are [1, 4, 7]. It is NOT [1, 4, 7, 10] because the innermost progression stopped at 9. (The + // return value of `getProgressionLastElement(1, 9, 3)` is 7.) + // - For `...step 2`, the original "last" value of 10 is NOT restored, because the previous step already reduced "last" to 7. + // The values are [1, 3, 5, 7], the new "last" value is 7. (The return value of `getProgressionLastElement(1, 7, 2)` is 7.) + // - Therefore the final values are: first = 1, last = 7, step = 2. The final "last" is calculated as: + // getProgressionLastElement(1, + // getProgressionLastElement(1, + // getProgressionLastElement(1, 10, 2), + // 3), + // 2) + val recalculatedLast = + callGetProgressionLastElementIfNecessary(data, nestedFirstExpression, nestedLastExpression, newStepExpression) + + // Consider the following for-loop: + // + // for (i in A..B step C step D) { // Loop body } + // + // ...where `A`, `B`, `C`, `D` may have side-effects. Variables will be created for those expressions where necessary, and we + // must preserve the evaluation order when adding these variables. If all the above expressions can have side-effects (e.g., + // function calls), the final lowered form is something like: + // + // // Additional variables for inner step progression `A..B step C`: + // val innerNestedFirst = A + // val innerNestedLast = B + // // No nested step var because step for `A..B` is a constant 1 + // val innerStepArg = C + // val innerNewStep = if (innerStepArg > 0) innerStepArg + // else throw IllegalArgumentException("Step must be positive, was: $innerStepArg.") + // + // // Additional variables for outer step progression `(A..B step C) step D`: + // // No nested first var because `innerNestedFirst` is a local variable get (cannot have side-effects) + // val outerNestedLast = // "last" for `A..B step C` + // getProgressionLastElement(innerNestedFirst, innerNestedLast, innerNewStep) + // // No nested step var because nested step `innerNewStep` is a local variable get (cannot have side-effects) + // val outerStepArg = D + // val outerNewStep = if (outerStepArg > 0) outerStepArg + // else throw IllegalArgumentException("Step must be positive, was: $outerStepArg.") + // + // // Standard form of loop over progression + // var inductionVar = innerNestedFirst + // val last = // "last" for `(A..B step C) step D` + // getProgressionLastElement(innerNestedFirst, // "Passed through" from inner step progression + // outerNestedLast, outerNewStep) + // val step = outerNewStep + // if (inductionVar <= last) { + // // Loop is not empty + // do { + // val i = inductionVar + // inductionVar += step + // // Loop body + // } while (i != last) + // } + // + // Another example (`step` on non-literal progression expression): + // + // for (i in P step C) { // Loop body } + // + // ...where `P` and `C` have side-effects. The final lowered form is something like: + // + // // Additional variables: + // val progression = P + // val nestedFirst = progression.first + // val nestedLast = progression.last + // val nestedStep = progression.step + // val stepArg = C + // val checkedStep = if (stepArg > 0) stepArg + // else throw IllegalArgumentException("Step must be positive, was: $stepArg.") + // val newStep = // Direction of P is unknown so we check its step to determine whether to negate + // if (nestedStep > 0) checkedStep else -checkedStep + // + // // Standard form of loop over progression + // var inductionVar = nestedFirst + // val last = getProgressionLastElement(nestedFirst, nestedLast, newStep) + // val step = newStep + // if ((step > 0 && inductionVar <= last) || (step < 0 && last <= inductionVar)) { + // // Loop is not empty + // do { + // val i = inductionVar + // inductionVar += step + // // Loop body + // } while (i != last) + // } + // + // If the nested progression is reversed, e.g.: + // + // for (i in (A..B).reversed() step C) { // Loop body } + // + // ...in the nested HeaderInfo, "first" is `B` and "last" is `A` (the progression goes from `B` to `A`). Therefore in this case, + // the nested "last" variable must come before the nested "first" variable (if both variables are necessary). + val additionalVariables = nestedInfo.additionalVariables + if (nestedInfo.isReversed) { + listOfNotNull(nestedLastVar, nestedFirstVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar) + } else { + listOfNotNull(nestedFirstVar, nestedLastVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar) + } + + return ProgressionHeaderInfo( + data, + first = nestedFirstExpression, + last = recalculatedLast, + step = newStepExpression, + isReversed = nestedInfo.isReversed, + additionalVariables = additionalVariables, + additionalNotEmptyCondition = nestedInfo.additionalNotEmptyCondition, + direction = nestedInfo.direction + ) + } + + private fun DeclarationIrBuilder.callGetProgressionLastElementIfNecessary( + progressionType: ProgressionType, + first: IrExpression, + last: IrExpression, + step: IrExpression + ): IrExpression { + // Calling getProgressionLastElement() is not needed if step == 1 or -1; the "last" value is unchanged in such cases. + if (step.constLongValue?.absoluteValue == 1L) { + return last + } + + // Call `getProgressionLastElement(first, last, step)`. The following overloads are present in the stdlib: + // - getProgressionLastElement(Int, Int, Int): Int // Used by IntProgression and CharProgression (uses Int step) + // - getProgressionLastElement(Long, Long, Long): Long // Used by LongProgression + // - getProgressionLastElement(UInt, UInt, Int): UInt // Used by UIntProgression (uses Int step) + // - getProgressionLastElement(ULong, ULong, Long): ULong // Used by ULongProgression (uses Long step) + with(progressionType) { + val getProgressionLastElementFun = getProgressionLastElementFunction + ?: error("No `getProgressionLastElement` for progression type ${progressionType::class.simpleName}") + return if (this is UnsignedProgressionType) { + // Bounds are signed for unsigned progressions but `getProgressionLastElement` expects unsigned. + // The return value is finally converted back to signed since it will be assigned back to `last`. + irCall(getProgressionLastElementFun).apply { + putValueArgument(0, first.deepCopyWithSymbols().asElementType().asUnsigned()) + putValueArgument(1, last.deepCopyWithSymbols().asElementType().asUnsigned()) + putValueArgument(2, step.deepCopyWithSymbols().asStepType()) + }.asSigned() + } else { + irCall(getProgressionLastElementFun).apply { + // Step type is used for casting because it works for all signed progressions. In particular, + // getProgressionLastElement(Int, Int, Int) is called for CharProgression, which uses an Int step. + putValueArgument(0, first.deepCopyWithSymbols().asStepType()) + putValueArgument(1, last.deepCopyWithSymbols().asStepType()) + putValueArgument(2, step.deepCopyWithSymbols().asStepType()) + } + } + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/UntilHandler.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/UntilHandler.kt new file mode 100644 index 00000000000..35899f657da --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/UntilHandler.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.loops.* +import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher +import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.defaultType +import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols +import org.jetbrains.kotlin.name.FqName + +/** Builds a [HeaderInfo] for progressions built using the `until` extension function. */ +internal class UntilHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection) : + ProgressionHandler { + + private val symbols = context.ir.symbols + private val uByteType = symbols.uByte?.defaultType + private val uShortType = symbols.uShort?.defaultType + private val uIntType = symbols.uInt?.defaultType + private val uLongType = symbols.uLong?.defaultType + + override val matcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.until"), progressionElementTypes) + parameterCount { it == 1 } + parameter(0) { it.type in progressionElementTypes } + } + + override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + with(data) { + // `A until B` is essentially the same as `A .. (B-1)`. However, B could be MIN_VALUE and hence `(B-1)` could underflow. + // If B is MIN_VALUE, then `A until B` is an empty range. We handle this special case be adding an additional "not empty" + // condition in the lowered for-loop. Therefore the following for-loop: + // + // for (i in A until B) { // Loop body } + // + // is lowered into: + // + // var inductionVar = A + // val last = B - 1 + // if (inductionVar <= last && B != MIN_VALUE) { + // // Loop is not empty + // do { + // val i = inductionVar + // inductionVar++ + // // Loop body + // } while (inductionVar <= last) + // } + // + // However, `B` may be an expression with side-effects that should only be evaluated once, and `A` may also have + // side-effects. They are evaluated once and in the correct order (`A` then `B`), the final lowered form is: + // + // // Additional variables + // val untilReceiverValue = A + // val untilArg = B + // // Standard form of loop over progression + // var inductionVar = untilReceiverValue + // val last = untilArg - 1 + // if (inductionVar <= last && untilArg != MIN_VALUE) { + // // Loop is not empty + // do { + // val i = inductionVar + // inductionVar++ + // // Loop body + // } while (inductionVar <= last) + // } + val receiverValue = expression.extensionReceiver!! + val untilArg = expression.getValueArgument(0)!! + + // Ensure that the argument conforms to the progression type before we decrement. + val untilArgCasted = untilArg.asElementType() + + // To reduce local variable usage, we create and use temporary variables only if necessary. + var receiverValueVar: IrVariable? = null + var untilArgVar: IrVariable? = null + var additionalVariables = emptyList() + if (untilArg.canHaveSideEffects) { + if (receiverValue.canHaveSideEffects) { + receiverValueVar = scope.createTmpVariable(receiverValue, nameHint = "untilReceiverValue") + } + untilArgVar = scope.createTmpVariable(untilArgCasted, nameHint = "untilArg") + additionalVariables = listOfNotNull(receiverValueVar, untilArgVar) + } + + val first = if (receiverValueVar == null) receiverValue else irGet(receiverValueVar) + val untilArgExpression = if (untilArgVar == null) untilArgCasted else irGet(untilArgVar) + val last = untilArgExpression.decrement() + + // Type of MIN_VALUE constant is signed even for unsigned progressions since the bounds are signed. + val additionalNotEmptyCondition = untilArg.constLongValue.let { + when { + it == null && isAdditionalNotEmptyConditionNeeded(receiverValue.type, untilArg.type) -> + // Condition is needed and untilArg is non-const. + // Build the additional "not empty" condition: `untilArg != MIN_VALUE`. + // Make sure to copy untilArgExpression as it is also used in `last`. + irNotEquals(untilArgExpression.deepCopyWithSymbols(), minValueExpression()) + it == data.minValueAsLong -> + // Hardcode "false" as additional condition so that the progression is considered empty. + // The entire lowered loop becomes a candidate for dead code elimination, depending on backend. + irFalse() + else -> + // We know that untilArg != MIN_VALUE, so the additional condition is not necessary. + null + } + } + + ProgressionHeaderInfo( + data, + first = first, + last = last, + step = irInt(1), + canOverflow = false, + additionalVariables = additionalVariables, + additionalNotEmptyCondition = additionalNotEmptyCondition, + direction = ProgressionDirection.INCREASING + ) + } + } + + private fun isAdditionalNotEmptyConditionNeeded(receiverType: IrType, argType: IrType): Boolean { + // Here are the available `until` extension functions: + // + // infix fun Char.until(to: Char): CharRange + // infix fun Byte.until(to: Byte): IntRange + // infix fun Byte.until(to: Short): IntRange + // infix fun Byte.until(to: Int): IntRange + // infix fun Byte.until(to: Long): LongRange + // infix fun Short.until(to: Byte): IntRange + // infix fun Short.until(to: Short): IntRange + // infix fun Short.until(to: Int): IntRange + // infix fun Short.until(to: Long): LongRange + // infix fun Int.until(to: Byte): IntRange + // infix fun Int.until(to: Short): IntRange + // infix fun Int.until(to: Int): IntRange + // infix fun Int.until(to: Long): LongRange + // infix fun Long.until(to: Byte): LongRange + // infix fun Long.until(to: Short): LongRange + // infix fun Long.until(to: Int): LongRange + // infix fun Long.until(to: Long): LongRange + // infix fun UByte.until(to: UByte): UIntRange + // infix fun UShort.until(to: UShort): UIntRange + // infix fun UInt.until(to: UInt): UIntRange + // infix fun ULong.until(to: ULong): ULongRange + // + // The combinations where the range element type is strictly larger than the argument type do NOT need the additional condition. + // In such combinations, there is no possibility of underflow when the argument (casted to the range element type) is decremented. + // For unexpected combinations that currently don't exist (e.g., Int until Char), we assume the check is needed to be safe. + return with(context.irBuiltIns) { + when (receiverType) { + charType -> true + byteType, shortType, intType -> when (argType) { + byteType, shortType -> false + else -> true + } + longType -> when (argType) { + byteType, shortType, intType -> false + else -> true + } + uByteType -> false + uShortType -> false + uIntType -> true + uLongType -> true + else -> true // Default in case a new `until` overload is added to stdlib and this function was not updated. + } + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/WithIndexHandler.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/WithIndexHandler.kt new file mode 100644 index 00000000000..553526723a3 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/WithIndexHandler.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2020 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.handlers + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfo +import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfoFromCallHandler +import org.jetbrains.kotlin.backend.common.lower.loops.NestedHeaderInfoBuilderForWithIndex +import org.jetbrains.kotlin.backend.common.lower.loops.WithIndexHeaderInfo +import org.jetbrains.kotlin.backend.common.lower.matchers.Quantifier +import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.isArray +import org.jetbrains.kotlin.ir.types.isIterable +import org.jetbrains.kotlin.ir.types.isSequence +import org.jetbrains.kotlin.ir.types.isSubtypeOfClass +import org.jetbrains.kotlin.ir.util.isPrimitiveArray +import org.jetbrains.kotlin.name.FqName + +/** Builds a [HeaderInfo] for calls to `withIndex()`. */ +internal class WithIndexHandler(context: CommonBackendContext, private val visitor: NestedHeaderInfoBuilderForWithIndex) : + HeaderInfoFromCallHandler { + + // Use Quantifier.ANY so we can handle all `withIndex()` calls in the same manner. + override val matcher = + createIrCallMatcher(Quantifier.ANY) { + callee { + fqName { it == FqName("kotlin.collections.withIndex") } + extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isIterable() } } + parameterCount { it == 0 } + } + callee { + fqName { it == FqName("kotlin.text.withIndex") } + extensionReceiver { it != null && it.type.isSubtypeOfClass(context.ir.symbols.charSequence) } + parameterCount { it == 0 } + } + callee { + fqName { it == FqName("kotlin.sequences.withIndex") } + extensionReceiver { it != null && it.type.run { isSequence() } } + parameterCount { it == 0 } + } + } + + override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol): HeaderInfo? { + // WithIndexHeaderInfo is a composite that contains the HeaderInfo for the underlying iterable (if any). + val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) ?: return null + + // We cannot lower `iterable.withIndex().withIndex()`. + // NestedHeaderInfoBuilderForWithIndex should not be yielding a WithIndexHeaderInfo, hence the assert. + assert(nestedInfo !is WithIndexHeaderInfo) + + return WithIndexHeaderInfo(nestedInfo) + } +} \ No newline at end of file