Cache array length in for-in-array loop if possible
If the range expression is not a local variable (which can be updated in the loop body affecting loop behavior, see KT-21354), we can cache the array length, thus turning a for-in-array loop into a simple optimizable counter loop. #KT-21321 In Progress
This commit is contained in:
@@ -23,9 +23,9 @@ import org.jetbrains.kotlin.codegen.range.inExpression.InExpressionGenerator
|
|||||||
import org.jetbrains.kotlin.psi.KtForExpression
|
import org.jetbrains.kotlin.psi.KtForExpression
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
|
|
||||||
class ArrayRangeValue : RangeValue {
|
class ArrayRangeValue(private val canCacheArrayLength: Boolean) : RangeValue {
|
||||||
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression) =
|
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression) =
|
||||||
ForInArrayLoopGenerator(codegen, forExpression)
|
ForInArrayLoopGenerator(codegen, forExpression, canCacheArrayLength)
|
||||||
|
|
||||||
override fun createInExpressionGenerator(codegen: ExpressionCodegen, operatorReference: KtSimpleNameExpression): InExpressionGenerator =
|
override fun createInExpressionGenerator(codegen: ExpressionCodegen, operatorReference: KtSimpleNameExpression): InExpressionGenerator =
|
||||||
CallBasedInExpressionGenerator(codegen, operatorReference)
|
CallBasedInExpressionGenerator(codegen, operatorReference)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.range
|
|||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.codegen.*
|
import org.jetbrains.kotlin.codegen.*
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
@@ -44,7 +45,7 @@ fun ExpressionCodegen.createRangeValueForExpression(rangeExpression: KtExpressio
|
|||||||
|
|
||||||
return when {
|
return when {
|
||||||
asmRangeType.sort == Type.ARRAY ->
|
asmRangeType.sort == Type.ARRAY ->
|
||||||
ArrayRangeValue()
|
ArrayRangeValue(!isLocalVarReference(rangeExpression, bindingContext))
|
||||||
isPrimitiveRange(rangeType) ->
|
isPrimitiveRange(rangeType) ->
|
||||||
PrimitiveRangeRangeValue()
|
PrimitiveRangeRangeValue()
|
||||||
isPrimitiveProgression(rangeType) ->
|
isPrimitiveProgression(rangeType) ->
|
||||||
@@ -56,6 +57,12 @@ fun ExpressionCodegen.createRangeValueForExpression(rangeExpression: KtExpressio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isLocalVarReference(rangeExpression: KtExpression, bindingContext: BindingContext): Boolean {
|
||||||
|
if (rangeExpression !is KtSimpleNameExpression) return false
|
||||||
|
val resultingDescriptor = rangeExpression.getResolvedCall(bindingContext)?.resultingDescriptor ?: return false
|
||||||
|
return resultingDescriptor is LocalVariableDescriptor && resultingDescriptor.isVar
|
||||||
|
}
|
||||||
|
|
||||||
private fun isSubtypeOfCharSequence(type: KotlinType, builtIns: KotlinBuiltIns) =
|
private fun isSubtypeOfCharSequence(type: KotlinType, builtIns: KotlinBuiltIns) =
|
||||||
KotlinTypeChecker.DEFAULT.isSubtypeOf(type, builtIns.getBuiltInClassByName(Name.identifier("CharSequence")).defaultType)
|
KotlinTypeChecker.DEFAULT.isSubtypeOf(type, builtIns.getBuiltInClassByName(Name.identifier("CharSequence")).defaultType)
|
||||||
|
|
||||||
|
|||||||
+26
-10
@@ -17,21 +17,25 @@
|
|||||||
package org.jetbrains.kotlin.codegen.range.forLoop
|
package org.jetbrains.kotlin.codegen.range.forLoop
|
||||||
|
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
|
import org.jetbrains.kotlin.codegen.AsmUtil.boxType
|
||||||
|
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||||
|
import org.jetbrains.kotlin.codegen.StackValue
|
||||||
import org.jetbrains.kotlin.psi.KtForExpression
|
import org.jetbrains.kotlin.psi.KtForExpression
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.org.objectweb.asm.Label
|
import org.jetbrains.org.objectweb.asm.Label
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
|
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil.boxType
|
class ForInArrayLoopGenerator(
|
||||||
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
codegen: ExpressionCodegen,
|
||||||
import org.jetbrains.kotlin.codegen.StackValue
|
forExpression: KtForExpression,
|
||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
|
// We can cache array length if the corresponding range expression is not a local var.
|
||||||
|
// See https://youtrack.jetbrains.com/issue/KT-21354.
|
||||||
class ForInArrayLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression)
|
private val canCacheArrayLength: Boolean
|
||||||
: AbstractForLoopGenerator(codegen, forExpression)
|
) : AbstractForLoopGenerator(codegen, forExpression) {
|
||||||
{
|
|
||||||
private var indexVar: Int = 0
|
private var indexVar: Int = 0
|
||||||
private var arrayVar: Int = 0
|
private var arrayVar: Int = 0
|
||||||
|
private var arrayLengthVar: Int = 0
|
||||||
private val loopRangeType: KotlinType = bindingContext.getType(forExpression.loopRange!!)!!
|
private val loopRangeType: KotlinType = bindingContext.getType(forExpression.loopRange!!)!!
|
||||||
|
|
||||||
override fun beforeLoop() {
|
override fun beforeLoop() {
|
||||||
@@ -51,6 +55,13 @@ class ForInArrayLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForEx
|
|||||||
v.store(arrayVar, OBJECT_TYPE)
|
v.store(arrayVar, OBJECT_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canCacheArrayLength) {
|
||||||
|
arrayLengthVar = createLoopTempVariable(Type.INT_TYPE)
|
||||||
|
v.load(arrayVar, OBJECT_TYPE)
|
||||||
|
v.arraylength()
|
||||||
|
v.store(arrayLengthVar, Type.INT_TYPE)
|
||||||
|
}
|
||||||
|
|
||||||
v.iconst(0)
|
v.iconst(0)
|
||||||
v.store(indexVar, Type.INT_TYPE)
|
v.store(indexVar, Type.INT_TYPE)
|
||||||
}
|
}
|
||||||
@@ -59,8 +70,13 @@ class ForInArrayLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForEx
|
|||||||
|
|
||||||
override fun checkPreCondition(loopExit: Label) {
|
override fun checkPreCondition(loopExit: Label) {
|
||||||
v.load(indexVar, Type.INT_TYPE)
|
v.load(indexVar, Type.INT_TYPE)
|
||||||
v.load(arrayVar, OBJECT_TYPE)
|
if (canCacheArrayLength) {
|
||||||
v.arraylength()
|
v.load(arrayLengthVar, Type.INT_TYPE)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
v.load(arrayVar, OBJECT_TYPE)
|
||||||
|
v.arraylength()
|
||||||
|
}
|
||||||
v.ificmpge(loopExit)
|
v.ificmpge(loopExit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ fun box() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// METHOD : UnderscoreNamesKt$box$1.invoke(LA;Ljava/lang/String;I)Ljava/lang/String;
|
// METHOD : UnderscoreNamesKt$box$1.invoke(LA;Ljava/lang/String;I)Ljava/lang/String;
|
||||||
// VARIABLE : NAME=q TYPE=Ljava/lang/String; INDEX=15
|
// VARIABLE : NAME=q TYPE=Ljava/lang/String; INDEX=16
|
||||||
// VARIABLE : NAME=d TYPE=C INDEX=11
|
// VARIABLE : NAME=d TYPE=C INDEX=11
|
||||||
// VARIABLE : NAME=_ TYPE=Ljava/lang/String; INDEX=10
|
// VARIABLE : NAME=_ TYPE=Ljava/lang/String; INDEX=10
|
||||||
// VARIABLE : NAME=c TYPE=C INDEX=9
|
// VARIABLE : NAME=c TYPE=C INDEX=9
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
var xs = intArrayOf(1, 2, 3)
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
var sum = 0
|
||||||
|
for (x in xs) {
|
||||||
|
sum = sum * 10 + x
|
||||||
|
xs = IntArray(0)
|
||||||
|
}
|
||||||
|
return if (sum == 123) "OK" else "Fail: $sum"
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IGNORE_BACKEND: JS
|
||||||
|
|
||||||
|
// In Kotlin 1.0, in a for-in-array loop, range expression is cached in
|
||||||
|
// a local variable unless it is already a local variable.
|
||||||
|
// This caused the following quirky behavior:
|
||||||
|
// if an array variable is updated in the loop body, it affects the loop
|
||||||
|
// execution (see https://youtrack.jetbrains.com/issue/KT-21354).
|
||||||
|
// Most likely it is a bug, however, fixing it right now is a breaking
|
||||||
|
// change requiring a proper deprecation loop.
|
||||||
|
// When the design decision is made, it might be required to update this
|
||||||
|
// test (e.g., by adding a proper LANGUAGE_VERSION directive).
|
||||||
|
// Note that JS back-end handles this case "correctly".
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
var xs = intArrayOf(1, 2, 3)
|
||||||
|
var sum = 0
|
||||||
|
for (x in xs) {
|
||||||
|
sum = sum * 10 + x
|
||||||
|
xs = intArrayOf(4, 5)
|
||||||
|
}
|
||||||
|
return if (sum == 15) "OK" else "Fail: $sum"
|
||||||
|
}
|
||||||
+12
@@ -14537,6 +14537,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInArrayWithArrayPropertyUpdatedInLoopBody.kt")
|
||||||
|
public void testForInArrayWithArrayPropertyUpdatedInLoopBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayPropertyUpdatedInLoopBody.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
||||||
|
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
@@ -14537,6 +14537,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInArrayWithArrayPropertyUpdatedInLoopBody.kt")
|
||||||
|
public void testForInArrayWithArrayPropertyUpdatedInLoopBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayPropertyUpdatedInLoopBody.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
||||||
|
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
@@ -14537,6 +14537,18 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInArrayWithArrayPropertyUpdatedInLoopBody.kt")
|
||||||
|
public void testForInArrayWithArrayPropertyUpdatedInLoopBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayPropertyUpdatedInLoopBody.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
||||||
|
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
+19
-1
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY test*/
|
||||||
@SuppressWarnings("all")
|
@SuppressWarnings("all")
|
||||||
@TestMetadata("compiler/testData/codegen/box")
|
@TestMetadata("compiler/testData/codegen/box")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@@ -15803,6 +15803,24 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInArrayWithArrayPropertyUpdatedInLoopBody.kt")
|
||||||
|
public void testForInArrayWithArrayPropertyUpdatedInLoopBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayPropertyUpdatedInLoopBody.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInArrayWithArrayVarUpdatedInLoopBody.kt")
|
||||||
|
public void testForInArrayWithArrayVarUpdatedInLoopBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInArrayWithArrayVarUpdatedInLoopBody.kt");
|
||||||
|
try {
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
catch (Throwable ignore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user