diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt index e606437899c..7c23e1a81e4 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt @@ -6,10 +6,7 @@ package org.jetbrains.kotlin.backend.common.ir import org.jetbrains.kotlin.backend.common.CommonBackendContext -import org.jetbrains.kotlin.builtins.KOTLIN_REFLECT_FQ_NAME -import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.builtins.PrimitiveType -import org.jetbrains.kotlin.builtins.UnsignedType +import org.jetbrains.kotlin.builtins.* import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies @@ -109,6 +106,12 @@ abstract class Symbols(val context: T, private val val progressionClasses = listOf(charProgression, intProgression, longProgression) val progressionClassesTypes = progressionClasses.map { it.descriptor.defaultType }.toSet() + val getProgressionLastElementByReturnType = builtInsPackage("kotlin", "internal").getContributedFunctions( + Name.identifier("getProgressionLastElement"), + NoLookupLocation.FROM_BACKEND + ).filter { it.containingDeclaration !is BuiltInsPackageFragment } + .map { Pair(it.returnType!!, symbolTable.referenceSimpleFunction(it)) }.toMap() + val any = symbolTable.referenceClass(builtIns.any) val unit = symbolTable.referenceClass(builtIns.unit) 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 bf91f01aff7..198472df9cc 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 @@ -31,22 +31,22 @@ internal enum class ProgressionType(val elementCastFunctionName: Name, val stepC /** Returns the [IrType] of the `first`/`last` properties and elements in the progression. */ fun elementType(builtIns: IrBuiltIns): IrType = when (this) { - ProgressionType.INT_PROGRESSION -> builtIns.intType - ProgressionType.LONG_PROGRESSION -> builtIns.longType - ProgressionType.CHAR_PROGRESSION -> builtIns.charType + INT_PROGRESSION -> builtIns.intType + LONG_PROGRESSION -> builtIns.longType + CHAR_PROGRESSION -> builtIns.charType } /** Returns the [IrType] of the `step` property in the progression. */ fun stepType(builtIns: IrBuiltIns): IrType = when (this) { - ProgressionType.INT_PROGRESSION, ProgressionType.CHAR_PROGRESSION -> builtIns.intType - ProgressionType.LONG_PROGRESSION -> builtIns.longType + INT_PROGRESSION, CHAR_PROGRESSION -> builtIns.intType + LONG_PROGRESSION -> builtIns.longType } companion object { fun fromIrType(irType: IrType, symbols: Symbols): ProgressionType? = when { - irType.isSubtypeOfClass(symbols.charProgression) -> ProgressionType.CHAR_PROGRESSION - irType.isSubtypeOfClass(symbols.intProgression) -> ProgressionType.INT_PROGRESSION - irType.isSubtypeOfClass(symbols.longProgression) -> ProgressionType.LONG_PROGRESSION + irType.isSubtypeOfClass(symbols.charProgression) -> CHAR_PROGRESSION + irType.isSubtypeOfClass(symbols.intProgression) -> INT_PROGRESSION + irType.isSubtypeOfClass(symbols.longProgression) -> LONG_PROGRESSION else -> null } } @@ -107,26 +107,49 @@ internal class ProgressionHeaderInfo( val canOverflow: Boolean by lazy { if (canOverflow != null) return@lazy canOverflow - // Induction variable can overflow if it is not a const, or is MAX/MIN_VALUE (depending on direction). + // We can't determine the safe limit at compile-time if "step" is not const. + val stepValueAsLong = step.constLongValue ?: return@lazy true + + // Induction variable can NOT overflow if "last" is const and is <= (MAX/MIN_VALUE - step) (depending on direction). + // + // Examples that can NOT overflow: + // - `0..10` cannot overflow (10 <= MAX_VALUE - 1) + // - `0..MAX_VALUE - 1` cannot overflow (MAX_VALUE - 1 <= MAX_VALUE - 1) + // - `0..MAX_VALUE - 3 step 3` cannot overflow (MAX_VALUE - 3 <= MAX_VALUE - 3) + // - `0 downTo -10` cannot overflow (-10 >= MIN_VALUE - (-1)) + // - `0 downTo MIN_VALUE + 1` (step is -1) cannot overflow (MIN_VALUE + 1 >= MIN_VALUE - (-1)) + // - `0 downTo MIN_VALUE + 3 step 3` (step is -3) cannot overflow (MIN_VALUE + 3 >= MIN_VALUE - (-3)) + // + // Examples that CAN overflow: + // - `0..MAX_VALUE` CAN overflow (MAX_VALUE > MAX_VALUE - 1) + // - `0..MAX_VALUE - 2 step 3` cannot overflow (MAX_VALUE - 2 > MAX_VALUE - 3) + // - `0 downTo MIN_VALUE` (step is -1) CAN overflow (MIN_VALUE < MIN_VALUE - (-1)) + // - `0 downTo MIN_VALUE + 2 step 3` (step is -3) cannot overflow (MIN_VALUE + 2 < MIN_VALUE - (-3)) + // - `0..10 step someStep()` CAN overflow (we don't know the step and hence can't determine the safe limit) + // - `0..someLast()` CAN overflow (we don't know the direction) + // - `someProgression()` CAN overflow (we don't know the direction) val lastValueAsLong = last.constLongValue ?: return@lazy true // If "last" is not a const Number or Char. - val constLimitAsLong = when (direction) { + when (direction) { ProgressionDirection.UNKNOWN -> // If we don't know the direction, we can't be sure which limit to use. - return@lazy true - ProgressionDirection.DECREASING -> - when (progressionType) { + true + ProgressionDirection.DECREASING -> { + val constLimitAsLong = when (progressionType) { ProgressionType.INT_PROGRESSION -> Int.MIN_VALUE.toLong() ProgressionType.CHAR_PROGRESSION -> Char.MIN_VALUE.toLong() ProgressionType.LONG_PROGRESSION -> Long.MIN_VALUE } - ProgressionDirection.INCREASING -> - when (progressionType) { + lastValueAsLong < (constLimitAsLong - stepValueAsLong) + } + ProgressionDirection.INCREASING -> { + val constLimitAsLong = when (progressionType) { ProgressionType.INT_PROGRESSION -> Int.MAX_VALUE.toLong() ProgressionType.CHAR_PROGRESSION -> Char.MAX_VALUE.toLong() ProgressionType.LONG_PROGRESSION -> Long.MAX_VALUE } + lastValueAsLong > (constLimitAsLong - stepValueAsLong) + } } - constLimitAsLong == lastValueAsLong } override fun asReversed() = ProgressionHeaderInfo( @@ -226,7 +249,8 @@ internal class HeaderInfoBuilder(context: CommonBackendContext, private val scop CharSequenceIndicesHandler(context), UntilHandler(context, progressionElementTypes), DownToHandler(context, progressionElementTypes), - RangeToHandler(context, progressionElementTypes) + RangeToHandler(context, progressionElementTypes), + StepHandler(context, this) ) private val reversedHandler = ReversedHandler(context, this) 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 index 751cf38ffa3..e285f2020e4 100644 --- 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 @@ -6,6 +6,7 @@ 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 @@ -22,6 +23,7 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +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) : @@ -93,7 +95,7 @@ internal class UntilHandler(private val context: CommonBackendContext, private v // if (inductionVar <= last && B != MIN_VALUE) { // // Loop is not empty // do { - // val loopVar = inductionVar + // val i = inductionVar // inductionVar++ // // Loop body // } while (inductionVar <= last) @@ -108,10 +110,10 @@ internal class UntilHandler(private val context: CommonBackendContext, private v // // Standard form of loop over progression // var inductionVar = untilReceiverValue // val last = untilArg - 1 - // if (inductionVar <= last && untilFunArg != MIN_VALUE) { + // if (inductionVar <= last && untilArg != MIN_VALUE) { // // Loop is not empty // do { - // val loopVar = inductionVar + // val i = inductionVar // inductionVar++ // // Loop body // } while (inductionVar <= last) @@ -218,6 +220,303 @@ internal class UntilHandler(private val context: CommonBackendContext, private v } } +/** Builds a [HeaderInfo] for progressions built using the `step` extension function. */ +internal class StepHandler( + private val context: CommonBackendContext, + private val visitor: IrElementVisitor +) : ProgressionHandler { + + private val symbols = context.ir.symbols + + override val matcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.step"), symbols.progressionClasses.map { it.typeWith() }) + 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. + var stepArgVar: IrVariable? = null + val stepArgExpression = if (stepArg.canHaveSideEffects) { + stepArgVar = scope.createTemporaryVariable(stepArg, nameHint = "stepArg") + irGet(stepArgVar) + } else { + 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.stepType(context.irBuiltIns) + val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType[stepType.toKotlinType()]!! + val zeroStep = if (data == ProgressionType.LONG_PROGRESSION) irLong(0) else irInt(0) + val throwIllegalStepExceptionCall = { + irCall(context.irBuiltIns.illegalArgumentExceptionSymbol, stepType).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 nestedStepExpression = if (nestedStep.canHaveSideEffects) { + nestedStepVar = scope.createTemporaryVariable(nestedStep, nameHint = "nestedStep") + irGet(nestedStepVar) + } else { + nestedStep + } + val nestedStepPositiveCheck = irCall(stepGreaterFun).apply { + putValueArgument(0, nestedStepExpression) + putValueArgument(1, zeroStep.deepCopyWithSymbols()) + } + + checkedStepVar = scope.createTemporaryVariable(checkedStepExpression, nameHint = "checkedStep") + irIfThenElse(stepType, nestedStepPositiveCheck, irGet(checkedStepVar), irGet(checkedStepVar).negate()) + } + } + + // Store the final "step" a temporary variable only if necessary, so we can preserve the evaluation order. + var newStepVar: IrVariable? = null + val newStepExpression = if (checkedAndMaybeNegatedStep.canHaveSideEffects) { + newStepVar = scope.createTemporaryVariable(checkedAndMaybeNegatedStep, nameHint = "newStep") + irGet(newStepVar) + } else { + checkedAndMaybeNegatedStep + } + + // Store the nested "first" and "last" in temporary variables only if necessary, so we can preserve the evaluation order. + var nestedFirstVar: IrVariable? = null + val nestedFirst = nestedInfo.first + val nestedFirstExpression = if (nestedFirst.canHaveSideEffects) { + nestedFirstVar = scope.createTemporaryVariable(nestedFirst, nameHint = "nestedFirst") + irGet(nestedFirstVar) + } else { + nestedFirst + } + + var nestedLastVar: IrVariable? = null + val nestedLast = nestedInfo.last + val nestedLastExpression = if (nestedLast.canHaveSideEffects) { + nestedLastVar = scope.createTemporaryVariable(nestedLast, nameHint = "nestedLast") + irGet(nestedLastVar) + } else { + nestedLast + } + + // 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 ((nestedStep > 0 && inductionVar <= last) || (nestedStep < 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)` + val stepType = progressionType.stepType(context.irBuiltIns).toKotlinType() + val getProgressionLastElementFun = symbols.getProgressionLastElementByReturnType[stepType] + ?: throw IllegalArgumentException("No `getProgressionLastElement` for step type $stepType") + return irCall(getProgressionLastElementFun).apply { + putValueArgument( + 0, first.deepCopyWithSymbols().castIfNecessary( + progressionType.stepType(context.irBuiltIns), + progressionType.stepCastFunctionName + ) + ) + putValueArgument( + 1, last.deepCopyWithSymbols().castIfNecessary( + progressionType.stepType(context.irBuiltIns), + progressionType.stepCastFunctionName + ) + ) + putValueArgument( + 2, step.deepCopyWithSymbols().castIfNecessary( + progressionType.stepType(context.irBuiltIns), + progressionType.stepCastFunctionName + ) + ) + } + } +} + /** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */ internal abstract class IndicesHandler(protected val context: CommonBackendContext) : ProgressionHandler { @@ -243,6 +542,7 @@ internal abstract class IndicesHandler(protected val context: CommonBackendConte } internal class CollectionIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) { + override val matcher = SimpleCalleeMatcher { extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isCollection() } } fqName { it == FqName("kotlin.collections.") }