Make all progression headers inclusive, and decrement last for

last-exclusive progressions (i.e., "until" progressions and loop over
array indices).

This change makes it possible to correctly implement the handling of
"step" progressions. Computing the last element of a stepped progression
requires that the last is inclusive.

Also invert the while loop (into if + do-while) that is used when
lowering for-loops over progressions that cannot overflow. This keeps
the performance characteristics closer to the ForLoopsLowering in
kotlin-native, since the goal is to converge to this shared version.

Also used IrType instead of KotlinType, where possible.

 https://github.com/JetBrains/kotlin/pull/2390
 https://github.com/JetBrains/kotlin/pull/2305
This commit is contained in:
Mark Punzalan
2019-06-14 00:59:47 -07:00
committed by Mikhael Bogdanov
parent 39f6416757
commit de1e27c584
75 changed files with 1873 additions and 377 deletions
@@ -44,22 +44,24 @@ val forLoopsPhase = makeIrFilePhase(
* }
* ```
* We transform it into one of the following loops:
*
* ```
* // 1. If the induction variable cannot overflow, i.e., `B` is const and != MAX_VALUE (if increasing, or MIN_VALUE if decreasing).
*
* var inductionVar = A
* var last = B
* while (inductionVar <= last) { // (`inductionVar >= last` if the progression is decreasing)
* val loopVar = inductionVar
* inductionVar++ // (`inductionVar--` if the progression is decreasing)
* // Loop body
* val last = B
* if (inductionVar <= last) { // (`inductionVar >= last` if the progression is decreasing)
* // Loop is not empty
* do {
* val loopVar = inductionVar
* inductionVar++ // (`inductionVar--` if the progression is decreasing)
* // Loop body
* } while (inductionVar <= last)
* }
*
* // 2. If the induction variable CAN overflow, i.e., `last` is not const or is MAX/MIN_VALUE:
*
* var inductionVar = A
* var last = B
* val last = B
* if (inductionVar <= last) { // (`inductionVar >= last` if the progression is decreasing)
* // Loop is not empty
* do {
@@ -68,15 +70,18 @@ val forLoopsPhase = makeIrFilePhase(
* // Loop body
* } while (loopVar != last)
* }
*
* // 3. If loop is an until loop (e.g., `for (i in A until B)`), it cannot overflow and we use `<` for comparisons:
*
* ```
* If loop is an until loop (e.g., `for (i in A until B)`), it is transformed into:
* ```
* var inductionVar = A
* var last = B
* while (inductionVar < last) {
* val loopVar = inductionVar
* inductionVar++
* // Loop body
* val last = B - 1
* if (inductionVar <= last && B != MIN_VALUE) {
* // Loop is not empty
* do {
* val loopVar = inductionVar
* inductionVar++
* // Loop body
* } while (inductionVar <= last)
* }
* ```
* In case of iteration over an array (e.g., `for (i in array)`), we transform it into the following:
@@ -221,7 +226,7 @@ private class RangeLoopTransformer(
// inductionVariable = inductionVariable + step
return with(context.createIrBuilder(getScopeOwnerSymbol(), initializer.startOffset, initializer.endOffset)) {
variable.initializer = forLoopInfo.initializeLoopVariable(symbols, this)
val increment = forLoopInfo.buildIncrementInductionVariableExpression(this)
val increment = forLoopInfo.incrementInductionVariable(this)
IrCompositeImpl(
variable.startOffset,
variable.endOffset,
@@ -12,16 +12,10 @@ import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstKind
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
@@ -31,7 +25,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
// TODO: Handle UIntProgression, ULongProgression
/** Represents a progression type in the Kotlin stdlib. */
enum class ProgressionType(val elementCastFunctionName: Name, val stepCastFunctionName: Name) {
internal enum class ProgressionType(val elementCastFunctionName: Name, val stepCastFunctionName: Name) {
INT_PROGRESSION(Name.identifier("toInt"), Name.identifier("toInt")),
LONG_PROGRESSION(Name.identifier("toLong"), Name.identifier("toLong")),
CHAR_PROGRESSION(Name.identifier("toChar"), Name.identifier("toInt"));
@@ -59,7 +53,19 @@ enum class ProgressionType(val elementCastFunctionName: Name, val stepCastFuncti
}
}
internal enum class ProgressionDirection { DECREASING, INCREASING, UNKNOWN }
internal enum class ProgressionDirection {
DECREASING {
override fun asReversed() = INCREASING
},
INCREASING {
override fun asReversed() = DECREASING
},
UNKNOWN {
override fun asReversed() = UNKNOWN
};
abstract fun asReversed(): ProgressionDirection
}
/** Information about a loop that is required by [HeaderProcessor] to build a [ForLoopHeader]. */
internal sealed class HeaderInfo(
@@ -67,25 +73,13 @@ internal sealed class HeaderInfo(
val first: IrExpression,
val last: IrExpression,
val step: IrExpression,
val isFirstInclusive: Boolean,
val isLastInclusive: Boolean,
val isReversed: Boolean
val isReversed: Boolean,
val direction: ProgressionDirection,
val additionalNotEmptyCondition: IrExpression?
) {
val direction: ProgressionDirection by lazy {
// If step is a constant (either Int or Long), then we can determine the direction.
val stepValue = (step as? IrConst<*>)?.value as? Number
val stepValueAsLong = stepValue?.toLong()
when {
stepValueAsLong == null -> ProgressionDirection.UNKNOWN
stepValueAsLong < 0L -> ProgressionDirection.DECREASING
stepValueAsLong > 0L -> ProgressionDirection.INCREASING
else -> ProgressionDirection.UNKNOWN
}
}
/**
* Returns a copy of this [HeaderInfo] with the values reversed.
* I.e., first and last (and their inclusiveness) are swapped, step is negated.
* I.e., first and last are swapped, step is negated.
* Returns null if the iterable cannot be iterated in reverse.
*/
abstract fun asReversed(): HeaderInfo?
@@ -97,23 +91,18 @@ internal class ProgressionHeaderInfo(
first: IrExpression,
last: IrExpression,
step: IrExpression,
isFirstInclusive: Boolean = true,
isLastInclusive: Boolean = true,
isReversed: Boolean = false,
canOverflow: Boolean? = null,
direction: ProgressionDirection,
additionalNotEmptyCondition: IrExpression? = null,
val additionalVariables: List<IrVariable> = listOf()
) : HeaderInfo(progressionType, first, last, step, isFirstInclusive, isLastInclusive, isReversed) {
) : HeaderInfo(progressionType, first, last, step, isReversed, direction, additionalNotEmptyCondition) {
val canOverflow: Boolean by lazy {
// Last-exclusive progressions can never overflow.
if (!isLastInclusive) return@lazy false
if (canOverflow != null) return@lazy canOverflow
// Induction variable can overflow if it is not a const, or is MAX/MIN_VALUE (depending on direction).
val lastValue = (last as? IrConst<*>)?.value
val lastValueAsLong = when (lastValue) {
is Number -> lastValue.toLong()
is Char -> lastValue.toLong()
else -> return@lazy true // If "last" is not a const Number or Char.
}
val lastValueAsLong = last.constLongValue ?: return@lazy true // If "last" is not a const Number or Char.
val constLimitAsLong = when (direction) {
ProgressionDirection.UNKNOWN ->
// If we don't know the direction, we can't be sure which limit to use.
@@ -139,9 +128,9 @@ internal class ProgressionHeaderInfo(
first = last,
last = first,
step = step.negate(),
isFirstInclusive = isLastInclusive,
isLastInclusive = isFirstInclusive,
isReversed = !isReversed,
direction = direction.asReversed(),
additionalNotEmptyCondition = additionalNotEmptyCondition,
additionalVariables = additionalVariables
)
}
@@ -157,9 +146,9 @@ internal class ArrayHeaderInfo(
first,
last,
step,
isFirstInclusive = true,
isLastInclusive = false,
isReversed = false
isReversed = false,
direction = ProgressionDirection.INCREASING,
additionalNotEmptyCondition = null
) {
// Technically one can easily iterate over an array in reverse by swapping first/last and
// negating the step. However, Array.reversed() and Array.reversedArray() return a collection
@@ -177,21 +166,6 @@ internal class ArrayHeaderInfo(
override fun asReversed(): HeaderInfo? = null
}
/** Return the negated value if the expression is const, otherwise call unaryMinus(). */
private fun IrExpression.negate(): IrExpression {
val stepValue = (this as? IrConst<*>)?.value as? Number
return when (stepValue) {
is Int -> IrConstImpl(startOffset, endOffset, type, IrConstKind.Int, -stepValue)
is Long -> IrConstImpl(startOffset, endOffset, type, IrConstKind.Long, -stepValue)
else -> {
val unaryMinusFun = type.getClass()!!.functions.first { it.name.asString() == "unaryMinus" }
IrCallImpl(startOffset, endOffset, type, unaryMinusFun.symbol, unaryMinusFun.descriptor).apply {
dispatchReceiver = this@negate
}
}
}
}
/** Matches an iterable expression and builds a [HeaderInfo] from the expression. */
internal interface HeaderInfoHandler<E : IrExpression, D> {
/** Returns true if the handler can build a [HeaderInfo] from the expression. */
@@ -227,7 +201,13 @@ internal class HeaderInfoBuilder(context: CommonBackendContext, private val scop
private val symbols = context.ir.symbols
// TODO: Include unsigned types
private val progressionElementTypes = symbols.integerClassesTypes + context.irBuiltIns.char
private val progressionElementTypes = listOf(
context.irBuiltIns.byteType,
context.irBuiltIns.shortType,
context.irBuiltIns.intType,
context.irBuiltIns.longType,
context.irBuiltIns.charType
)
private val progressionHandlers = listOf(
IndicesHandler(context),
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irComposite
import org.jetbrains.kotlin.backend.common.lower.irIfThen
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
@@ -18,13 +17,11 @@ import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrDoWhileLoopImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrWhileLoopImpl
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.name.Name
/**
* Contains the loop and expression to replace the old loop.
@@ -44,7 +41,8 @@ internal sealed class ForLoopHeader(
val inductionVariable: IrVariable,
val last: IrVariable,
val step: IrVariable,
var loopVariable: IrVariable? = null
var loopVariable: IrVariable? = null,
val isLastInclusive: Boolean
) {
/** Expression used to initialize the loop variable at the beginning of the loop. */
abstract fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder): IrExpression
@@ -55,17 +53,34 @@ internal sealed class ForLoopHeader(
/** Builds a new loop from the old loop. */
abstract fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement
/** Statement used to increment the induction variable. */
fun incrementInductionVariable(builder: DeclarationIrBuilder): IrStatement = with(builder) {
// inductionVariable = inductionVariable + step
val plusFun = inductionVariable.type.getClass()!!.functions.first {
it.name.asString() == "plus" &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type == step.type
}
irSetVar(
inductionVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(inductionVariable),
irGet(step)
)
)
}
protected fun buildLoopCondition(builder: DeclarationIrBuilder): IrExpression =
with(builder) {
val builtIns = context.irBuiltIns
val progressionType = headerInfo.progressionType
val progressionKotlinType = progressionType.elementType(builtIns).toKotlinType()
val compFun =
if (headerInfo.isLastInclusive) builtIns.lessOrEqualFunByOperandType[progressionKotlinType]!!
if (isLastInclusive) builtIns.lessOrEqualFunByOperandType[progressionKotlinType]!!
else builtIns.lessFunByOperandType[progressionKotlinType]!!
// The default condition depends on the direction.
return when (headerInfo.direction) {
when (headerInfo.direction) {
ProgressionDirection.DECREASING ->
// last <= inductionVar (use `<` if last is exclusive)
irCall(compFun).apply {
@@ -114,7 +129,7 @@ internal class ProgressionLoopHeader(
inductionVariable: IrVariable,
last: IrVariable,
step: IrVariable
) : ForLoopHeader(headerInfo, inductionVariable, last, step) {
) : ForLoopHeader(headerInfo, inductionVariable, last, step, isLastInclusive = true) {
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
// loopVariable = inductionVariable
@@ -135,9 +150,9 @@ internal class ProgressionLoopHeader(
(if (headerInfo.isReversed) listOf(last, inductionVariable) else listOf(inductionVariable, last)) +
step
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement {
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
with(builder) {
var (newLoop, replacementExpression) = if (headerInfo.canOverflow) {
val newLoop = if (headerInfo.canOverflow) {
// If the induction variable CAN overflow, we cannot use it in the loop condition. Loop is lowered into something like:
//
// if (inductionVar <= last) {
@@ -148,46 +163,37 @@ internal class ProgressionLoopHeader(
// // Loop body
// } while (loopVar != last)
// }
assert(loopVariable != null)
val booleanNotFun = context.irBuiltIns.booleanClass.functions.first { it.owner.name.asString() == "not" }
val newCondition = irCallOp(booleanNotFun, booleanNotFun.owner.returnType, irCall(context.irBuiltIns.eqeqSymbol).apply {
putValueArgument(0, irGet(loopVariable!!))
putValueArgument(1, irGet(last))
})
val newLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
label = oldLoop.label
condition = newCondition
condition = irNotEquals(irGet(loopVariable!!), irGet(last))
body = newBody
}
val notEmptyCheck = irIfThen(buildLoopCondition(builder), newLoop)
LoopReplacement(newLoop, notEmptyCheck)
} else {
// If the induction variable can NOT overflow, use a simple while loop. Loop is lowered into something like:
// If the induction variable can NOT overflow, use a do-while loop. Loop is lowered into something like:
//
// while (inductionVar <= last) {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// if (inductionVar <= last) {
// do {
// val loopVar = inductionVar
// inductionVar += step
// // Loop body
// } while (inductionVar <= last)
// }
val newLoop = IrWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
//
// Even though this can be simplified into a simpler while loop, using if + do-while (i.e., doing a loop inversion)
// performs better in benchmarks. In cases where `last` is a constant, the `if` may be optimized away.
IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
label = oldLoop.label
condition = buildLoopCondition(this@with)
body = newBody
}
LoopReplacement(newLoop, newLoop)
}
if (!headerInfo.isFirstInclusive) {
// Pre-increment the induction variable.
replacementExpression = irComposite(replacementExpression) {
+buildIncrementInductionVariableExpression(this@with)
+replacementExpression
}
}
return LoopReplacement(newLoop, replacementExpression)
val loopCondition = buildLoopCondition(this@with)
// Combine with the additional "not empty" condition, if any.
val notEmptyCheck =
irIfThen(headerInfo.additionalNotEmptyCondition?.let { context.andand(it, loopCondition) } ?: loopCondition, newLoop)
LoopReplacement(newLoop, notEmptyCheck)
}
}
}
internal class ArrayLoopHeader(
@@ -195,7 +201,7 @@ internal class ArrayLoopHeader(
inductionVariable: IrVariable,
last: IrVariable,
step: IrVariable
) : ForLoopHeader(headerInfo, inductionVariable, last, step) {
) : ForLoopHeader(headerInfo, inductionVariable, last, step, isLastInclusive = false) {
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
// inductionVar = loopVar[inductionVariable]
@@ -224,14 +230,7 @@ internal class ArrayLoopHeader(
condition = buildLoopCondition(this@with)
body = newBody
}
val replacementExpression = if (!headerInfo.isFirstInclusive) {
// Pre-increment the induction variable.
irComposite(newLoop) {
+buildIncrementInductionVariableExpression(this@with)
+newLoop
}
} else newLoop
LoopReplacement(newLoop, replacementExpression)
LoopReplacement(newLoop, newLoop)
}
}
@@ -340,30 +339,4 @@ internal class HeaderProcessor(
} else {
expression
}
private fun IrExpression.castIfNecessary(targetType: IrType, numberCastFunctionName: Name): IrExpression {
return if (type.toKotlinType() == targetType.toKotlinType()) {
this
} else {
val function = type.getClass()!!.functions.first { it.name == numberCastFunctionName }
IrCallImpl(startOffset, endOffset, function.returnType, function.symbol)
.apply { dispatchReceiver = this@castIfNecessary }
}
}
}
internal fun ForLoopHeader.buildIncrementInductionVariableExpression(builder: DeclarationIrBuilder): IrExpression = with(builder) {
// inductionVariable = inductionVariable + step
val plusFun = inductionVariable.type.getClass()!!.functions.first {
it.name.asString() == "plus" &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type.toKotlinType() == step.type.toKotlinType()
}
irSetVar(
inductionVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(inductionVariable),
irGet(step)
)
)
}
@@ -11,31 +11,31 @@ import org.jetbrains.kotlin.backend.common.lower.matchers.Quantifier
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher
import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.isArray
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
import org.jetbrains.kotlin.ir.util.properties
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.SimpleType
/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */
internal class RangeToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<SimpleType>) :
internal class RangeToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
ProgressionHandler {
override val matcher = SimpleCalleeMatcher {
dispatchReceiver { it != null && it.type.toKotlinType() in progressionElementTypes }
dispatchReceiver { it != null && it.type in progressionElementTypes }
fqName { it.pathSegments().last() == Name.identifier("rangeTo") }
parameterCount { it == 1 }
parameter(0) { it.type.toKotlinType() in progressionElementTypes }
parameter(0) { it.type in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol) =
@@ -44,19 +44,20 @@ internal class RangeToHandler(private val context: CommonBackendContext, private
data,
first = expression.dispatchReceiver!!,
last = expression.getValueArgument(0)!!,
step = irInt(1)
step = irInt(1),
direction = ProgressionDirection.INCREASING
)
}
}
/** Builds a [HeaderInfo] for progressions built using the `downTo` extension function. */
internal class DownToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<SimpleType>) :
internal class DownToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
ProgressionHandler {
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(FqName("kotlin.ranges.downTo"), progressionElementTypes)
parameterCount { it == 1 }
parameter(0) { it.type.toKotlinType() in progressionElementTypes }
parameter(0) { it.type in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
@@ -65,64 +66,193 @@ internal class DownToHandler(private val context: CommonBackendContext, private
data,
first = expression.extensionReceiver!!,
last = expression.getValueArgument(0)!!,
step = irInt(-1)
step = irInt(-1),
direction = ProgressionDirection.DECREASING
)
}
}
/** Builds a [HeaderInfo] for progressions built using the `until` extension function. */
internal class UntilHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<SimpleType>) :
internal class UntilHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
ProgressionHandler {
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(FqName("kotlin.ranges.until"), progressionElementTypes)
parameterCount { it == 1 }
parameter(0) { it.type.toKotlinType() in progressionElementTypes }
parameter(0) { it.type in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// `A until B` is essentially the same as `A .. (B-1)`. However, B could be MIN_VALUE and hence `(B-1)` could underflow.
// If B is MIN_VALUE, then `A until B` is an empty range. We handle this special case be adding an additional "not empty"
// condition in the lowered for-loop. Therefore the following for-loop:
//
// for (i in A until B) { // Loop body }
//
// is lowered into:
//
// var inductionVar = A
// val last = B - 1
// if (inductionVar <= last && B != MIN_VALUE) {
// // Loop is not empty
// do {
// val loopVar = inductionVar
// inductionVar++
// // Loop body
// } while (inductionVar <= last)
// }
//
// However, `B` may be an expression with side-effects that should only be evaluated once, and `A` may also have side-effects.
// They are evaluated once and in the correct order (`A` then `B`), the final lowered form is:
//
// // Additional variables
// val untilReceiverValue = A
// val untilArg = B
// // Standard form of loop over progression
// var inductionVar = untilReceiverValue
// val last = untilArg - 1
// if (inductionVar <= last && untilFunArg != MIN_VALUE) {
// // Loop is not empty
// do {
// val loopVar = inductionVar
// inductionVar++
// // Loop body
// } while (inductionVar <= last)
// }
val receiverValue = expression.extensionReceiver!!
val untilArg = expression.getValueArgument(0)!!
// Ensure that the argument conforms to the progression type before we decrement.
val untilArgCasted = untilArg.castIfNecessary(
data.elementType(context.irBuiltIns),
data.elementCastFunctionName
)
// To reduce local variable usage, we create and use temporary variables only if necessary.
var receiverValueVar: IrVariable? = null
var untilArgVar: IrVariable? = null
var additionalVariables = emptyList<IrVariable>()
if (untilArg.canHaveSideEffects) {
if (receiverValue.canHaveSideEffects) {
receiverValueVar = scope.createTemporaryVariable(receiverValue, nameHint = "untilReceiverValue")
}
untilArgVar = scope.createTemporaryVariable(untilArgCasted, nameHint = "untilArg")
additionalVariables = listOfNotNull(receiverValueVar, untilArgVar)
}
val first = if (receiverValueVar == null) receiverValue else irGet(receiverValueVar)
val untilArgExpression = if (untilArgVar == null) untilArgCasted else irGet(untilArgVar)
val last = untilArgExpression.decrement()
val (minValueAsLong, minValueIrConst) =
when (data) {
ProgressionType.INT_PROGRESSION -> Pair(Int.MIN_VALUE.toLong(), irInt(Int.MIN_VALUE))
ProgressionType.CHAR_PROGRESSION -> Pair(Char.MIN_VALUE.toLong(), irChar(Char.MIN_VALUE))
ProgressionType.LONG_PROGRESSION -> Pair(Long.MIN_VALUE, irLong(Long.MIN_VALUE))
}
val additionalNotEmptyCondition = untilArg.constLongValue.let {
when {
it == null && isAdditionalNotEmptyConditionNeeded(receiverValue.type, untilArg.type) ->
// Condition is needed and untilArg is non-const.
// Build the additional "not empty" condition: `untilArg != MIN_VALUE`.
irNotEquals(untilArgExpression, minValueIrConst)
it == minValueAsLong ->
// Hardcode "false" as additional condition so that the progression is considered empty.
// The entire lowered loop becomes a candidate for dead code elimination, depending on backend.
irFalse()
else ->
// We know that untilArg != MIN_VALUE, so the additional condition is not necessary.
null
}
}
ProgressionHeaderInfo(
data,
first = expression.extensionReceiver!!,
last = expression.getValueArgument(0)!!,
first = first,
last = last,
step = irInt(1),
isLastInclusive = false
canOverflow = false,
additionalVariables = additionalVariables,
additionalNotEmptyCondition = additionalNotEmptyCondition,
direction = ProgressionDirection.INCREASING
)
}
private fun isAdditionalNotEmptyConditionNeeded(receiverType: IrType, argType: IrType): Boolean {
// Here are the available `until` extension functions:
//
// infix fun Char.until(to: Char): CharRange
// infix fun Byte.until(to: Byte): IntRange
// infix fun Byte.until(to: Short): IntRange
// infix fun Byte.until(to: Int): IntRange
// infix fun Byte.until(to: Long): LongRange
// infix fun Short.until(to: Byte): IntRange
// infix fun Short.until(to: Short): IntRange
// infix fun Short.until(to: Int): IntRange
// infix fun Short.until(to: Long): LongRange
// infix fun Int.until(to: Byte): IntRange
// infix fun Int.until(to: Short): IntRange
// infix fun Int.until(to: Int): IntRange
// infix fun Int.until(to: Long): LongRange
// infix fun Long.until(to: Byte): LongRange
// infix fun Long.until(to: Short): LongRange
// infix fun Long.until(to: Int): LongRange
// infix fun Long.until(to: Long): LongRange
//
// 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.
// TODO: Include unsigned types
return with(context.irBuiltIns) {
when (receiverType) {
charType -> true
byteType, shortType, intType -> when (argType) {
byteType, shortType -> false
else -> true
}
longType -> when (argType) {
byteType, shortType, intType -> false
else -> true
}
else -> true
}
}
}
}
/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */
internal class IndicesHandler(val context: CommonBackendContext) : ProgressionHandler {
internal class IndicesHandler(private val context: CommonBackendContext) : ProgressionHandler {
override val matcher = SimpleCalleeMatcher {
// TODO: Handle Collection<*>.indices
// TODO: Handle CharSequence.indices
extensionReceiver { it != null && KotlinBuiltIns.isArrayOrPrimitiveArray(it.type.toKotlinType()) }
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() } }
fqName { it == FqName("kotlin.collections.<get-indices>") }
parameterCount { it == 0 }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// `last = array.size` for the loop `for (i in array.indices)`.
// `last = array.size - 1` (last is inclusive) for the loop `for (i in array.indices)`.
val arraySizeProperty = expression.extensionReceiver!!.type.getClass()!!.properties.first { it.name.asString() == "size" }
val last = irCall(arraySizeProperty.getter!!).apply {
dispatchReceiver = expression.extensionReceiver
}
}.decrement()
ProgressionHeaderInfo(
data,
first = irInt(0),
last = last,
step = irInt(1),
isLastInclusive = false
canOverflow = false,
direction = ProgressionDirection.INCREASING
)
}
}
/** Builds a [HeaderInfo] for calls to reverse an iterable. */
internal class ReversedHandler(context: CommonBackendContext, val visitor: IrElementVisitor<HeaderInfo?, Nothing?>) :
internal class ReversedHandler(context: CommonBackendContext, private val visitor: IrElementVisitor<HeaderInfo?, Nothing?>) :
HeaderInfoFromCallHandler<Nothing?> {
private val symbols = context.ir.symbols
@@ -154,7 +284,7 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// Directly use the `first/last/step` properties of the progression.
val progression = scope.createTemporaryVariable(expression)
val progression = scope.createTemporaryVariable(expression, nameHint = "progression")
val progressionClass = progression.type.getClass()!!
val firstProperty = progressionClass.properties.first { it.name.asString() == "first" }
val first = irCall(firstProperty.getter!!).apply {
@@ -174,7 +304,8 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte
first,
last,
step,
additionalVariables = listOf(progression)
additionalVariables = listOf(progression),
direction = ProgressionDirection.UNKNOWN
)
}
}
@@ -182,7 +313,7 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte
/** Builds a [HeaderInfo] for arrays. */
internal class ArrayIterationHandler(private val context: CommonBackendContext) : ExpressionHandler {
override fun match(expression: IrExpression) = KotlinBuiltIns.isArrayOrPrimitiveArray(expression.type.toKotlinType())
override fun match(expression: IrExpression) = expression.type.run { isArray() || isPrimitiveArray() }
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
@@ -206,7 +337,7 @@ internal class ArrayIterationHandler(private val context: CommonBackendContext)
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
)
// `last = array.size` for the loop `for (i in array.indices)`.
// `last = array.size` (last is exclusive) for the loop `for (i in array.indices)`.
val arraySizeProperty = arrayReference.type.getClass()!!.properties.first { it.name.asString() == "size" }
val last = irCall(arraySizeProperty.getter!!).apply {
dispatchReceiver = irGet(arrayReference)
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstKind
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
internal fun IrExpression.castIfNecessary(targetType: IrType, numberCastFunctionName: Name): IrExpression {
return if (type == targetType) {
this
} else {
val function = type.getClass()!!.functions.first { it.name == numberCastFunctionName }
IrCallImpl(startOffset, endOffset, function.returnType, function.symbol)
.apply { dispatchReceiver = this@castIfNecessary }
}
}
/** Return the negated value if the expression is const, otherwise call unaryMinus(). */
internal fun IrExpression.negate(): IrExpression {
val value = (this as? IrConst<*>)?.value as? Number
return when (value) {
is Int -> IrConstImpl(startOffset, endOffset, type, IrConstKind.Int, -value)
is Long -> IrConstImpl(startOffset, endOffset, type, IrConstKind.Long, -value)
else -> {
val unaryMinusFun = type.getClass()!!.functions.first { it.name == OperatorNameConventions.UNARY_MINUS }
IrCallImpl(startOffset, endOffset, type, unaryMinusFun.symbol, unaryMinusFun.descriptor).apply {
dispatchReceiver = this@negate
}
}
}
}
/** Return `this - 1` if the expression is const, otherwise call dec(). */
internal fun IrExpression.decrement(): IrExpression {
val thisValue = (this as? IrConst<*>)?.value
return when (thisValue) {
is Int -> IrConstImpl(startOffset, endOffset, type, IrConstKind.Int, thisValue - 1)
is Long -> IrConstImpl(startOffset, endOffset, type, IrConstKind.Long, thisValue - 1)
is Char -> IrConstImpl(startOffset, endOffset, type, IrConstKind.Char, thisValue - 1)
else -> {
val decFun = type.getClass()!!.functions.first { it.name == OperatorNameConventions.DEC }
IrCallImpl(startOffset, endOffset, type, decFun.symbol, decFun.descriptor).apply {
dispatchReceiver = this@decrement
}
}
}
}
internal val IrExpression.canHaveSideEffects: Boolean
get() = this !is IrConst<*> && this !is IrGetValue
internal val IrExpression.constLongValue: Long?
get() = if (this is IrConst<*>) {
val value = this.value
when (value) {
is Number -> value.toLong()
is Char -> value.toLong()
else -> null
}
} else null
@@ -7,10 +7,9 @@ package org.jetbrains.kotlin.backend.common.lower.matchers
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.SimpleType
internal interface IrFunctionMatcher : (IrFunction) -> Boolean
@@ -91,9 +90,9 @@ internal fun createIrFunctionRestrictions(restrictions: IrFunctionMatcherContain
internal fun IrFunctionMatcherContainer.singleArgumentExtension(
fqName: FqName,
types: Collection<SimpleType>
types: Collection<IrType>
): IrFunctionMatcherContainer {
extensionReceiver { it != null && it.type.toKotlinType() in types }
extensionReceiver { it != null && it.type in types }
parameterCount { it == 1 }
fqName { it == fqName }
return this