KT-22334 Specialized loops using range such as integer..array.size-1

- Into PrimitiveNumberRangeLiteralRangeValue modifies how bounded
value are created by checking if the high bound range can be modified
to be exclusive instead of inclusive such as the generated code will
be optimized.
- Add black box tests checking that the specialization does not fail
on any kind of arrays.
- Add a bytecode test checking that the code is correctly optimized.

Fix of https://youtrack.jetbrains.com/issue/KT-22334
This commit is contained in:
Mikaël Peltier
2018-02-02 13:48:41 +01:00
committed by Dmitry Petrov
parent 8c8caa4707
commit 8676ca34d7
8 changed files with 236 additions and 3 deletions
@@ -22,18 +22,33 @@ import org.jetbrains.kotlin.codegen.generateCallSingleArgument
import org.jetbrains.kotlin.codegen.range.forLoop.ForInSimpleProgressionLoopGenerator
import org.jetbrains.kotlin.codegen.range.forLoop.ForLoopGenerator
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getFirstArgumentExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getReceiverExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.constants.IntegerValueConstant
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Type
class PrimitiveNumberRangeLiteralRangeValue(
rangeCall: ResolvedCall<out CallableDescriptor>
) : PrimitiveNumberRangeIntrinsicRangeValue(rangeCall),
ReversableRangeValue {
override fun getBoundedValue(codegen: ExpressionCodegen) =
SimpleBoundedValue(codegen, rangeCall)
override fun getBoundedValue(codegen: ExpressionCodegen): SimpleBoundedValue {
if (codegen.canBeSpecializedByExcludingHighBound(rangeCall)) {
val highBound = (rangeCall.getFirstArgumentExpression() as KtBinaryExpression).left
return SimpleBoundedValue(
codegen,
rangeCall,
codegen.generateCallReceiver(rangeCall),
true,
codegen.gen(highBound),
false
)
}
return SimpleBoundedValue(codegen, rangeCall)
}
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression): ForLoopGenerator =
createConstBoundedForInRangeLiteralGenerator(codegen, forExpression)
@@ -71,4 +86,37 @@ class PrimitiveNumberRangeLiteralRangeValue(
-1
)
}
}
private fun ExpressionCodegen.canBeSpecializedByExcludingHighBound(rangeCall: ResolvedCall<out CallableDescriptor>): Boolean {
// Currently only "cst..<array>.size-1" can be specialized to "cst until <array>.size"
return isArraySizeMinusOne(rangeCall.getFirstArgumentExpression()!!)
}
private fun ExpressionCodegen.isArraySizeMinusOne(expression: KtExpression): Boolean {
return when {
expression is KtBinaryExpression -> {
isArraySizeAccess(expression.left!!) &&
expression.getOperationToken() === org.jetbrains.kotlin.lexer.KtTokens.MINUS &&
isConstantOne(expression.right!!)
}
else -> false
}
}
private fun ExpressionCodegen.isConstantOne(expression: KtExpression): Boolean {
val constantValue = getCompileTimeConstant(expression).safeAs<IntegerValueConstant<*>>() ?: return false
return constantValue.value == 1
}
private fun ExpressionCodegen.isArraySizeAccess(expression: KtExpression): Boolean {
return when {
expression is KtDotQualifiedExpression -> {
val selector = expression.selectorExpression
asmType(bindingContext.getType(expression.receiverExpression)!!).sort == Type.ARRAY &&
selector is KtNameReferenceExpression &&
selector.text == "size"
}
else -> false
}
}
@@ -0,0 +1,143 @@
// WITH_RUNTIME
fun checkByteArray():Boolean {
val byteArray = byteArrayOf(1, 2, 3)
var sum = 0
for (i in 0..byteArray.size-1) {
sum += byteArray[i]
}
if (sum != 6) return false
return true
}
fun checkShortArray():Boolean {
val shortArray = shortArrayOf(1, 2, 3)
var sum = 0
for (i in 0..shortArray.size-1) {
sum += shortArray[i]
}
if (sum != 6) return false
return true
}
fun checkCharArray():Boolean {
val charArray = charArrayOf('1', '2', '3')
var sum = ""
for (i in 0..charArray.size-1) {
sum += charArray[i]
}
if (sum != "123") return false
return true
}
fun checkIntArray():Boolean {
val intArray = intArrayOf(1, 2, 3)
var sum = 0
for (i in 0..intArray.size-1) {
sum += intArray[i]
}
if (sum != 6) return false
return true
}
fun checkLongArray():Boolean {
val longArray = longArrayOf(1L, 2L, 3L)
var sum = 0L
for (i in 0..longArray.size-1) {
sum += longArray[i]
}
if (sum != 6L) return false
return true
}
fun checkFloatArray():Boolean {
val floatArray = floatArrayOf(1.1f, 2.2f, 3.3f)
var sum = 0f
for (i in 0..floatArray.size-1) {
sum += floatArray[i]
}
if (sum != (1.1f + 2.2f + 3.3f)) return false
return true
}
fun checkDoubleArray():Boolean {
val doubleArray = doubleArrayOf(1.1, 2.2, 3.3)
var sum = 0.0
for (i in 0..doubleArray.size-1) {
sum += doubleArray[i]
}
if (sum != 6.6) return false
return true
}
fun checkBooleanArray():Boolean {
val booleanArray = booleanArrayOf(false, false, true)
var result = false
for (i in 0..booleanArray.size-1) {
result = booleanArray[i]
}
return result
}
class Value(val value: Int) {}
fun checkObjectArray():Boolean {
val objectArray = arrayOf(Value(1), Value(2), Value(3))
var sum = 0
for (i in 0..objectArray.size-1) {
sum += objectArray[i].value
}
if (sum != 6) return false
return true
}
fun checkWithArrayUpdate():Boolean {
var intArray = intArrayOf(1, 2, 3)
var sum = 0
for (i in 0..intArray.size-1) {
sum += intArray[i]
intArray = intArrayOf(4, 5, 6, 7)
}
if (sum != 12) return false
return true
}
fun checkIntArrayMinusArbitraryConstant():Boolean {
val intArray = intArrayOf(1, 2, 3)
var sum = 0
for (i in 0..intArray.size-2) {
sum += intArray[i]
}
if (sum != 3) return false
return true
}
fun checkReversedIntArray(): Boolean {
val intArray = intArrayOf(1, 2, 3)
var start = 0
var sum = 0
for (i in (start..intArray.size-1).reversed()) {
sum += intArray[i]
}
if (sum != 6) return false
return true
}
fun box() : String {
// Check that the specialization of 'for (i in 0..array.size-1)' to 'for (i in 0 until array.size)' does not fail on
// any kind of arrays.
if (!checkByteArray()) return "Failure"
if (!checkShortArray()) return "Failure"
if (!checkCharArray()) return "Failure"
if (!checkIntArray()) return "Failure"
if (!checkLongArray()) return "Failure"
if (!checkFloatArray()) return "Failure"
if (!checkDoubleArray()) return "Failure"
if (!checkBooleanArray()) return "Failure"
if (!checkObjectArray()) return "Failure"
if (!checkWithArrayUpdate()) return "Failure"
if (!checkIntArrayMinusArbitraryConstant()) return "Failure"
if (!checkReversedIntArray()) return "Failure"
return "OK"
}
@@ -0,0 +1,12 @@
fun test(): Int {
val intArray = intArrayOf(1, 2, 3)
var sum = 0
for (i in 0..intArray.size-1) {
sum += intArray[i]
}
return sum
}
// 1 IF_ICMPGE
// 0 IF_ICMPGT
// 0 IF_ICMPEQ
@@ -5147,6 +5147,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/controlStructures/forInArray"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("forInArraySpecializedToUntil.kt")
public void testForInArraySpecializedToUntil() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/forInArray/forInArraySpecializedToUntil.kt");
doTest(fileName);
}
@TestMetadata("forInArrayWithArrayPropertyUpdatedInLoopBody.kt")
public void testForInArrayWithArrayPropertyUpdatedInLoopBody() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/forInArray/forInArrayWithArrayPropertyUpdatedInLoopBody.kt");
@@ -5147,6 +5147,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/controlStructures/forInArray"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("forInArraySpecializedToUntil.kt")
public void testForInArraySpecializedToUntil() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/forInArray/forInArraySpecializedToUntil.kt");
doTest(fileName);
}
@TestMetadata("forInArrayWithArrayPropertyUpdatedInLoopBody.kt")
public void testForInArrayWithArrayPropertyUpdatedInLoopBody() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/forInArray/forInArrayWithArrayPropertyUpdatedInLoopBody.kt");
@@ -1305,6 +1305,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
doTest(fileName);
}
@TestMetadata("forInRangeSpecializedToUntil.kt")
public void testForInRangeSpecializedToUntil() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/forLoop/forInRangeSpecializedToUntil.kt");
doTest(fileName);
}
@TestMetadata("forInRangeToCharConst.kt")
public void testForInRangeToCharConst() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/forLoop/forInRangeToCharConst.kt");
@@ -5147,6 +5147,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/controlStructures/forInArray"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("forInArraySpecializedToUntil.kt")
public void testForInArraySpecializedToUntil() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/forInArray/forInArraySpecializedToUntil.kt");
doTest(fileName);
}
@TestMetadata("forInArrayWithArrayPropertyUpdatedInLoopBody.kt")
public void testForInArrayWithArrayPropertyUpdatedInLoopBody() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/forInArray/forInArrayWithArrayPropertyUpdatedInLoopBody.kt");
@@ -5717,6 +5717,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/controlStructures/forInArray"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("forInArraySpecializedToUntil.kt")
public void testForInArraySpecializedToUntil() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/forInArray/forInArraySpecializedToUntil.kt");
doTest(fileName);
}
@TestMetadata("forInArrayWithArrayPropertyUpdatedInLoopBody.kt")
public void testForInArrayWithArrayPropertyUpdatedInLoopBody() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/forInArray/forInArrayWithArrayPropertyUpdatedInLoopBody.kt");