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:
Dmitry Petrov
2017-11-24 17:46:54 +03:00
parent 328c67b9e8
commit e2fa613b70
10 changed files with 127 additions and 15 deletions
@@ -23,9 +23,9 @@ import org.jetbrains.kotlin.codegen.range.inExpression.InExpressionGenerator
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
class ArrayRangeValue : RangeValue {
class ArrayRangeValue(private val canCacheArrayLength: Boolean) : RangeValue {
override fun createForLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression) =
ForInArrayLoopGenerator(codegen, forExpression)
ForInArrayLoopGenerator(codegen, forExpression, canCacheArrayLength)
override fun createInExpressionGenerator(codegen: ExpressionCodegen, operatorReference: KtSimpleNameExpression): InExpressionGenerator =
CallBasedInExpressionGenerator(codegen, operatorReference)
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.range
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
@@ -44,7 +45,7 @@ fun ExpressionCodegen.createRangeValueForExpression(rangeExpression: KtExpressio
return when {
asmRangeType.sort == Type.ARRAY ->
ArrayRangeValue()
ArrayRangeValue(!isLocalVarReference(rangeExpression, bindingContext))
isPrimitiveRange(rangeType) ->
PrimitiveRangeRangeValue()
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) =
KotlinTypeChecker.DEFAULT.isSubtypeOf(type, builtIns.getBuiltInClassByName(Name.identifier("CharSequence")).defaultType)
@@ -17,21 +17,25 @@
package org.jetbrains.kotlin.codegen.range.forLoop
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.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.kotlin.codegen.AsmUtil.boxType
import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
class ForInArrayLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForExpression)
: AbstractForLoopGenerator(codegen, forExpression)
{
class ForInArrayLoopGenerator(
codegen: ExpressionCodegen,
forExpression: KtForExpression,
// We can cache array length if the corresponding range expression is not a local var.
// See https://youtrack.jetbrains.com/issue/KT-21354.
private val canCacheArrayLength: Boolean
) : AbstractForLoopGenerator(codegen, forExpression) {
private var indexVar: Int = 0
private var arrayVar: Int = 0
private var arrayLengthVar: Int = 0
private val loopRangeType: KotlinType = bindingContext.getType(forExpression.loopRange!!)!!
override fun beforeLoop() {
@@ -51,6 +55,13 @@ class ForInArrayLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForEx
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.store(indexVar, Type.INT_TYPE)
}
@@ -59,8 +70,13 @@ class ForInArrayLoopGenerator(codegen: ExpressionCodegen, forExpression: KtForEx
override fun checkPreCondition(loopExit: Label) {
v.load(indexVar, Type.INT_TYPE)
v.load(arrayVar, OBJECT_TYPE)
v.arraylength()
if (canCacheArrayLength) {
v.load(arrayLengthVar, Type.INT_TYPE)
}
else {
v.load(arrayVar, OBJECT_TYPE)
v.arraylength()
}
v.ificmpge(loopExit)
}