diff --git a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBytecodeTextTestGenerated.java b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBytecodeTextTestGenerated.java index cccf477b352..203b9654741 100644 --- a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBytecodeTextTestGenerated.java +++ b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBytecodeTextTestGenerated.java @@ -2746,11 +2746,6 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilULongMinValue.kt"); } - @TestMetadata("forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt") - public void testForInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR() throws Exception { - runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt"); - } - @TestMetadata("illegalStepConst.kt") public void testIllegalStepConst() throws Exception { runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/illegalStepConst.kt"); diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionType.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionType.kt index 58b2086589d..b2d71ded126 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionType.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionType.kt @@ -45,14 +45,10 @@ internal sealed class ProgressionType( irType.isSubtypeOfClass(symbols.charProgression) -> CharProgressionType(symbols) irType.isSubtypeOfClass(symbols.intProgression) -> IntProgressionType(symbols) irType.isSubtypeOfClass(symbols.longProgression) -> LongProgressionType(symbols) - symbols.uIntProgression != null && irType.isSubtypeOfClass(symbols.uIntProgression) -> UIntProgressionType( - symbols, - allowUnsignedBounds - ) - symbols.uLongProgression != null && irType.isSubtypeOfClass(symbols.uLongProgression) -> ULongProgressionType( - symbols, - allowUnsignedBounds - ) + symbols.uIntProgression != null && irType.isSubtypeOfClass(symbols.uIntProgression) -> + UIntProgressionType(symbols, allowUnsignedBounds) + symbols.uLongProgression != null && irType.isSubtypeOfClass(symbols.uLongProgression) -> + ULongProgressionType(symbols, allowUnsignedBounds) else -> null } } 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 index 744fe3d6cfa..de6afd1042e 100644 --- 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 @@ -153,9 +153,8 @@ internal class UntilHandler(private val context: CommonBackendContext) : // 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. + // The additional condition is only needed when the argument casted to the range element type and then decremented can produce + // negative overflow - in other words, if the minimum value of `to` is the same as the minimum value for the progression type. return with(context.irBuiltIns) { when (receiverType) { charType -> true @@ -167,11 +166,9 @@ internal class UntilHandler(private val context: CommonBackendContext) : 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. + // 1. All unsigned types regardless of size "bottom out" at 0, so they all need the check. + // 2. In case new types are added to stdlib, conservatively add a check as well. + else -> true } } } diff --git a/compiler/testData/codegen/box/ranges/expression/overflowZeroToMinValue.kt b/compiler/testData/codegen/box/ranges/expression/overflowZeroToMinValue.kt index b51a8300551..b8eb8a5dbda 100644 --- a/compiler/testData/codegen/box/ranges/expression/overflowZeroToMinValue.kt +++ b/compiler/testData/codegen/box/ranges/expression/overflowZeroToMinValue.kt @@ -4,27 +4,89 @@ val MinI = Int.MIN_VALUE +val MinB = Byte.MIN_VALUE +val MinS = Short.MIN_VALUE val MinL = Long.MIN_VALUE fun box(): String { val list1 = ArrayList() - val range1 = 0..MinI step 3 + val range1 = 0.toByte()..MinB step 3 for (i in range1) { list1.add(i) if (list1.size > 23) break } if (list1 != listOf()) { - return "Wrong elements for 0..MinI step 3: $list1" + return "Wrong elements for 0.toByte()..MinB step 3: $list1" } - val list2 = ArrayList() - val range2 = 0L..MinL step 3 + val list2 = ArrayList() + val range2 = 0.toShort()..MinS step 3 for (i in range2) { list2.add(i) if (list2.size > 23) break } - if (list2 != listOf()) { - return "Wrong elements for 0L..MinL step 3: $list2" + if (list2 != listOf()) { + return "Wrong elements for 0.toShort()..MinS step 3: $list2" + } + + val list3 = ArrayList() + val range3 = 0..MinI step 3 + for (i in range3) { + list3.add(i) + if (list3.size > 23) break + } + if (list3 != listOf()) { + return "Wrong elements for 0..MinI step 3: $list3" + } + + val list4 = ArrayList() + val range4 = 0L..MinL step 3 + for (i in range4) { + list4.add(i) + if (list4.size > 23) break + } + if (list4 != listOf()) { + return "Wrong elements for 0L..MinL step 3: $list4" + } + + val list5 = ArrayList() + val range5 = 0.toByte() until MinB step 3 + for (i in range5) { + list5.add(i) + if (list5.size > 23) break + } + if (list5 != listOf()) { + return "Wrong elements for 0.toByte() until MinB step 3: $list5" + } + + val list6 = ArrayList() + val range6 = 0.toShort() until MinS step 3 + for (i in range6) { + list6.add(i) + if (list6.size > 23) break + } + if (list6 != listOf()) { + return "Wrong elements for 0.toShort() until MinS step 3: $list6" + } + + val list7 = ArrayList() + val range7 = 0 until MinI step 3 + for (i in range7) { + list7.add(i) + if (list7.size > 23) break + } + if (list7 != listOf()) { + return "Wrong elements for 0 until MinI step 3: $list7" + } + + val list8 = ArrayList() + val range8 = 0L until MinL step 3 + for (i in range8) { + list8.add(i) + if (list8.size > 23) break + } + if (list8 != listOf()) { + return "Wrong elements for 0L until MinL step 3: $list8" } return "OK" diff --git a/compiler/testData/codegen/box/ranges/literal/overflowZeroToMinValue.kt b/compiler/testData/codegen/box/ranges/literal/overflowZeroToMinValue.kt index 0b09e72cee7..feed8bc7d1b 100644 --- a/compiler/testData/codegen/box/ranges/literal/overflowZeroToMinValue.kt +++ b/compiler/testData/codegen/box/ranges/literal/overflowZeroToMinValue.kt @@ -4,25 +4,81 @@ val MinI = Int.MIN_VALUE +val MinB = Byte.MIN_VALUE +val MinS = Short.MIN_VALUE val MinL = Long.MIN_VALUE fun box(): String { val list1 = ArrayList() - for (i in 0..MinI step 3) { + for (i in 0.toByte()..MinB step 3) { list1.add(i) if (list1.size > 23) break } if (list1 != listOf()) { - return "Wrong elements for 0..MinI step 3: $list1" + return "Wrong elements for 0.toByte()..MinB step 3: $list1" } - val list2 = ArrayList() - for (i in 0L..MinL step 3) { + val list2 = ArrayList() + for (i in 0.toShort()..MinS step 3) { list2.add(i) if (list2.size > 23) break } - if (list2 != listOf()) { - return "Wrong elements for 0L..MinL step 3: $list2" + if (list2 != listOf()) { + return "Wrong elements for 0.toShort()..MinS step 3: $list2" + } + + val list3 = ArrayList() + for (i in 0..MinI step 3) { + list3.add(i) + if (list3.size > 23) break + } + if (list3 != listOf()) { + return "Wrong elements for 0..MinI step 3: $list3" + } + + val list4 = ArrayList() + for (i in 0L..MinL step 3) { + list4.add(i) + if (list4.size > 23) break + } + if (list4 != listOf()) { + return "Wrong elements for 0L..MinL step 3: $list4" + } + + val list5 = ArrayList() + for (i in 0.toByte() until MinB step 3) { + list5.add(i) + if (list5.size > 23) break + } + if (list5 != listOf()) { + return "Wrong elements for 0.toByte() until MinB step 3: $list5" + } + + val list6 = ArrayList() + for (i in 0.toShort() until MinS step 3) { + list6.add(i) + if (list6.size > 23) break + } + if (list6 != listOf()) { + return "Wrong elements for 0.toShort() until MinS step 3: $list6" + } + + val list7 = ArrayList() + for (i in 0 until MinI step 3) { + list7.add(i) + if (list7.size > 23) break + } + if (list7 != listOf()) { + return "Wrong elements for 0 until MinI step 3: $list7" + } + + val list8 = ArrayList() + for (i in 0L until MinL step 3) { + list8.add(i) + if (list8.size > 23) break + } + if (list8 != listOf()) { + return "Wrong elements for 0L until MinL step 3: $list8" } return "OK" diff --git a/compiler/testData/codegen/box/ranges/unsigned/expression/overflowZeroToMinValue.kt b/compiler/testData/codegen/box/ranges/unsigned/expression/overflowZeroToMinValue.kt index f6774a424fe..9d0ead69173 100644 --- a/compiler/testData/codegen/box/ranges/unsigned/expression/overflowZeroToMinValue.kt +++ b/compiler/testData/codegen/box/ranges/unsigned/expression/overflowZeroToMinValue.kt @@ -4,27 +4,89 @@ val MinUI = UInt.MIN_VALUE +val MinUB = UByte.MIN_VALUE +val MinUS = UShort.MIN_VALUE val MinUL = ULong.MIN_VALUE fun box(): String { val list1 = ArrayList() - val range1 = 1u..MinUI step 3 + val range1 = 1u.toUByte()..MinUB step 3 for (i in range1) { list1.add(i) if (list1.size > 23) break } if (list1 != listOf()) { - return "Wrong elements for 1u..MinUI step 3: $list1" + return "Wrong elements for 1u.toUByte()..MinUB step 3: $list1" } - val list2 = ArrayList() - val range2 = 1uL..MinUL step 3 + val list2 = ArrayList() + val range2 = 1u.toUShort()..MinUS step 3 for (i in range2) { list2.add(i) if (list2.size > 23) break } - if (list2 != listOf()) { - return "Wrong elements for 1uL..MinUL step 3: $list2" + if (list2 != listOf()) { + return "Wrong elements for 1u.toUShort()..MinUS step 3: $list2" + } + + val list3 = ArrayList() + val range3 = 1u..MinUI step 3 + for (i in range3) { + list3.add(i) + if (list3.size > 23) break + } + if (list3 != listOf()) { + return "Wrong elements for 1u..MinUI step 3: $list3" + } + + val list4 = ArrayList() + val range4 = 1uL..MinUL step 3 + for (i in range4) { + list4.add(i) + if (list4.size > 23) break + } + if (list4 != listOf()) { + return "Wrong elements for 1uL..MinUL step 3: $list4" + } + + val list5 = ArrayList() + val range5 = 1u.toUByte() until MinUB step 3 + for (i in range5) { + list5.add(i) + if (list5.size > 23) break + } + if (list5 != listOf()) { + return "Wrong elements for 1u.toUByte() until MinUB step 3: $list5" + } + + val list6 = ArrayList() + val range6 = 1u.toUShort() until MinUS step 3 + for (i in range6) { + list6.add(i) + if (list6.size > 23) break + } + if (list6 != listOf()) { + return "Wrong elements for 1u.toUShort() until MinUS step 3: $list6" + } + + val list7 = ArrayList() + val range7 = 1u until MinUI step 3 + for (i in range7) { + list7.add(i) + if (list7.size > 23) break + } + if (list7 != listOf()) { + return "Wrong elements for 1u until MinUI step 3: $list7" + } + + val list8 = ArrayList() + val range8 = 1uL until MinUL step 3 + for (i in range8) { + list8.add(i) + if (list8.size > 23) break + } + if (list8 != listOf()) { + return "Wrong elements for 1uL until MinUL step 3: $list8" } return "OK" diff --git a/compiler/testData/codegen/box/ranges/unsigned/literal/overflowZeroToMinValue.kt b/compiler/testData/codegen/box/ranges/unsigned/literal/overflowZeroToMinValue.kt index c3e1bc42673..7ad278920dc 100644 --- a/compiler/testData/codegen/box/ranges/unsigned/literal/overflowZeroToMinValue.kt +++ b/compiler/testData/codegen/box/ranges/unsigned/literal/overflowZeroToMinValue.kt @@ -4,25 +4,81 @@ val MinUI = UInt.MIN_VALUE +val MinUB = UByte.MIN_VALUE +val MinUS = UShort.MIN_VALUE val MinUL = ULong.MIN_VALUE fun box(): String { val list1 = ArrayList() - for (i in 1u..MinUI step 3) { + for (i in 1u.toUByte()..MinUB step 3) { list1.add(i) if (list1.size > 23) break } if (list1 != listOf()) { - return "Wrong elements for 1u..MinUI step 3: $list1" + return "Wrong elements for 1u.toUByte()..MinUB step 3: $list1" } - val list2 = ArrayList() - for (i in 1uL..MinUL step 3) { + val list2 = ArrayList() + for (i in 1u.toUShort()..MinUS step 3) { list2.add(i) if (list2.size > 23) break } - if (list2 != listOf()) { - return "Wrong elements for 1uL..MinUL step 3: $list2" + if (list2 != listOf()) { + return "Wrong elements for 1u.toUShort()..MinUS step 3: $list2" + } + + val list3 = ArrayList() + for (i in 1u..MinUI step 3) { + list3.add(i) + if (list3.size > 23) break + } + if (list3 != listOf()) { + return "Wrong elements for 1u..MinUI step 3: $list3" + } + + val list4 = ArrayList() + for (i in 1uL..MinUL step 3) { + list4.add(i) + if (list4.size > 23) break + } + if (list4 != listOf()) { + return "Wrong elements for 1uL..MinUL step 3: $list4" + } + + val list5 = ArrayList() + for (i in 1u.toUByte() until MinUB step 3) { + list5.add(i) + if (list5.size > 23) break + } + if (list5 != listOf()) { + return "Wrong elements for 1u.toUByte() until MinUB step 3: $list5" + } + + val list6 = ArrayList() + for (i in 1u.toUShort() until MinUS step 3) { + list6.add(i) + if (list6.size > 23) break + } + if (list6 != listOf()) { + return "Wrong elements for 1u.toUShort() until MinUS step 3: $list6" + } + + val list7 = ArrayList() + for (i in 1u until MinUI step 3) { + list7.add(i) + if (list7.size > 23) break + } + if (list7 != listOf()) { + return "Wrong elements for 1u until MinUI step 3: $list7" + } + + val list8 = ArrayList() + for (i in 1uL until MinUL step 3) { + list8.add(i) + if (list8.size > 23) break + } + if (list8 != listOf()) { + return "Wrong elements for 1uL until MinUL step 3: $list8" } return "OK" diff --git a/compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt b/compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt deleted file mode 100644 index e71e6089ec7..00000000000 --- a/compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt +++ /dev/null @@ -1,36 +0,0 @@ -// TARGET_BACKEND: JVM_IR -// WITH_RUNTIME -fun testUByteUntilUByte(a: UByte, b: UByte): Int { - var sum = 0 - for (i in a until b) { - sum += i.toInt() - } - return sum -} - -fun testUShortUntilUShort(a: UShort, b: UShort): Int { - var sum = 0 - for (i in a until b) { - sum += i.toInt() - } - return sum -} - -// For "until" progressions in JVM IR, there is typically a check that the range is not empty: upper bound != MIN_VALUE. -// However, this check is not needed when the upper bound is smaller than the range element type. -// Here are the available `until` extension functions with mixed bounds that return UIntRange: -// -// infix fun UByte.until(to: UByte): UIntRange // NO bound check needed -// infix fun UShort.until(to: UShort): UIntRange // NO bound check needed - -// 0 iterator -// 0 getStart -// 0 getEnd -// 0 getFirst -// 0 getLast -// 0 getStep -// 2 IFGT -// 2 IFLE -// 4 IF -// 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 f1e985630ab..d618016320b 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -2746,11 +2746,6 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilULongMinValue.kt"); } - @TestMetadata("forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt") - public void testForInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR() throws Exception { - runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt"); - } - @TestMetadata("illegalStepConst.kt") public void testIllegalStepConst() throws Exception { runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/illegalStepConst.kt"); diff --git a/libraries/stdlib/test/ranges/RangeIterationTest.kt b/libraries/stdlib/test/ranges/RangeIterationTest.kt index 0f0ceb62bcf..af24a10b2f1 100644 --- a/libraries/stdlib/test/ranges/RangeIterationTest.kt +++ b/libraries/stdlib/test/ranges/RangeIterationTest.kt @@ -467,12 +467,26 @@ public class RangeIterationTest : RangeIterationTestBase() { } @Test fun overflowZeroToMinValue() { + doTest(0.toByte()..MinB step 3, 0, MinB.toInt(), 3, listOf()) + doTest(0.toShort()..MinS step 3, 0, MinS.toInt(), 3, listOf()) doTest(0..MinI step 3, 0, MinI, 3, listOf()) doTest(0L..MinL step 3, 0, MinL, 3L, listOf()) + doTest(0.toByte() until MinB step 3, 0, MinB.toInt() - 1, 3, listOf()) + doTest(0.toShort() until MinS step 3, 0, MinS.toInt() - 1, 3, listOf()) + doTest(0 until MinI step 3, 1, 0, 3, listOf()) + doTest(0L until MinL step 3, 1L, 0L, 3L, listOf()) + // 1u as used as a start since min value is 0u for unsigned types + doTest(1u.toUByte()..MinUB step 3, 1u, MinUB.toUInt(), 3, listOf()) + doTest(1u.toUShort()..MinUS step 3, 1u, MinUS.toUInt(), 3, listOf()) doTest(1u..MinUI step 3, 1u, MinUI, 3, listOf()) doTest(1uL..MinUL step 3, 1u, MinUL, 3L, listOf()) + + doTest(1u.toUByte() until MinUB step 3, MaxUI, MinUI, 3, listOf()) + doTest(1u.toUShort() until MinUS step 3, MaxUI, MinUI, 3, listOf()) + doTest(1u until MinUI step 3, MaxUI, MinUI, 3, listOf()) + doTest(1uL until MinUL step 3, MaxUL, MinUL, 3L, listOf()) } @Test fun progressionDownToMinValue() {