ForLoopsLowering: Move handlers to sub-package and separate files.

This commit is contained in:
Mark Punzalan
2020-07-09 17:06:51 -07:00
committed by Alexander Udalov
parent 44d283eb07
commit 291d62f653
12 changed files with 980 additions and 769 deletions
@@ -9,6 +9,21 @@ package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.ArrayIndicesHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.ArrayIterationHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.CharSequenceIndicesHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.CharSequenceIterationHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.CollectionIndicesHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.DefaultIterableHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.DefaultProgressionHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.DownToHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.IndexedGetIterationHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.RangeToHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.ReversedHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.StepHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.StringIterationHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.UntilHandler
import org.jetbrains.kotlin.backend.common.lower.loops.handlers.WithIndexHandler
import org.jetbrains.kotlin.backend.common.lower.matchers.IrCallMatcher
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrVariable
@@ -188,7 +203,7 @@ internal class IndexedGetHeaderInfo(
*/
internal class WithIndexHeaderInfo(val nestedInfo: HeaderInfo) : HeaderInfo() {
// We cannot easily reverse `withIndex()` so we do not attempt to handle it. We would have to start from the last value of the index,
// easily calculable (or even impossible) in most cases.
// which is not easily calculable (or even impossible) in most cases.
override fun asReversed(): HeaderInfo? = null
}
@@ -295,7 +310,13 @@ internal class DefaultHeaderInfoBuilder(context: CommonBackendContext, scopeOwne
HeaderInfoBuilder(context, scopeOwnerSymbol) {
override val callHandlers = listOf(
ReversedHandler(context, this),
WithIndexHandler(context, NestedHeaderInfoBuilderForWithIndex(context, scopeOwnerSymbol))
WithIndexHandler(
context,
NestedHeaderInfoBuilderForWithIndex(
context,
scopeOwnerSymbol
)
)
)
// NOTE: StringIterationHandler MUST come before CharSequenceIterationHandler.
@@ -1,767 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
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.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
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.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.util.OperatorNameConventions
import kotlin.math.absoluteValue
/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */
internal class RangeToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
ProgressionHandler {
override val matcher = SimpleCalleeMatcher {
dispatchReceiver { it != null && it.type in progressionElementTypes }
fqName { it.pathSegments().last() == OperatorNameConventions.RANGE_TO }
parameterCount { it == 1 }
parameter(0) { it.type in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol) =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
ProgressionHeaderInfo(
data,
first = expression.dispatchReceiver!!,
last = expression.getValueArgument(0)!!,
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<IrType>) :
ProgressionHandler {
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(FqName("kotlin.ranges.downTo"), progressionElementTypes)
parameterCount { it == 1 }
parameter(0) { it.type in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
ProgressionHeaderInfo(
data,
first = expression.extensionReceiver!!,
last = expression.getValueArgument(0)!!,
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<IrType>) :
ProgressionHandler {
private val symbols = context.ir.symbols
private val uByteType = symbols.uByte?.defaultType
private val uShortType = symbols.uShort?.defaultType
private val uIntType = symbols.uInt?.defaultType
private val uLongType = symbols.uLong?.defaultType
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(FqName("kotlin.ranges.until"), progressionElementTypes)
parameterCount { it == 1 }
parameter(0) { it.type in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
with(data) {
// `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 i = 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 && untilArg != MIN_VALUE) {
// // Loop is not empty
// do {
// val i = 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.asElementType()
// 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.createTmpVariable(receiverValue, nameHint = "untilReceiverValue")
}
untilArgVar = scope.createTmpVariable(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()
// Type of MIN_VALUE constant is signed even for unsigned progressions since the bounds are signed.
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`.
// Make sure to copy untilArgExpression as it is also used in `last`.
irNotEquals(untilArgExpression.deepCopyWithSymbols(), minValueExpression())
it == data.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 = first,
last = last,
step = irInt(1),
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
// infix fun UByte.until(to: UByte): UIntRange
// infix fun UShort.until(to: UShort): UIntRange
// infix fun UInt.until(to: UInt): UIntRange
// infix fun ULong.until(to: ULong): ULongRange
//
// 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.
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
}
uByteType -> false
uShortType -> false
uIntType -> true
uLongType -> true
else -> true // Default in case a new `until` overload is added to stdlib and this function was not updated.
}
}
}
}
/** Builds a [HeaderInfo] for progressions built using the `step` extension function. */
internal class StepHandler(
private val context: CommonBackendContext,
private val visitor: HeaderInfoBuilder
) : ProgressionHandler {
private val symbols = context.ir.symbols
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(FqName("kotlin.ranges.step"), symbols.progressionClasses.map { it.defaultType })
parameter(0) { it.type.isInt() || it.type.isLong() }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// Retrieve the HeaderInfo from the underlying progression (if any).
val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) as? ProgressionHeaderInfo
?: return null
val stepArg = expression.getValueArgument(0)!!
// We can return the nested info if its step is constant and its absolute value is the same as the step argument. Examples:
//
// 1..10 step 1 // Nested step is 1, argument is 1. Equivalent to `1..10`.
// 10 downTo 1 step 1 // Nested step is -1, argument is 1. Equivalent to `10 downTo 1`.
// 10 downTo 1 step 2 step 2 // Nested step is -2, argument is 2. Equivalent to `10 downTo 1 step 2`.
if (stepArg.constLongValue != null && nestedInfo.step.constLongValue?.absoluteValue == stepArg.constLongValue) {
return nestedInfo
}
// To reduce local variable usage, we create and use temporary variables only if necessary.
val (stepArgVar, stepArgExpression) = createTemporaryVariableIfNecessary(stepArg, "stepArg")
// The `step` standard library function only accepts positive values, and performs the following check:
//
// if (step > 0) step else throw IllegalArgumentException("Step must be positive, was: $step.")
//
// We insert this check in the lowered form only if necessary.
val stepType = data.stepClass.defaultType
val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType.getValue(data.stepClass.symbol)
val zeroStep = data.run { zeroStepExpression() }
val throwIllegalStepExceptionCall = {
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply {
val exceptionMessage = irConcat()
exceptionMessage.addArgument(irString("Step must be positive, was: "))
exceptionMessage.addArgument(stepArgExpression.deepCopyWithSymbols())
exceptionMessage.addArgument(irString("."))
putValueArgument(0, exceptionMessage)
}
}
val stepArgValueAsLong = stepArgExpression.constLongValue
val checkedStepExpression = when {
stepArgValueAsLong == null -> {
// Step argument is not a constant.
val stepPositiveCheck = irCall(stepGreaterFun).apply {
putValueArgument(0, stepArgExpression.deepCopyWithSymbols())
putValueArgument(1, zeroStep.deepCopyWithSymbols())
}
irIfThenElse(
stepType,
stepPositiveCheck,
stepArgExpression.deepCopyWithSymbols(),
throwIllegalStepExceptionCall()
)
}
stepArgValueAsLong > 0L ->
// Step argument is a positive constant and is valid.
stepArgExpression.deepCopyWithSymbols()
else ->
// Step argument is a non-positive constant and is invalid, directly throw the exception.
throwIllegalStepExceptionCall()
}
// While the `step` function accepts positive values, the "step" value in the progression depends on the direction of the
// nested progression. For example, in `10 downTo 1 step 2`, the nested progression is `10 downTo 1` which is decreasing,
// therefore the step used should be negated (-2).
//
// If we don't know the direction of the nested progression (e.g., `someProgression() step 2`) then we have to check its value
// first to determine whether to negate.
var nestedStepVar: IrVariable? = null
var checkedStepVar: IrVariable? = null
val checkedAndMaybeNegatedStep = when (nestedInfo.direction) {
ProgressionDirection.INCREASING -> checkedStepExpression
ProgressionDirection.DECREASING -> checkedStepExpression.negate()
ProgressionDirection.UNKNOWN -> {
// Check value of nested step and negate step arg if needed: `if (nestedStep > 0) nestedStep else -nestedStep`
// A temporary variable is created only if necessary, so we can preserve the evaluation order.
val nestedStep = nestedInfo.step
val (tmpNestedStepVar, nestedStepExpression) = createTemporaryVariableIfNecessary(nestedStep, "nestedStep")
nestedStepVar = tmpNestedStepVar
val nestedStepPositiveCheck = irCall(stepGreaterFun).apply {
putValueArgument(0, nestedStepExpression)
putValueArgument(1, zeroStep.deepCopyWithSymbols())
}
val (tmpCheckedStepVar, checkedStepOrGet) = createTemporaryVariableIfNecessary(checkedStepExpression, "checkedStep")
checkedStepVar = tmpCheckedStepVar
irIfThenElse(stepType, nestedStepPositiveCheck, checkedStepOrGet, checkedStepOrGet.deepCopyWithSymbols().negate())
}
}
// Store the nested "first" and "last" and final "step" in temporary variables only if necessary, so we can preserve the
// evaluation order.
val (nestedFirstVar, nestedFirstExpression) = createTemporaryVariableIfNecessary(nestedInfo.first, "nestedFirst")
val (nestedLastVar, nestedLastExpression) = createTemporaryVariableIfNecessary(nestedInfo.last, "nestedLast")
val (newStepVar, newStepExpression) = createTemporaryVariableIfNecessary(checkedAndMaybeNegatedStep, "newStep")
// Creating a progression with a step value != 1 may result in a "last" value that is smaller than the given "last". The new
// "last" value is such that iterating over the progression (by incrementing by "step") does not go over the "last" value.
//
// For example, in `1..10 step 2`, the values in the progression are [1, 3, 5, 7, 9]. Therefore the "last" value used in the
// stepped progression should be 9 even though the "last" in the nested progression is 10. Conversely, in `1..10 step 3`, the
// values in the progression are [1, 4, 7, 10], therefore the "last" value in the stepped progression is still 10. On the other
// hand, in `1..10 step 10`, the only value in the progression is 1, therefore the "last" value in the progression should be 1.
// In all cases, the "first" value is unchanged and the nested "first" can be used.
//
// The standard library calculates the correct "last" value by calling the internal getProgressionLastElement() function and we
// do the same when lowering to keep the behavior.
//
// In the case of multiple nested steps such as `1..10 step 2 step 3 step 2`, the recalculation happens 3 times:
// - In the innermost stepped progression `1..10 step 2`, the values are [1, 3, 5, 7, 9], the new "last" value is 9. (The
// return value of `getProgressionLastElement(1, 10, 2)` is 9.)
// - For `...step 3`, the values are [1, 4, 7]. It is NOT [1, 4, 7, 10] because the innermost progression stopped at 9. (The
// return value of `getProgressionLastElement(1, 9, 3)` is 7.)
// - For `...step 2`, the original "last" value of 10 is NOT restored, because the previous step already reduced "last" to 7.
// The values are [1, 3, 5, 7], the new "last" value is 7. (The return value of `getProgressionLastElement(1, 7, 2)` is 7.)
// - Therefore the final values are: first = 1, last = 7, step = 2. The final "last" is calculated as:
// getProgressionLastElement(1,
// getProgressionLastElement(1,
// getProgressionLastElement(1, 10, 2),
// 3),
// 2)
val recalculatedLast =
callGetProgressionLastElementIfNecessary(data, nestedFirstExpression, nestedLastExpression, newStepExpression)
// Consider the following for-loop:
//
// for (i in A..B step C step D) { // Loop body }
//
// ...where `A`, `B`, `C`, `D` may have side-effects. Variables will be created for those expressions where necessary, and we
// must preserve the evaluation order when adding these variables. If all the above expressions can have side-effects (e.g.,
// function calls), the final lowered form is something like:
//
// // Additional variables for inner step progression `A..B step C`:
// val innerNestedFirst = A
// val innerNestedLast = B
// // No nested step var because step for `A..B` is a constant 1
// val innerStepArg = C
// val innerNewStep = if (innerStepArg > 0) innerStepArg
// else throw IllegalArgumentException("Step must be positive, was: $innerStepArg.")
//
// // Additional variables for outer step progression `(A..B step C) step D`:
// // No nested first var because `innerNestedFirst` is a local variable get (cannot have side-effects)
// val outerNestedLast = // "last" for `A..B step C`
// getProgressionLastElement(innerNestedFirst, innerNestedLast, innerNewStep)
// // No nested step var because nested step `innerNewStep` is a local variable get (cannot have side-effects)
// val outerStepArg = D
// val outerNewStep = if (outerStepArg > 0) outerStepArg
// else throw IllegalArgumentException("Step must be positive, was: $outerStepArg.")
//
// // Standard form of loop over progression
// var inductionVar = innerNestedFirst
// val last = // "last" for `(A..B step C) step D`
// getProgressionLastElement(innerNestedFirst, // "Passed through" from inner step progression
// outerNestedLast, outerNewStep)
// val step = outerNewStep
// if (inductionVar <= last) {
// // Loop is not empty
// do {
// val i = inductionVar
// inductionVar += step
// // Loop body
// } while (i != last)
// }
//
// Another example (`step` on non-literal progression expression):
//
// for (i in P step C) { // Loop body }
//
// ...where `P` and `C` have side-effects. The final lowered form is something like:
//
// // Additional variables:
// val progression = P
// val nestedFirst = progression.first
// val nestedLast = progression.last
// val nestedStep = progression.step
// val stepArg = C
// val checkedStep = if (stepArg > 0) stepArg
// else throw IllegalArgumentException("Step must be positive, was: $stepArg.")
// val newStep = // Direction of P is unknown so we check its step to determine whether to negate
// if (nestedStep > 0) checkedStep else -checkedStep
//
// // Standard form of loop over progression
// var inductionVar = nestedFirst
// val last = getProgressionLastElement(nestedFirst, nestedLast, newStep)
// val step = newStep
// if ((step > 0 && inductionVar <= last) || (step < 0 && last <= inductionVar)) {
// // Loop is not empty
// do {
// val i = inductionVar
// inductionVar += step
// // Loop body
// } while (i != last)
// }
//
// If the nested progression is reversed, e.g.:
//
// for (i in (A..B).reversed() step C) { // Loop body }
//
// ...in the nested HeaderInfo, "first" is `B` and "last" is `A` (the progression goes from `B` to `A`). Therefore in this case,
// the nested "last" variable must come before the nested "first" variable (if both variables are necessary).
val additionalVariables = nestedInfo.additionalVariables + if (nestedInfo.isReversed) {
listOfNotNull(nestedLastVar, nestedFirstVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar)
} else {
listOfNotNull(nestedFirstVar, nestedLastVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar)
}
return ProgressionHeaderInfo(
data,
first = nestedFirstExpression,
last = recalculatedLast,
step = newStepExpression,
isReversed = nestedInfo.isReversed,
additionalVariables = additionalVariables,
additionalNotEmptyCondition = nestedInfo.additionalNotEmptyCondition,
direction = nestedInfo.direction
)
}
private fun DeclarationIrBuilder.callGetProgressionLastElementIfNecessary(
progressionType: ProgressionType,
first: IrExpression,
last: IrExpression,
step: IrExpression
): IrExpression {
// Calling getProgressionLastElement() is not needed if step == 1 or -1; the "last" value is unchanged in such cases.
if (step.constLongValue?.absoluteValue == 1L) {
return last
}
// Call `getProgressionLastElement(first, last, step)`. The following overloads are present in the stdlib:
// - getProgressionLastElement(Int, Int, Int): Int // Used by IntProgression and CharProgression (uses Int step)
// - getProgressionLastElement(Long, Long, Long): Long // Used by LongProgression
// - getProgressionLastElement(UInt, UInt, Int): UInt // Used by UIntProgression (uses Int step)
// - getProgressionLastElement(ULong, ULong, Long): ULong // Used by ULongProgression (uses Long step)
with(progressionType) {
val getProgressionLastElementFun = getProgressionLastElementFunction
?: error("No `getProgressionLastElement` for progression type ${progressionType::class.simpleName}")
return if (this is UnsignedProgressionType) {
// Bounds are signed for unsigned progressions but `getProgressionLastElement` expects unsigned.
// The return value is finally converted back to signed since it will be assigned back to `last`.
irCall(getProgressionLastElementFun).apply {
putValueArgument(0, first.deepCopyWithSymbols().asElementType().asUnsigned())
putValueArgument(1, last.deepCopyWithSymbols().asElementType().asUnsigned())
putValueArgument(2, step.deepCopyWithSymbols().asStepType())
}.asSigned()
} else {
irCall(getProgressionLastElementFun).apply {
// Step type is used for casting because it works for all signed progressions. In particular,
// getProgressionLastElement(Int, Int, Int) is called for CharProgression, which uses an Int step.
putValueArgument(0, first.deepCopyWithSymbols().asStepType())
putValueArgument(1, last.deepCopyWithSymbols().asStepType())
putValueArgument(2, step.deepCopyWithSymbols().asStepType())
}
}
}
}
}
/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */
internal abstract class IndicesHandler(protected val context: CommonBackendContext) : ProgressionHandler {
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// `last = array.size - 1` (last is inclusive) for the loop `for (i in array.indices)`.
val receiver = expression.extensionReceiver!!
val last = irCall(receiver.type.sizePropertyGetter).apply {
dispatchReceiver = receiver
}.decrement()
ProgressionHeaderInfo(
data,
first = irInt(0),
last = last,
step = irInt(1),
canOverflow = false,
direction = ProgressionDirection.INCREASING
)
}
abstract val IrType.sizePropertyGetter: IrSimpleFunction
}
internal class CollectionIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
override val matcher = SimpleCalleeMatcher {
extensionReceiver { it?.type?.isCollection() == true }
fqName { it == FqName("kotlin.collections.<get-indices>") }
parameterCount { it == 0 }
}
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = context.ir.symbols.collection.getPropertyGetter("size")!!.owner
}
internal class ArrayIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
override val matcher = SimpleCalleeMatcher {
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() } }
fqName { it == FqName("kotlin.collections.<get-indices>") }
parameterCount { it == 0 }
}
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = getClass()!!.getPropertyGetter("size")!!.owner
}
internal class CharSequenceIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
override val matcher = SimpleCalleeMatcher {
extensionReceiver { it != null && it.type.run { isCharSequence() } }
fqName { it == FqName("kotlin.text.<get-indices>") }
parameterCount { it == 0 }
}
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = context.ir.symbols.charSequence.getPropertyGetter("length")!!.owner
}
/** Builds a [HeaderInfo] for calls to reverse an iterable. */
@OptIn(ObsoleteDescriptorBasedAPI::class)
internal class ReversedHandler(context: CommonBackendContext, private val visitor: HeaderInfoBuilder) :
HeaderInfoFromCallHandler<Nothing?> {
private val symbols = context.ir.symbols
// Use Quantifier.ANY so we can handle all reversed iterables in the same manner.
override val matcher = createIrCallMatcher(Quantifier.ANY) {
// Matcher for reversed progression.
callee {
fqName { it == FqName("kotlin.ranges.reversed") }
extensionReceiver { it != null && it.type.toKotlinType() in symbols.progressionClassesTypes }
parameterCount { it == 0 }
}
// TODO: Handle reversed String, Progression.withIndex(), etc.
}
// Reverse the HeaderInfo from the underlying progression or array (if any).
override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol) =
expression.extensionReceiver!!.accept(visitor, null)?.asReversed()
}
/** Builds a [HeaderInfo] for progressions not handled by more specialized handlers. */
internal class DefaultProgressionHandler(private val context: CommonBackendContext) : ExpressionHandler {
private val symbols = context.ir.symbols
override fun matchIterable(expression: IrExpression) = ProgressionType.fromIrType(expression.type, symbols) != null
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 (progressionVar, progressionExpression) = createTemporaryVariableIfNecessary(expression, nameHint = "progression")
val progressionClass = progressionExpression.type.getClass()!!
val first = irCall(progressionClass.symbol.getPropertyGetter("first")!!).apply {
dispatchReceiver = progressionExpression
}
val last = irCall(progressionClass.symbol.getPropertyGetter("last")!!).apply {
dispatchReceiver = progressionExpression.deepCopyWithSymbols()
}
val step = irCall(progressionClass.symbol.getPropertyGetter("step")!!).apply {
dispatchReceiver = progressionExpression.deepCopyWithSymbols()
}
ProgressionHeaderInfo(
ProgressionType.fromIrType(progressionExpression.type, symbols)!!,
first,
last,
step,
additionalVariables = listOfNotNull(progressionVar),
direction = ProgressionDirection.UNKNOWN
)
}
}
internal abstract class IndexedGetIterationHandler(
protected val context: CommonBackendContext,
private val canCacheLast: Boolean
) : ExpressionHandler {
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// Consider the case like:
//
// for (elem in A) { f(elem) }`
//
// If we lower it to:
//
// for (i in A.indices) { f(A[i]) }
//
// ...then we will break program behaviour if `A` is an expression with side-effect. Instead, we lower it to:
//
// val a = A
// for (i in a.indices) { f(a[i]) }
//
// This also ensures that the semantics of re-assignment of array variables used in the loop is consistent with the semantics
// proposed in https://youtrack.jetbrains.com/issue/KT-21354.
val objectVariable = scope.createTmpVariable(
expression, nameHint = "indexedObject"
)
val last = irCall(expression.type.sizePropertyGetter).apply {
dispatchReceiver = irGet(objectVariable)
}
IndexedGetHeaderInfo(
this@IndexedGetIterationHandler.context.ir.symbols,
first = irInt(0),
last = last,
step = irInt(1),
canCacheLast = canCacheLast,
objectVariable = objectVariable,
expressionHandler = this@IndexedGetIterationHandler
)
}
abstract val IrType.sizePropertyGetter: IrSimpleFunction
abstract val IrType.getFunction: IrSimpleFunction
}
/** Builds a [HeaderInfo] for arrays. */
internal class ArrayIterationHandler(context: CommonBackendContext) : IndexedGetIterationHandler(context, canCacheLast = true) {
override fun matchIterable(expression: IrExpression) = expression.type.run { isArray() || isPrimitiveArray() }
override val IrType.sizePropertyGetter
get() = getClass()!!.getPropertyGetter("size")!!.owner
override val IrType.getFunction
get() = getClass()!!.functions.single {
it.name == OperatorNameConventions.GET &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type.isInt()
}
}
/**
* Builds a [HeaderInfo] for iteration over characters in a [CharSequence].
*
* Note: The value for "last" can NOT be cached (i.e., stored in a variable) because the size/length can change within the loop. This means
* that "last" is re-evaluated with each iteration of the loop.
*/
internal open class CharSequenceIterationHandler(context: CommonBackendContext, canCacheLast: Boolean = false) :
IndexedGetIterationHandler(context, canCacheLast) {
override fun matchIterable(expression: IrExpression) = expression.type.isSubtypeOfClass(context.ir.symbols.charSequence)
// We only want to handle the known extension function for CharSequence in the standard library (top level `kotlin.text.iterator`).
// The behavior of this iterator is well-defined and can be lowered. CharSequences can have their own iterators, either as a member or
// extension function, and the behavior of those custom iterators is unknown.
override val iteratorCallMatcher = SimpleCalleeMatcher {
extensionReceiver { it != null && it.type.run { isCharSequence() } }
fqName { it == FqName("kotlin.text.${OperatorNameConventions.ITERATOR}") }
parameterCount { it == 0 }
}
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = context.ir.symbols.charSequence.getPropertyGetter("length")!!.owner
override val IrType.getFunction: IrSimpleFunction
get() = context.ir.symbols.charSequence.getSimpleFunction(OperatorNameConventions.GET.asString())!!.owner
}
/**
* Builds a [HeaderInfo] for iteration over characters in a [String].
*
* Note: The value for "last" CAN be cached for Strings as they are immutable and the size/length cannot change.
*/
internal class StringIterationHandler(context: CommonBackendContext) : CharSequenceIterationHandler(context, canCacheLast = true) {
override fun matchIterable(expression: IrExpression) = expression.type.isString()
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = context.ir.symbols.string.getPropertyGetter("length")!!.owner
override val IrType.getFunction: IrSimpleFunction
get() = context.ir.symbols.string.getSimpleFunction(OperatorNameConventions.GET.asString())!!.owner
}
/** Builds a [HeaderInfo] for calls to `withIndex()`. */
internal class WithIndexHandler(context: CommonBackendContext, private val visitor: NestedHeaderInfoBuilderForWithIndex) :
HeaderInfoFromCallHandler<Nothing?> {
// Use Quantifier.ANY so we can handle all `withIndex()` calls in the same manner.
override val matcher = createIrCallMatcher(Quantifier.ANY) {
callee {
fqName { it == FqName("kotlin.collections.withIndex") }
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isIterable() } }
parameterCount { it == 0 }
}
callee {
fqName { it == FqName("kotlin.text.withIndex") }
extensionReceiver { it != null && it.type.isSubtypeOfClass(context.ir.symbols.charSequence) }
parameterCount { it == 0 }
}
callee {
fqName { it == FqName("kotlin.sequences.withIndex") }
extensionReceiver { it != null && it.type.run { isSequence() } }
parameterCount { it == 0 }
}
}
override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol): HeaderInfo? {
// WithIndexHeaderInfo is a composite that contains the HeaderInfo for the underlying iterable (if any).
val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) ?: return null
// We cannot lower `iterable.withIndex().withIndex()`.
// NestedHeaderInfoBuilderForWithIndex should not be yielding a WithIndexHeaderInfo, hence the assert.
assert(nestedInfo !is WithIndexHeaderInfo)
return WithIndexHeaderInfo(nestedInfo)
}
}
/** Builds a [HeaderInfo] for Iterables not handled by more specialized handlers. */
internal open class DefaultIterableHandler(private val context: CommonBackendContext) : ExpressionHandler {
protected open val iterableClassSymbol = context.ir.symbols.iterable
override fun matchIterable(expression: IrExpression) = expression.type.isSubtypeOfClass(iterableClassSymbol)
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
val iteratorFun = iterableClassSymbol.getSimpleFunction(OperatorNameConventions.ITERATOR.asString())!!.owner
IterableHeaderInfo(
scope.createTmpVariable(irCall(iteratorFun).apply { dispatchReceiver = expression }, nameHint = "iterator")
)
}
}
// TODO: Handle Sequences by extending DefaultIterableHandler.
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.ExpressionHandler
import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfo
import org.jetbrains.kotlin.backend.common.lower.loops.IterableHeaderInfo
import org.jetbrains.kotlin.ir.builders.createTmpVariable
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
import org.jetbrains.kotlin.ir.util.getSimpleFunction
/** Builds a [HeaderInfo] for Iterables not handled by more specialized handlers. */
internal open class DefaultIterableHandler(private val context: CommonBackendContext) :
ExpressionHandler {
protected open val iterableClassSymbol = context.ir.symbols.iterable
override fun matchIterable(expression: IrExpression) = expression.type.isSubtypeOfClass(iterableClassSymbol)
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
val iteratorFun =
iterableClassSymbol.getSimpleFunction(org.jetbrains.kotlin.util.OperatorNameConventions.ITERATOR.asString())!!.owner
IterableHeaderInfo(
scope.createTmpVariable(irCall(iteratorFun).apply { dispatchReceiver = expression }, nameHint = "iterator")
)
}
}
// TODO: Handle Sequences by extending DefaultIterableHandler.
@@ -0,0 +1,53 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.*
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.ir.util.getPropertyGetter
/** Builds a [HeaderInfo] for progressions not handled by more specialized handlers. */
internal class DefaultProgressionHandler(private val context: CommonBackendContext) :
ExpressionHandler {
private val symbols = context.ir.symbols
override fun matchIterable(expression: IrExpression) = ProgressionType.fromIrType(
expression.type,
symbols
) != null
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 (progressionVar, progressionExpression) = createTemporaryVariableIfNecessary(expression, nameHint = "progression")
val progressionClass = progressionExpression.type.getClass()!!
val first = irCall(progressionClass.symbol.getPropertyGetter("first")!!).apply {
dispatchReceiver = progressionExpression
}
val last = irCall(progressionClass.symbol.getPropertyGetter("last")!!).apply {
dispatchReceiver = progressionExpression.deepCopyWithSymbols()
}
val step = irCall(progressionClass.symbol.getPropertyGetter("step")!!).apply {
dispatchReceiver = progressionExpression.deepCopyWithSymbols()
}
ProgressionHeaderInfo(
ProgressionType.fromIrType(progressionExpression.type, symbols)!!,
first,
last,
step,
additionalVariables = listOfNotNull(progressionVar),
direction = ProgressionDirection.UNKNOWN
)
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.*
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.name.FqName
/** Builds a [HeaderInfo] for progressions built using the `downTo` extension function. */
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 in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
ProgressionHeaderInfo(
data,
first = expression.extensionReceiver!!,
last = expression.getValueArgument(0)!!,
step = irInt(-1),
direction = ProgressionDirection.DECREASING
)
}
}
@@ -0,0 +1,129 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.ExpressionHandler
import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfo
import org.jetbrains.kotlin.backend.common.lower.loops.IndexedGetHeaderInfo
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
import org.jetbrains.kotlin.ir.builders.createTmpVariable
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.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.getPropertyGetter
import org.jetbrains.kotlin.ir.util.getSimpleFunction
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.util.OperatorNameConventions
/** Builds a [HeaderInfo] for iteration over iterables using the `get / []` operator and an index. */
internal abstract class IndexedGetIterationHandler(
protected val context: CommonBackendContext,
private val canCacheLast: Boolean
) : ExpressionHandler {
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// Consider the case like:
//
// for (elem in A) { f(elem) }`
//
// If we lower it to:
//
// for (i in A.indices) { f(A[i]) }
//
// ...then we will break program behaviour if `A` is an expression with side-effect. Instead, we lower it to:
//
// val a = A
// for (i in a.indices) { f(a[i]) }
//
// This also ensures that the semantics of re-assignment of array variables used in the loop is consistent with the semantics
// proposed in https://youtrack.jetbrains.com/issue/KT-21354.
val objectVariable = scope.createTmpVariable(
expression, nameHint = "indexedObject"
)
val last = irCall(expression.type.sizePropertyGetter).apply {
dispatchReceiver = irGet(objectVariable)
}
IndexedGetHeaderInfo(
this@IndexedGetIterationHandler.context.ir.symbols,
first = irInt(0),
last = last,
step = irInt(1),
canCacheLast = canCacheLast,
objectVariable = objectVariable,
expressionHandler = this@IndexedGetIterationHandler
)
}
abstract val IrType.sizePropertyGetter: IrSimpleFunction
abstract val IrType.getFunction: IrSimpleFunction
}
/** Builds a [HeaderInfo] for arrays. */
internal class ArrayIterationHandler(context: CommonBackendContext) : IndexedGetIterationHandler(context, canCacheLast = true) {
override fun matchIterable(expression: IrExpression) = expression.type.run { isArray() || isPrimitiveArray() }
override val IrType.sizePropertyGetter
get() = getClass()!!.getPropertyGetter("size")!!.owner
override val IrType.getFunction
get() = getClass()!!.functions.single {
it.name == OperatorNameConventions.GET &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type.isInt()
}
}
/**
* Builds a [HeaderInfo] for iteration over characters in a [CharSequence].
*
* Note: The value for "last" can NOT be cached (i.e., stored in a variable) because the size/length can change within the loop. This means
* that "last" is re-evaluated with each iteration of the loop.
*/
internal open class CharSequenceIterationHandler(context: CommonBackendContext, canCacheLast: Boolean = false) :
IndexedGetIterationHandler(context, canCacheLast) {
override fun matchIterable(expression: IrExpression) = expression.type.isSubtypeOfClass(context.ir.symbols.charSequence)
// We only want to handle the known extension function for CharSequence in the standard library (top level `kotlin.text.iterator`).
// The behavior of this iterator is well-defined and can be lowered. CharSequences can have their own iterators, either as a member or
// extension function, and the behavior of those custom iterators is unknown.
override val iteratorCallMatcher = SimpleCalleeMatcher {
extensionReceiver { it != null && it.type.run { isCharSequence() } }
fqName { it == FqName("kotlin.text.${OperatorNameConventions.ITERATOR}") }
parameterCount { it == 0 }
}
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = context.ir.symbols.charSequence.getPropertyGetter("length")!!.owner
override val IrType.getFunction: IrSimpleFunction
get() = context.ir.symbols.charSequence.getSimpleFunction(OperatorNameConventions.GET.asString())!!.owner
}
/**
* Builds a [HeaderInfo] for iteration over characters in a [String].
*
* Note: The value for "last" CAN be cached for Strings as they are immutable and the size/length cannot change.
*/
internal class StringIterationHandler(context: CommonBackendContext) : CharSequenceIterationHandler(context, canCacheLast = true) {
override fun matchIterable(expression: IrExpression) = expression.type.isString()
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = context.ir.symbols.string.getPropertyGetter("length")!!.owner
override val IrType.getFunction: IrSimpleFunction
get() = context.ir.symbols.string.getSimpleFunction(OperatorNameConventions.GET.asString())!!.owner
}
@@ -0,0 +1,81 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.*
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.getPropertyGetter
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
import org.jetbrains.kotlin.name.FqName
/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */
internal abstract class IndicesHandler(protected val context: CommonBackendContext) :
ProgressionHandler {
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// `last = array.size - 1` (last is inclusive) for the loop `for (i in array.indices)`.
val receiver = expression.extensionReceiver!!
val last = irCall(receiver.type.sizePropertyGetter).apply {
dispatchReceiver = receiver
}.decrement()
ProgressionHeaderInfo(
data,
first = irInt(0),
last = last,
step = irInt(1),
canOverflow = false,
direction = ProgressionDirection.INCREASING
)
}
abstract val IrType.sizePropertyGetter: IrSimpleFunction
}
internal class CollectionIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
override val matcher = SimpleCalleeMatcher {
extensionReceiver { it?.type?.isCollection() == true }
fqName { it == FqName("kotlin.collections.<get-indices>") }
parameterCount { it == 0 }
}
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = context.ir.symbols.collection.getPropertyGetter("size")!!.owner
}
internal class ArrayIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
override val matcher = SimpleCalleeMatcher {
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() } }
fqName { it == FqName("kotlin.collections.<get-indices>") }
parameterCount { it == 0 }
}
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = getClass()!!.getPropertyGetter("size")!!.owner
}
internal class CharSequenceIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
override val matcher = SimpleCalleeMatcher {
extensionReceiver { it != null && it.type.run { isCharSequence() } }
fqName { it == FqName("kotlin.text.<get-indices>") }
parameterCount { it == 0 }
}
override val IrType.sizePropertyGetter: IrSimpleFunction
get() = context.ir.symbols.charSequence.getPropertyGetter("length")!!.owner
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionDirection
import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionHandler
import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionHeaderInfo
import org.jetbrains.kotlin.backend.common.lower.loops.ProgressionType
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.util.OperatorNameConventions
/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */
internal class RangeToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
ProgressionHandler {
override val matcher = SimpleCalleeMatcher {
dispatchReceiver { it != null && it.type in progressionElementTypes }
fqName { it.pathSegments().last() == OperatorNameConventions.RANGE_TO }
parameterCount { it == 1 }
parameter(0) { it.type in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol) =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
ProgressionHeaderInfo(
data,
first = expression.dispatchReceiver!!,
last = expression.getValueArgument(0)!!,
step = irInt(1),
direction = ProgressionDirection.INCREASING
)
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfoBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfoFromCallHandler
import org.jetbrains.kotlin.backend.common.lower.matchers.Quantifier
import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.name.FqName
/** Builds a [HeaderInfo] for calls to reverse an iterable. */
@OptIn(ObsoleteDescriptorBasedAPI::class)
internal class ReversedHandler(context: CommonBackendContext, private val visitor: HeaderInfoBuilder) :
HeaderInfoFromCallHandler<Nothing?> {
private val symbols = context.ir.symbols
// Use Quantifier.ANY so we can handle all reversed iterables in the same manner.
override val matcher =
createIrCallMatcher(Quantifier.ANY) {
// Matcher for reversed progression.
callee {
fqName { it == FqName("kotlin.ranges.reversed") }
extensionReceiver { it != null && it.type.toKotlinType() in symbols.progressionClassesTypes }
parameterCount { it == 0 }
}
// TODO: Handle reversed String, Progression.withIndex(), etc.
}
// Reverse the HeaderInfo from the underlying progression or array (if any).
override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol) =
expression.extensionReceiver!!.accept(visitor, null)?.asReversed()
}
@@ -0,0 +1,298 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.*
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irConcat
import org.jetbrains.kotlin.ir.builders.irIfThenElse
import org.jetbrains.kotlin.ir.builders.irString
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.defaultType
import org.jetbrains.kotlin.ir.types.isInt
import org.jetbrains.kotlin.ir.types.isLong
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.name.FqName
import kotlin.math.absoluteValue
/** Builds a [HeaderInfo] for progressions built using the `step` extension function. */
internal class StepHandler(
private val context: CommonBackendContext,
private val visitor: HeaderInfoBuilder
) : ProgressionHandler {
private val symbols = context.ir.symbols
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(
FqName("kotlin.ranges.step"),
symbols.progressionClasses.map { it.defaultType })
parameter(0) { it.type.isInt() || it.type.isLong() }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// Retrieve the HeaderInfo from the underlying progression (if any).
val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) as? ProgressionHeaderInfo
?: return null
val stepArg = expression.getValueArgument(0)!!
// We can return the nested info if its step is constant and its absolute value is the same as the step argument. Examples:
//
// 1..10 step 1 // Nested step is 1, argument is 1. Equivalent to `1..10`.
// 10 downTo 1 step 1 // Nested step is -1, argument is 1. Equivalent to `10 downTo 1`.
// 10 downTo 1 step 2 step 2 // Nested step is -2, argument is 2. Equivalent to `10 downTo 1 step 2`.
if (stepArg.constLongValue != null && nestedInfo.step.constLongValue?.absoluteValue == stepArg.constLongValue) {
return nestedInfo
}
// To reduce local variable usage, we create and use temporary variables only if necessary.
val (stepArgVar, stepArgExpression) = createTemporaryVariableIfNecessary(stepArg, "stepArg")
// The `step` standard library function only accepts positive values, and performs the following check:
//
// if (step > 0) step else throw IllegalArgumentException("Step must be positive, was: $step.")
//
// We insert this check in the lowered form only if necessary.
val stepType = data.stepClass.defaultType
val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType.getValue(data.stepClass.symbol)
val zeroStep = data.run { zeroStepExpression() }
val throwIllegalStepExceptionCall = {
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply {
val exceptionMessage = irConcat()
exceptionMessage.addArgument(irString("Step must be positive, was: "))
exceptionMessage.addArgument(stepArgExpression.deepCopyWithSymbols())
exceptionMessage.addArgument(irString("."))
putValueArgument(0, exceptionMessage)
}
}
val stepArgValueAsLong = stepArgExpression.constLongValue
val checkedStepExpression = when {
stepArgValueAsLong == null -> {
// Step argument is not a constant.
val stepPositiveCheck = irCall(stepGreaterFun).apply {
putValueArgument(0, stepArgExpression.deepCopyWithSymbols())
putValueArgument(1, zeroStep.deepCopyWithSymbols())
}
irIfThenElse(
stepType,
stepPositiveCheck,
stepArgExpression.deepCopyWithSymbols(),
throwIllegalStepExceptionCall()
)
}
stepArgValueAsLong > 0L ->
// Step argument is a positive constant and is valid.
stepArgExpression.deepCopyWithSymbols()
else ->
// Step argument is a non-positive constant and is invalid, directly throw the exception.
throwIllegalStepExceptionCall()
}
// While the `step` function accepts positive values, the "step" value in the progression depends on the direction of the
// nested progression. For example, in `10 downTo 1 step 2`, the nested progression is `10 downTo 1` which is decreasing,
// therefore the step used should be negated (-2).
//
// If we don't know the direction of the nested progression (e.g., `someProgression() step 2`) then we have to check its value
// first to determine whether to negate.
var nestedStepVar: IrVariable? = null
var checkedStepVar: IrVariable? = null
val checkedAndMaybeNegatedStep = when (nestedInfo.direction) {
ProgressionDirection.INCREASING -> checkedStepExpression
ProgressionDirection.DECREASING -> checkedStepExpression.negate()
ProgressionDirection.UNKNOWN -> {
// Check value of nested step and negate step arg if needed: `if (nestedStep > 0) checkedStep else -checkedStep`
// A temporary variable is created only if necessary, so we can preserve the evaluation order.
val nestedStep = nestedInfo.step
val (tmpNestedStepVar, nestedStepExpression) = createTemporaryVariableIfNecessary(nestedStep, "nestedStep")
nestedStepVar = tmpNestedStepVar
val nestedStepPositiveCheck = irCall(stepGreaterFun).apply {
putValueArgument(0, nestedStepExpression)
putValueArgument(1, zeroStep.deepCopyWithSymbols())
}
val (tmpCheckedStepVar, checkedStepOrGet) = createTemporaryVariableIfNecessary(checkedStepExpression, "checkedStep")
checkedStepVar = tmpCheckedStepVar
irIfThenElse(stepType, nestedStepPositiveCheck, checkedStepOrGet, checkedStepOrGet.deepCopyWithSymbols().negate())
}
}
// Store the nested "first" and "last" and final "step" in temporary variables only if necessary, so we can preserve the
// evaluation order.
val (nestedFirstVar, nestedFirstExpression) = createTemporaryVariableIfNecessary(nestedInfo.first, "nestedFirst")
val (nestedLastVar, nestedLastExpression) = createTemporaryVariableIfNecessary(nestedInfo.last, "nestedLast")
val (newStepVar, newStepExpression) = createTemporaryVariableIfNecessary(checkedAndMaybeNegatedStep, "newStep")
// Creating a progression with a step value != 1 may result in a "last" value that is smaller than the given "last". The new
// "last" value is such that iterating over the progression (by incrementing by "step") does not go over the "last" value.
//
// For example, in `1..10 step 2`, the values in the progression are [1, 3, 5, 7, 9]. Therefore the "last" value used in the
// stepped progression should be 9 even though the "last" in the nested progression is 10. Conversely, in `1..10 step 3`, the
// values in the progression are [1, 4, 7, 10], therefore the "last" value in the stepped progression is still 10. On the other
// hand, in `1..10 step 10`, the only value in the progression is 1, therefore the "last" value in the progression should be 1.
// In all cases, the "first" value is unchanged and the nested "first" can be used.
//
// The standard library calculates the correct "last" value by calling the internal getProgressionLastElement() function and we
// do the same when lowering to keep the behavior.
//
// In the case of multiple nested steps such as `1..10 step 2 step 3 step 2`, the recalculation happens 3 times:
// - In the innermost stepped progression `1..10 step 2`, the values are [1, 3, 5, 7, 9], the new "last" value is 9. (The
// return value of `getProgressionLastElement(1, 10, 2)` is 9.)
// - For `...step 3`, the values are [1, 4, 7]. It is NOT [1, 4, 7, 10] because the innermost progression stopped at 9. (The
// return value of `getProgressionLastElement(1, 9, 3)` is 7.)
// - For `...step 2`, the original "last" value of 10 is NOT restored, because the previous step already reduced "last" to 7.
// The values are [1, 3, 5, 7], the new "last" value is 7. (The return value of `getProgressionLastElement(1, 7, 2)` is 7.)
// - Therefore the final values are: first = 1, last = 7, step = 2. The final "last" is calculated as:
// getProgressionLastElement(1,
// getProgressionLastElement(1,
// getProgressionLastElement(1, 10, 2),
// 3),
// 2)
val recalculatedLast =
callGetProgressionLastElementIfNecessary(data, nestedFirstExpression, nestedLastExpression, newStepExpression)
// Consider the following for-loop:
//
// for (i in A..B step C step D) { // Loop body }
//
// ...where `A`, `B`, `C`, `D` may have side-effects. Variables will be created for those expressions where necessary, and we
// must preserve the evaluation order when adding these variables. If all the above expressions can have side-effects (e.g.,
// function calls), the final lowered form is something like:
//
// // Additional variables for inner step progression `A..B step C`:
// val innerNestedFirst = A
// val innerNestedLast = B
// // No nested step var because step for `A..B` is a constant 1
// val innerStepArg = C
// val innerNewStep = if (innerStepArg > 0) innerStepArg
// else throw IllegalArgumentException("Step must be positive, was: $innerStepArg.")
//
// // Additional variables for outer step progression `(A..B step C) step D`:
// // No nested first var because `innerNestedFirst` is a local variable get (cannot have side-effects)
// val outerNestedLast = // "last" for `A..B step C`
// getProgressionLastElement(innerNestedFirst, innerNestedLast, innerNewStep)
// // No nested step var because nested step `innerNewStep` is a local variable get (cannot have side-effects)
// val outerStepArg = D
// val outerNewStep = if (outerStepArg > 0) outerStepArg
// else throw IllegalArgumentException("Step must be positive, was: $outerStepArg.")
//
// // Standard form of loop over progression
// var inductionVar = innerNestedFirst
// val last = // "last" for `(A..B step C) step D`
// getProgressionLastElement(innerNestedFirst, // "Passed through" from inner step progression
// outerNestedLast, outerNewStep)
// val step = outerNewStep
// if (inductionVar <= last) {
// // Loop is not empty
// do {
// val i = inductionVar
// inductionVar += step
// // Loop body
// } while (i != last)
// }
//
// Another example (`step` on non-literal progression expression):
//
// for (i in P step C) { // Loop body }
//
// ...where `P` and `C` have side-effects. The final lowered form is something like:
//
// // Additional variables:
// val progression = P
// val nestedFirst = progression.first
// val nestedLast = progression.last
// val nestedStep = progression.step
// val stepArg = C
// val checkedStep = if (stepArg > 0) stepArg
// else throw IllegalArgumentException("Step must be positive, was: $stepArg.")
// val newStep = // Direction of P is unknown so we check its step to determine whether to negate
// if (nestedStep > 0) checkedStep else -checkedStep
//
// // Standard form of loop over progression
// var inductionVar = nestedFirst
// val last = getProgressionLastElement(nestedFirst, nestedLast, newStep)
// val step = newStep
// if ((step > 0 && inductionVar <= last) || (step < 0 && last <= inductionVar)) {
// // Loop is not empty
// do {
// val i = inductionVar
// inductionVar += step
// // Loop body
// } while (i != last)
// }
//
// If the nested progression is reversed, e.g.:
//
// for (i in (A..B).reversed() step C) { // Loop body }
//
// ...in the nested HeaderInfo, "first" is `B` and "last" is `A` (the progression goes from `B` to `A`). Therefore in this case,
// the nested "last" variable must come before the nested "first" variable (if both variables are necessary).
val additionalVariables = nestedInfo.additionalVariables + if (nestedInfo.isReversed) {
listOfNotNull(nestedLastVar, nestedFirstVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar)
} else {
listOfNotNull(nestedFirstVar, nestedLastVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar)
}
return ProgressionHeaderInfo(
data,
first = nestedFirstExpression,
last = recalculatedLast,
step = newStepExpression,
isReversed = nestedInfo.isReversed,
additionalVariables = additionalVariables,
additionalNotEmptyCondition = nestedInfo.additionalNotEmptyCondition,
direction = nestedInfo.direction
)
}
private fun DeclarationIrBuilder.callGetProgressionLastElementIfNecessary(
progressionType: ProgressionType,
first: IrExpression,
last: IrExpression,
step: IrExpression
): IrExpression {
// Calling getProgressionLastElement() is not needed if step == 1 or -1; the "last" value is unchanged in such cases.
if (step.constLongValue?.absoluteValue == 1L) {
return last
}
// Call `getProgressionLastElement(first, last, step)`. The following overloads are present in the stdlib:
// - getProgressionLastElement(Int, Int, Int): Int // Used by IntProgression and CharProgression (uses Int step)
// - getProgressionLastElement(Long, Long, Long): Long // Used by LongProgression
// - getProgressionLastElement(UInt, UInt, Int): UInt // Used by UIntProgression (uses Int step)
// - getProgressionLastElement(ULong, ULong, Long): ULong // Used by ULongProgression (uses Long step)
with(progressionType) {
val getProgressionLastElementFun = getProgressionLastElementFunction
?: error("No `getProgressionLastElement` for progression type ${progressionType::class.simpleName}")
return if (this is UnsignedProgressionType) {
// Bounds are signed for unsigned progressions but `getProgressionLastElement` expects unsigned.
// The return value is finally converted back to signed since it will be assigned back to `last`.
irCall(getProgressionLastElementFun).apply {
putValueArgument(0, first.deepCopyWithSymbols().asElementType().asUnsigned())
putValueArgument(1, last.deepCopyWithSymbols().asElementType().asUnsigned())
putValueArgument(2, step.deepCopyWithSymbols().asStepType())
}.asSigned()
} else {
irCall(getProgressionLastElementFun).apply {
// Step type is used for casting because it works for all signed progressions. In particular,
// getProgressionLastElement(Int, Int, Int) is called for CharProgression, which uses an Int step.
putValueArgument(0, first.deepCopyWithSymbols().asStepType())
putValueArgument(1, last.deepCopyWithSymbols().asStepType())
putValueArgument(2, step.deepCopyWithSymbols().asStepType())
}
}
}
}
}
@@ -0,0 +1,177 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.loops.*
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.name.FqName
/** Builds a [HeaderInfo] for progressions built using the `until` extension function. */
internal class UntilHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
ProgressionHandler {
private val symbols = context.ir.symbols
private val uByteType = symbols.uByte?.defaultType
private val uShortType = symbols.uShort?.defaultType
private val uIntType = symbols.uInt?.defaultType
private val uLongType = symbols.uLong?.defaultType
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(FqName("kotlin.ranges.until"), progressionElementTypes)
parameterCount { it == 1 }
parameter(0) { it.type in progressionElementTypes }
}
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
with(data) {
// `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 i = 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 && untilArg != MIN_VALUE) {
// // Loop is not empty
// do {
// val i = 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.asElementType()
// 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.createTmpVariable(receiverValue, nameHint = "untilReceiverValue")
}
untilArgVar = scope.createTmpVariable(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()
// Type of MIN_VALUE constant is signed even for unsigned progressions since the bounds are signed.
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`.
// Make sure to copy untilArgExpression as it is also used in `last`.
irNotEquals(untilArgExpression.deepCopyWithSymbols(), minValueExpression())
it == data.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 = first,
last = last,
step = irInt(1),
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
// infix fun UByte.until(to: UByte): UIntRange
// infix fun UShort.until(to: UShort): UIntRange
// infix fun UInt.until(to: UInt): UIntRange
// infix fun ULong.until(to: ULong): ULongRange
//
// 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.
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
}
uByteType -> false
uShortType -> false
uIntType -> true
uLongType -> true
else -> true // Default in case a new `until` overload is added to stdlib and this function was not updated.
}
}
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.handlers
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfo
import org.jetbrains.kotlin.backend.common.lower.loops.HeaderInfoFromCallHandler
import org.jetbrains.kotlin.backend.common.lower.loops.NestedHeaderInfoBuilderForWithIndex
import org.jetbrains.kotlin.backend.common.lower.loops.WithIndexHeaderInfo
import org.jetbrains.kotlin.backend.common.lower.matchers.Quantifier
import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.isArray
import org.jetbrains.kotlin.ir.types.isIterable
import org.jetbrains.kotlin.ir.types.isSequence
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
import org.jetbrains.kotlin.ir.util.isPrimitiveArray
import org.jetbrains.kotlin.name.FqName
/** Builds a [HeaderInfo] for calls to `withIndex()`. */
internal class WithIndexHandler(context: CommonBackendContext, private val visitor: NestedHeaderInfoBuilderForWithIndex) :
HeaderInfoFromCallHandler<Nothing?> {
// Use Quantifier.ANY so we can handle all `withIndex()` calls in the same manner.
override val matcher =
createIrCallMatcher(Quantifier.ANY) {
callee {
fqName { it == FqName("kotlin.collections.withIndex") }
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isIterable() } }
parameterCount { it == 0 }
}
callee {
fqName { it == FqName("kotlin.text.withIndex") }
extensionReceiver { it != null && it.type.isSubtypeOfClass(context.ir.symbols.charSequence) }
parameterCount { it == 0 }
}
callee {
fqName { it == FqName("kotlin.sequences.withIndex") }
extensionReceiver { it != null && it.type.run { isSequence() } }
parameterCount { it == 0 }
}
}
override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol): HeaderInfo? {
// WithIndexHeaderInfo is a composite that contains the HeaderInfo for the underlying iterable (if any).
val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) ?: return null
// We cannot lower `iterable.withIndex().withIndex()`.
// NestedHeaderInfoBuilderForWithIndex should not be yielding a WithIndexHeaderInfo, hence the assert.
assert(nestedInfo !is WithIndexHeaderInfo)
return WithIndexHeaderInfo(nestedInfo)
}
}