Handle step progressions in ForLoopsLowering.

This commit is contained in:
Mark Punzalan
2019-06-03 22:37:52 -07:00
committed by max-kammerer
parent 38f0fd256e
commit 8a4185202f
3 changed files with 351 additions and 24 deletions
@@ -6,10 +6,7 @@
package org.jetbrains.kotlin.backend.common.ir
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.builtins.KOTLIN_REFLECT_FQ_NAME
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.builtins.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
@@ -109,6 +106,12 @@ abstract class Symbols<out T : CommonBackendContext>(val context: T, private val
val progressionClasses = listOf(charProgression, intProgression, longProgression)
val progressionClassesTypes = progressionClasses.map { it.descriptor.defaultType }.toSet()
val getProgressionLastElementByReturnType = builtInsPackage("kotlin", "internal").getContributedFunctions(
Name.identifier("getProgressionLastElement"),
NoLookupLocation.FROM_BACKEND
).filter { it.containingDeclaration !is BuiltInsPackageFragment }
.map { Pair(it.returnType!!, symbolTable.referenceSimpleFunction(it)) }.toMap()
val any = symbolTable.referenceClass(builtIns.any)
val unit = symbolTable.referenceClass(builtIns.unit)
@@ -31,22 +31,22 @@ internal enum class ProgressionType(val elementCastFunctionName: Name, val stepC
/** Returns the [IrType] of the `first`/`last` properties and elements in the progression. */
fun elementType(builtIns: IrBuiltIns): IrType = when (this) {
ProgressionType.INT_PROGRESSION -> builtIns.intType
ProgressionType.LONG_PROGRESSION -> builtIns.longType
ProgressionType.CHAR_PROGRESSION -> builtIns.charType
INT_PROGRESSION -> builtIns.intType
LONG_PROGRESSION -> builtIns.longType
CHAR_PROGRESSION -> builtIns.charType
}
/** Returns the [IrType] of the `step` property in the progression. */
fun stepType(builtIns: IrBuiltIns): IrType = when (this) {
ProgressionType.INT_PROGRESSION, ProgressionType.CHAR_PROGRESSION -> builtIns.intType
ProgressionType.LONG_PROGRESSION -> builtIns.longType
INT_PROGRESSION, CHAR_PROGRESSION -> builtIns.intType
LONG_PROGRESSION -> builtIns.longType
}
companion object {
fun fromIrType(irType: IrType, symbols: Symbols<CommonBackendContext>): ProgressionType? = when {
irType.isSubtypeOfClass(symbols.charProgression) -> ProgressionType.CHAR_PROGRESSION
irType.isSubtypeOfClass(symbols.intProgression) -> ProgressionType.INT_PROGRESSION
irType.isSubtypeOfClass(symbols.longProgression) -> ProgressionType.LONG_PROGRESSION
irType.isSubtypeOfClass(symbols.charProgression) -> CHAR_PROGRESSION
irType.isSubtypeOfClass(symbols.intProgression) -> INT_PROGRESSION
irType.isSubtypeOfClass(symbols.longProgression) -> LONG_PROGRESSION
else -> null
}
}
@@ -107,26 +107,49 @@ internal class ProgressionHeaderInfo(
val canOverflow: Boolean by lazy {
if (canOverflow != null) return@lazy canOverflow
// Induction variable can overflow if it is not a const, or is MAX/MIN_VALUE (depending on direction).
// We can't determine the safe limit at compile-time if "step" is not const.
val stepValueAsLong = step.constLongValue ?: return@lazy true
// Induction variable can NOT overflow if "last" is const and is <= (MAX/MIN_VALUE - step) (depending on direction).
//
// Examples that can NOT overflow:
// - `0..10` cannot overflow (10 <= MAX_VALUE - 1)
// - `0..MAX_VALUE - 1` cannot overflow (MAX_VALUE - 1 <= MAX_VALUE - 1)
// - `0..MAX_VALUE - 3 step 3` cannot overflow (MAX_VALUE - 3 <= MAX_VALUE - 3)
// - `0 downTo -10` cannot overflow (-10 >= MIN_VALUE - (-1))
// - `0 downTo MIN_VALUE + 1` (step is -1) cannot overflow (MIN_VALUE + 1 >= MIN_VALUE - (-1))
// - `0 downTo MIN_VALUE + 3 step 3` (step is -3) cannot overflow (MIN_VALUE + 3 >= MIN_VALUE - (-3))
//
// Examples that CAN overflow:
// - `0..MAX_VALUE` CAN overflow (MAX_VALUE > MAX_VALUE - 1)
// - `0..MAX_VALUE - 2 step 3` cannot overflow (MAX_VALUE - 2 > MAX_VALUE - 3)
// - `0 downTo MIN_VALUE` (step is -1) CAN overflow (MIN_VALUE < MIN_VALUE - (-1))
// - `0 downTo MIN_VALUE + 2 step 3` (step is -3) cannot overflow (MIN_VALUE + 2 < MIN_VALUE - (-3))
// - `0..10 step someStep()` CAN overflow (we don't know the step and hence can't determine the safe limit)
// - `0..someLast()` CAN overflow (we don't know the direction)
// - `someProgression()` CAN overflow (we don't know the direction)
val lastValueAsLong = last.constLongValue ?: return@lazy true // If "last" is not a const Number or Char.
val constLimitAsLong = when (direction) {
when (direction) {
ProgressionDirection.UNKNOWN ->
// If we don't know the direction, we can't be sure which limit to use.
return@lazy true
ProgressionDirection.DECREASING ->
when (progressionType) {
true
ProgressionDirection.DECREASING -> {
val constLimitAsLong = when (progressionType) {
ProgressionType.INT_PROGRESSION -> Int.MIN_VALUE.toLong()
ProgressionType.CHAR_PROGRESSION -> Char.MIN_VALUE.toLong()
ProgressionType.LONG_PROGRESSION -> Long.MIN_VALUE
}
ProgressionDirection.INCREASING ->
when (progressionType) {
lastValueAsLong < (constLimitAsLong - stepValueAsLong)
}
ProgressionDirection.INCREASING -> {
val constLimitAsLong = when (progressionType) {
ProgressionType.INT_PROGRESSION -> Int.MAX_VALUE.toLong()
ProgressionType.CHAR_PROGRESSION -> Char.MAX_VALUE.toLong()
ProgressionType.LONG_PROGRESSION -> Long.MAX_VALUE
}
lastValueAsLong > (constLimitAsLong - stepValueAsLong)
}
}
constLimitAsLong == lastValueAsLong
}
override fun asReversed() = ProgressionHeaderInfo(
@@ -226,7 +249,8 @@ internal class HeaderInfoBuilder(context: CommonBackendContext, private val scop
CharSequenceIndicesHandler(context),
UntilHandler(context, progressionElementTypes),
DownToHandler(context, progressionElementTypes),
RangeToHandler(context, progressionElementTypes)
RangeToHandler(context, progressionElementTypes),
StepHandler(context, this)
)
private val reversedHandler = ReversedHandler(context, this)
@@ -6,6 +6,7 @@
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
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
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>) :
@@ -93,7 +95,7 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
// if (inductionVar <= last && B != MIN_VALUE) {
// // Loop is not empty
// do {
// val loopVar = inductionVar
// val i = inductionVar
// inductionVar++
// // Loop body
// } while (inductionVar <= last)
@@ -108,10 +110,10 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
// // Standard form of loop over progression
// var inductionVar = untilReceiverValue
// val last = untilArg - 1
// if (inductionVar <= last && untilFunArg != MIN_VALUE) {
// if (inductionVar <= last && untilArg != MIN_VALUE) {
// // Loop is not empty
// do {
// val loopVar = inductionVar
// val i = inductionVar
// inductionVar++
// // Loop body
// } while (inductionVar <= last)
@@ -218,6 +220,303 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
}
}
/** Builds a [HeaderInfo] for progressions built using the `step` extension function. */
internal class StepHandler(
private val context: CommonBackendContext,
private val visitor: IrElementVisitor<HeaderInfo?, Nothing?>
) : ProgressionHandler {
private val symbols = context.ir.symbols
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(FqName("kotlin.ranges.step"), symbols.progressionClasses.map { it.typeWith() })
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.
var stepArgVar: IrVariable? = null
val stepArgExpression = if (stepArg.canHaveSideEffects) {
stepArgVar = scope.createTemporaryVariable(stepArg, nameHint = "stepArg")
irGet(stepArgVar)
} else {
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.stepType(context.irBuiltIns)
val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType[stepType.toKotlinType()]!!
val zeroStep = if (data == ProgressionType.LONG_PROGRESSION) irLong(0) else irInt(0)
val throwIllegalStepExceptionCall = {
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol, stepType).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 nestedStepExpression = if (nestedStep.canHaveSideEffects) {
nestedStepVar = scope.createTemporaryVariable(nestedStep, nameHint = "nestedStep")
irGet(nestedStepVar)
} else {
nestedStep
}
val nestedStepPositiveCheck = irCall(stepGreaterFun).apply {
putValueArgument(0, nestedStepExpression)
putValueArgument(1, zeroStep.deepCopyWithSymbols())
}
checkedStepVar = scope.createTemporaryVariable(checkedStepExpression, nameHint = "checkedStep")
irIfThenElse(stepType, nestedStepPositiveCheck, irGet(checkedStepVar), irGet(checkedStepVar).negate())
}
}
// Store the final "step" a temporary variable only if necessary, so we can preserve the evaluation order.
var newStepVar: IrVariable? = null
val newStepExpression = if (checkedAndMaybeNegatedStep.canHaveSideEffects) {
newStepVar = scope.createTemporaryVariable(checkedAndMaybeNegatedStep, nameHint = "newStep")
irGet(newStepVar)
} else {
checkedAndMaybeNegatedStep
}
// Store the nested "first" and "last" in temporary variables only if necessary, so we can preserve the evaluation order.
var nestedFirstVar: IrVariable? = null
val nestedFirst = nestedInfo.first
val nestedFirstExpression = if (nestedFirst.canHaveSideEffects) {
nestedFirstVar = scope.createTemporaryVariable(nestedFirst, nameHint = "nestedFirst")
irGet(nestedFirstVar)
} else {
nestedFirst
}
var nestedLastVar: IrVariable? = null
val nestedLast = nestedInfo.last
val nestedLastExpression = if (nestedLast.canHaveSideEffects) {
nestedLastVar = scope.createTemporaryVariable(nestedLast, nameHint = "nestedLast")
irGet(nestedLastVar)
} else {
nestedLast
}
// 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 ((nestedStep > 0 && inductionVar <= last) || (nestedStep < 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)`
val stepType = progressionType.stepType(context.irBuiltIns).toKotlinType()
val getProgressionLastElementFun = symbols.getProgressionLastElementByReturnType[stepType]
?: throw IllegalArgumentException("No `getProgressionLastElement` for step type $stepType")
return irCall(getProgressionLastElementFun).apply {
putValueArgument(
0, first.deepCopyWithSymbols().castIfNecessary(
progressionType.stepType(context.irBuiltIns),
progressionType.stepCastFunctionName
)
)
putValueArgument(
1, last.deepCopyWithSymbols().castIfNecessary(
progressionType.stepType(context.irBuiltIns),
progressionType.stepCastFunctionName
)
)
putValueArgument(
2, step.deepCopyWithSymbols().castIfNecessary(
progressionType.stepType(context.irBuiltIns),
progressionType.stepCastFunctionName
)
)
}
}
}
/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */
internal abstract class IndicesHandler(protected val context: CommonBackendContext) : ProgressionHandler {
@@ -243,6 +542,7 @@ internal abstract class IndicesHandler(protected val context: CommonBackendConte
}
internal class CollectionIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
override val matcher = SimpleCalleeMatcher {
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isCollection() } }
fqName { it == FqName("kotlin.collections.<get-indices>") }