From 28759a3ac307a9c6741437119783bd754f657abe Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 9 Dec 2022 14:57:22 +0100 Subject: [PATCH] Optimize rangeUntil operator in for-loops and contains Newly added tests are basically copies of the existing tests on `until`. Note that this operator is optimized for all backends, but the fact that it's optimized is only checked for the JVM backend in bytecode text tests. #KT-53330 Fixed --- .../codegen/FirBytecodeTextTestGenerated.java | 70 +++++++++++++++++++ .../common/lower/RangeContainsLowering.kt | 1 + .../common/lower/loops/ForLoopsLowering.kt | 2 +- .../backend/common/lower/loops/HeaderInfo.kt | 1 + .../lower/loops/handlers/RangeUntilHandler.kt | 39 +++++++++++ .../forInRangeUntil/forInRangeUntilChar.kt | 37 ++++++++++ .../forInRangeUntilCharMaxValue.kt | 39 +++++++++++ .../forInRangeUntilCharMinValue.kt | 38 ++++++++++ .../forInRangeUntil/forInRangeUntilInt.kt | 36 ++++++++++ .../forInRangeUntilIntMaxValue.kt | 38 ++++++++++ .../forInRangeUntilIntMinValue.kt | 41 +++++++++++ .../forInRangeUntil/forInRangeUntilLong.kt | 37 ++++++++++ .../forInRangeUntilLongMaxValue.kt | 39 +++++++++++ .../forInRangeUntilLongMinValue.kt | 38 ++++++++++ .../bytecodeText/ranges/inRangeUntil.kt | 16 +++++ .../codegen/BytecodeTextTestGenerated.java | 70 +++++++++++++++++++ .../codegen/IrBytecodeTextTestGenerated.java | 70 +++++++++++++++++++ 17 files changed, 611 insertions(+), 1 deletion(-) create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/handlers/RangeUntilHandler.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilChar.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilCharMaxValue.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilCharMinValue.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilInt.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilIntMaxValue.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilIntMinValue.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilLong.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilLongMaxValue.kt create mode 100644 compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilLongMinValue.kt create mode 100644 compiler/testData/codegen/bytecodeText/ranges/inRangeUntil.kt diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java index bad5caf203f..7c9f2d7c7ed 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java @@ -2772,6 +2772,70 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest { } } + @Nested + @TestMetadata("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil") + @TestDataPath("$PROJECT_ROOT") + public class ForInRangeUntil { + @Test + public void testAllFilesPresentInForInRangeUntil() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("forInRangeUntilChar.kt") + public void testForInRangeUntilChar() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilChar.kt"); + } + + @Test + @TestMetadata("forInRangeUntilCharMaxValue.kt") + public void testForInRangeUntilCharMaxValue() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilCharMaxValue.kt"); + } + + @Test + @TestMetadata("forInRangeUntilCharMinValue.kt") + public void testForInRangeUntilCharMinValue() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilCharMinValue.kt"); + } + + @Test + @TestMetadata("forInRangeUntilInt.kt") + public void testForInRangeUntilInt() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilInt.kt"); + } + + @Test + @TestMetadata("forInRangeUntilIntMaxValue.kt") + public void testForInRangeUntilIntMaxValue() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilIntMaxValue.kt"); + } + + @Test + @TestMetadata("forInRangeUntilIntMinValue.kt") + public void testForInRangeUntilIntMinValue() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilIntMinValue.kt"); + } + + @Test + @TestMetadata("forInRangeUntilLong.kt") + public void testForInRangeUntilLong() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilLong.kt"); + } + + @Test + @TestMetadata("forInRangeUntilLongMaxValue.kt") + public void testForInRangeUntilLongMaxValue() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilLongMaxValue.kt"); + } + + @Test + @TestMetadata("forInRangeUntilLongMinValue.kt") + public void testForInRangeUntilLongMinValue() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilLongMinValue.kt"); + } + } + @Nested @TestMetadata("compiler/testData/codegen/bytecodeText/forLoop/forInRangeWithUpperBoundMinus1") @TestDataPath("$PROJECT_ROOT") @@ -5224,6 +5288,12 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/ranges/inOptimizableUnsignedRange.kt"); } + @Test + @TestMetadata("inRangeUntil.kt") + public void testInRangeUntil() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/ranges/inRangeUntil.kt"); + } + @Test @TestMetadata("inUntil.kt") public void testInUntil() throws Exception { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/RangeContainsLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/RangeContainsLowering.kt index b9335a05730..eb57086c52c 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/RangeContainsLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/RangeContainsLowering.kt @@ -393,6 +393,7 @@ internal open class RangeHeaderInfoBuilder(context: CommonBackendContext, scopeO ArrayIndicesHandler(context), CharSequenceIndicesHandler(context), UntilHandler(context), + RangeUntilHandler(context), DownToHandler(context), RangeToHandler(context) ) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt index 3e1924c9079..14beece0beb 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt @@ -79,7 +79,7 @@ val forLoopsPhase = makeIrFilePhase( * } while (loopVar != last) * } * ``` - * If loop is an until loop (e.g., `for (i in A until B)`), it is transformed into: + * If loop is an until loop (e.g., `for (i in A until B)` or `for (i in A.. { + private val progressionElementTypes = context.ir.symbols.progressionElementTypes + + override fun matchIterable(expression: IrCall): Boolean { + val callee = expression.symbol.owner + return callee.valueParameters.singleOrNull()?.type in progressionElementTypes && + callee.extensionReceiverParameter == null && + callee.dispatchReceiverParameter?.type in progressionElementTypes && + callee.name.asString() == "rangeUntil" + } + + override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo = + with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { + ProgressionHeaderInfo( + data, + first = expression.dispatchReceiver!!, + last = expression.getValueArgument(0)!!, + step = irInt(1), + canOverflow = false, + isLastInclusive = false, + direction = ProgressionDirection.INCREASING + ) + } +} diff --git a/compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilChar.kt b/compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilChar.kt new file mode 100644 index 00000000000..76e1ed3db43 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/forLoop/forInRangeUntil/forInRangeUntilChar.kt @@ -0,0 +1,37 @@ +// !OPT_IN: kotlin.ExperimentalStdlibApi +// IGNORE_BACKEND: JVM + +// IMPORTANT! +// Please, when your changes cause failures in bytecodeText tests for 'for' loops, +// examine the resulting bytecode shape carefully. +// Range and progression-based loops generated with Kotlin compiler should be +// as close as possible to Java counter loops ('for (int i = a; i < b; ++i) { ... }'). +// Otherwise it may result in performance regression due to missing HotSpot optimizations. +// Run Kotlin compiler benchmarks (https://github.com/Kotlin/kotlin-benchmarks) +// with compiler built from your changes if you are not sure. + +fun test(a: Char, b: Char): String { + var s = "" + for (i in a..