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 9bbe7216618..ae59edbaf5d 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 @@ -104,6 +104,8 @@ open class BuiltinSymbolsBase(protected val irBuiltIns: IrBuiltIns, protected va open val uLong = getClassOrNull(Name.identifier("ULong"), "kotlin") val uIntProgression = progressionOrNull("UIntProgression") val uLongProgression = progressionOrNull("ULongProgression") + val uIntRange = progressionOrNull("UIntRange") + val uLongRange = progressionOrNull("ULongRange") val sequence = getClassOrNull(Name.identifier("Sequence"), "kotlin", "sequences") val charProgression = progression("CharProgression") @@ -111,6 +113,11 @@ open class BuiltinSymbolsBase(protected val irBuiltIns: IrBuiltIns, protected va val longProgression = progression("LongProgression") val progressionClasses = listOfNotNull(charProgression, intProgression, longProgression, uIntProgression, uLongProgression) + val charRange = progression("CharRange") + val intRange = progression("IntRange") + val longRange = progression("LongRange") + val rangeClasses = listOfNotNull(charRange, intRange, longRange, uIntRange, uLongRange) + val getProgressionLastElementByReturnType = builtInsPackage("kotlin", "internal") .getContributedFunctions(Name.identifier("getProgressionLastElement"), NoLookupLocation.FROM_BACKEND) .filter { it.containingDeclaration !is BuiltInsPackageFragment } 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 index 32ec8026287..36760fa1601 100644 --- 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 @@ -9,8 +9,10 @@ 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.builders.irInt 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.getClass import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols import org.jetbrains.kotlin.ir.util.getPropertyGetter @@ -20,6 +22,7 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte ExpressionHandler { private val symbols = context.ir.symbols + private val rangeClassesTypes = symbols.rangeClasses.map { it.defaultType }.toSet() override fun matchIterable(expression: IrExpression) = ProgressionType.fromIrType( expression.type, @@ -37,9 +40,17 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte val last = irCall(progressionClass.symbol.getPropertyGetter("last")!!).apply { dispatchReceiver = progressionExpression.deepCopyWithSymbols() } - val step = irCall(progressionClass.symbol.getPropertyGetter("step")!!).apply { - dispatchReceiver = progressionExpression.deepCopyWithSymbols() + + // *Ranges (e.g., IntRange) have step == 1 and is always increasing. + val isRange = progressionExpression.type in rangeClassesTypes + val step = if (isRange) { + irInt(1) + } else { + irCall(progressionClass.symbol.getPropertyGetter("step")!!).apply { + dispatchReceiver = progressionExpression.deepCopyWithSymbols() + } } + val direction = if (isRange) ProgressionDirection.INCREASING else ProgressionDirection.UNKNOWN ProgressionHeaderInfo( ProgressionType.fromIrType(progressionExpression.type, symbols)!!, @@ -47,7 +58,7 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte last, step, additionalStatements = listOfNotNull(progressionVar), - direction = ProgressionDirection.UNKNOWN + direction = direction ) } } \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/forLoop/primitiveRange.kt b/compiler/testData/codegen/bytecodeText/forLoop/primitiveRange.kt index 535e629cb53..25a678239cc 100644 --- a/compiler/testData/codegen/bytecodeText/forLoop/primitiveRange.kt +++ b/compiler/testData/codegen/bytecodeText/forLoop/primitiveRange.kt @@ -7,4 +7,5 @@ fun f(r: IntRange) { // 0 getStart // 0 getEnd // 1 getFirst -// 1 getLast \ No newline at end of file +// 1 getLast +// 0 getStep \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepConstOnNonLiteralProgression.kt b/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepConstOnNonLiteralProgression.kt index 247ccc32e28..3f4e61ef32c 100644 --- a/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepConstOnNonLiteralProgression.kt +++ b/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepConstOnNonLiteralProgression.kt @@ -1,6 +1,6 @@ // TARGET_BACKEND: JVM_IR fun box(): String { - val intProgression = 1..7 + val intProgression = 1..7 step 3 // `step` ensures type is IntProgression, NOT IntRange for (i in intProgression step 2) { } @@ -15,10 +15,9 @@ fun box(): String { // Expected lowered form of loop: // // // Additional statements: -// val progression = intProgression -// val nestedFirst = progression.first -// val nestedLast = progression.last -// val nestedStep = progression.step +// val nestedFirst = intProgression.first +// val nestedLast = intProgression.last +// val nestedStep = intProgression.step // val maybeNegatedStep = if (nestedStep <= 0) -2 else 2 // // // Standard form of loop over progression diff --git a/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepNonConstOnNonLiteralProgression.kt b/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepNonConstOnNonLiteralProgression.kt index 67e0e1e9639..c83f788eb42 100644 --- a/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepNonConstOnNonLiteralProgression.kt +++ b/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepNonConstOnNonLiteralProgression.kt @@ -2,7 +2,7 @@ fun one() = 1 fun box(): String { - val intProgression = 1..7 + val intProgression = 1..7 step 3 // `step` ensures type is IntProgression, NOT IntRange for (i in intProgression step one()) { } @@ -16,10 +16,9 @@ fun box(): String { // Expected lowered form of loop: // // // Additional statements: -// val progression = intProgression -// val nestedFirst = progression.first -// val nestedLast = progression.last -// val nestedStep = progression.step +// val nestedFirst = intProgression.first +// val nestedLast = intProgression.last +// val nestedStep = intProgression.step // var stepArg = one() // if (stepArg <= 0) throw IllegalArgumentException("Step must be positive, was: $stepArg.") // if (nestedStep <= 0) stepArg = -stepArg diff --git a/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepOnNonLiteralRange.kt b/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepOnNonLiteralRange.kt new file mode 100644 index 00000000000..675a441ca5f --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/forLoop/stepped/stepOnNonLiteralRange.kt @@ -0,0 +1,45 @@ +// TARGET_BACKEND: JVM_IR +fun box(): String { + val intRange = 1..7 + for (i in intRange step 2) { + } + + return "OK" +} + +// For "step" progressions in JVM IR, a call to getProgressionLastElement() is made to compute the "last" value. +// If "step" is called on a non-literal progression, there is a check to see if that progression's step value is < 0. +// However, if the progression is of type *Range (e.g., IntRange) instead of *Progression (e.g., IntProgression), this +// check is not needed since *Range always has step == 1. +// +// Expected lowered form of loop: +// +// // Additional statements: +// val nestedFirst = intRange.first +// val nestedLast = intRange.last +// +// // Standard form of loop over progression +// var inductionVar = nestedFirst +// val last = getProgressionLastElement(nestedFirst, nestedLast, 2) +// if (inductionVar <= last) { +// // Loop is not empty +// do { +// val i = inductionVar +// inductionVar += 2 +// // Loop body +// } while (i != last) +// } + +// 0 iterator +// 0 getStart +// 0 getEnd +// 1 getFirst +// 1 getLast +// 0 getStep +// 1 INVOKESTATIC kotlin/internal/ProgressionUtilKt.getProgressionLastElement +// 0 NEW java/lang/IllegalArgumentException +// 0 ATHROW +// 1 IF_ICMPGT +// 1 IF_ICMPNE +// 2 IF +// 0 INEG \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepConstOnNonLiteralProgression.kt b/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepConstOnNonLiteralProgression.kt index ee5e4941747..48c316d7d23 100644 --- a/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepConstOnNonLiteralProgression.kt +++ b/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepConstOnNonLiteralProgression.kt @@ -1,7 +1,7 @@ // TARGET_BACKEND: JVM_IR // WITH_RUNTIME fun box(): String { - val uintProgression = 1u..7u + val uintProgression = 1u..7u step 3 // `step` ensures type is UIntProgression, NOT UIntRange for (i in uintProgression step 2) { } @@ -16,10 +16,9 @@ fun box(): String { // Expected lowered form of loop: // // // Additional statements: -// val progression = intProgression -// val nestedFirst = progression.first -// val nestedLast = progression.last -// val nestedStep = progression.step +// val nestedFirst = uintProgression.first +// val nestedLast = uintProgression.last +// val nestedStep = uintProgression.step // val maybeNegatedStep = if (nestedStep <= 0) -2 else 2 // // // Standard form of loop over progression diff --git a/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepNonConstOnNonLiteralProgression.kt b/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepNonConstOnNonLiteralProgression.kt index fae5292fe6c..f7e9a93ad0f 100644 --- a/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepNonConstOnNonLiteralProgression.kt +++ b/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepNonConstOnNonLiteralProgression.kt @@ -3,7 +3,7 @@ fun one() = 1 fun box(): String { - val uintProgression = 1u..7u + val uintProgression = 1u..7u step 3 // `step` ensures type is UIntProgression, NOT UIntRange for (i in uintProgression step one()) { } @@ -17,10 +17,9 @@ fun box(): String { // Expected lowered form of loop: // // // Additional statements: -// val progression = intProgression -// val nestedFirst = progression.first -// val nestedLast = progression.last -// val nestedStep = progression.step +// val nestedFirst = uintProgression.first +// val nestedLast = uintProgression.last +// val nestedStep = uintProgression.step // var stepArg = one() // if (stepArg <= 0) throw IllegalArgumentException("Step must be positive, was: $stepArg.") // if (nestedStep <= 0) stepArg = -stepArg diff --git a/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepOnNonLiteralRange.kt b/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepOnNonLiteralRange.kt new file mode 100644 index 00000000000..4da2218b0d7 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepOnNonLiteralRange.kt @@ -0,0 +1,49 @@ +// TARGET_BACKEND: JVM_IR +// WITH_RUNTIME +fun box(): String { + val uintRange = 1u..7u + for (i in uintRange step 2) { + } + + return "OK" +} + +// For "step" progressions in JVM IR, a call to getProgressionLastElement() is made to compute the "last" value. +// If "step" is called on a non-literal progression, there is a check to see if that progression's step value is < 0. +// However, if the progression is of type *Range (e.g., IntRange) instead of *Progression (e.g., IntProgression), this +// check is not needed since *Range always has step == 1. +// +// Expected lowered form of loop: +// +// // Additional statements: +// val nestedFirst = uintRange.first +// val nestedLast = uintRange.last +// +// // Standard form of loop over progression +// var inductionVar = nestedFirst +// val last = getProgressionLastElement(nestedFirst, nestedLast, 2) +// if (inductionVar <= last) { +// // Loop is not empty +// do { +// val i = inductionVar +// inductionVar += 2 +// // Loop body +// } while (i != last) +// } + +// 0 iterator +// 0 getStart +// 0 getEnd +// 1 getFirst +// 1 getLast +// 0 getStep +// 1 INVOKESTATIC kotlin/internal/UProgressionUtilKt.getProgressionLastElement +// 0 NEW java/lang/IllegalArgumentException +// 0 ATHROW +// 1 INVOKESTATIC kotlin/UnsignedKt.uintCompare +// 1 IFGT +// 1 IF_ICMPNE +// 2 IF +// 0 INEG +// 0 INVOKESTATIC kotlin/UInt.constructor-impl +// 0 INVOKE\w+ kotlin/UInt.(un)?box-impl diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index 51aaea0811a..ec7bc168de8 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -2502,6 +2502,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/forLoop/stepped/stepNonConstOnNonLiteralProgression.kt"); } + @TestMetadata("stepOnNonLiteralRange.kt") + public void testStepOnNonLiteralRange() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/stepped/stepOnNonLiteralRange.kt"); + } + @TestMetadata("stepOne.kt") public void testStepOne() throws Exception { runTest("compiler/testData/codegen/bytecodeText/forLoop/stepped/stepOne.kt"); @@ -2630,6 +2635,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepNonConstOnNonLiteralProgression.kt"); } + @TestMetadata("stepOnNonLiteralRange.kt") + public void testStepOnNonLiteralRange() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepOnNonLiteralRange.kt"); + } + @TestMetadata("stepThenDifferentStep.kt") public void testStepThenDifferentStep() throws Exception { runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/stepThenDifferentStep.kt");