Handle step progressions in ForLoopsLowering.
This commit is contained in:
committed by
max-kammerer
parent
38f0fd256e
commit
8a4185202f
@@ -6,10 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.backend.common.ir
|
package org.jetbrains.kotlin.backend.common.ir
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.CommonBackendContext
|
import org.jetbrains.kotlin.backend.common.CommonBackendContext
|
||||||
import org.jetbrains.kotlin.builtins.KOTLIN_REFLECT_FQ_NAME
|
import org.jetbrains.kotlin.builtins.*
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
|
||||||
import org.jetbrains.kotlin.builtins.PrimitiveType
|
|
||||||
import org.jetbrains.kotlin.builtins.UnsignedType
|
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
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 progressionClasses = listOf(charProgression, intProgression, longProgression)
|
||||||
val progressionClassesTypes = progressionClasses.map { it.descriptor.defaultType }.toSet()
|
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 any = symbolTable.referenceClass(builtIns.any)
|
||||||
val unit = symbolTable.referenceClass(builtIns.unit)
|
val unit = symbolTable.referenceClass(builtIns.unit)
|
||||||
|
|
||||||
|
|||||||
+41
-17
@@ -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. */
|
/** Returns the [IrType] of the `first`/`last` properties and elements in the progression. */
|
||||||
fun elementType(builtIns: IrBuiltIns): IrType = when (this) {
|
fun elementType(builtIns: IrBuiltIns): IrType = when (this) {
|
||||||
ProgressionType.INT_PROGRESSION -> builtIns.intType
|
INT_PROGRESSION -> builtIns.intType
|
||||||
ProgressionType.LONG_PROGRESSION -> builtIns.longType
|
LONG_PROGRESSION -> builtIns.longType
|
||||||
ProgressionType.CHAR_PROGRESSION -> builtIns.charType
|
CHAR_PROGRESSION -> builtIns.charType
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the [IrType] of the `step` property in the progression. */
|
/** Returns the [IrType] of the `step` property in the progression. */
|
||||||
fun stepType(builtIns: IrBuiltIns): IrType = when (this) {
|
fun stepType(builtIns: IrBuiltIns): IrType = when (this) {
|
||||||
ProgressionType.INT_PROGRESSION, ProgressionType.CHAR_PROGRESSION -> builtIns.intType
|
INT_PROGRESSION, CHAR_PROGRESSION -> builtIns.intType
|
||||||
ProgressionType.LONG_PROGRESSION -> builtIns.longType
|
LONG_PROGRESSION -> builtIns.longType
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromIrType(irType: IrType, symbols: Symbols<CommonBackendContext>): ProgressionType? = when {
|
fun fromIrType(irType: IrType, symbols: Symbols<CommonBackendContext>): ProgressionType? = when {
|
||||||
irType.isSubtypeOfClass(symbols.charProgression) -> ProgressionType.CHAR_PROGRESSION
|
irType.isSubtypeOfClass(symbols.charProgression) -> CHAR_PROGRESSION
|
||||||
irType.isSubtypeOfClass(symbols.intProgression) -> ProgressionType.INT_PROGRESSION
|
irType.isSubtypeOfClass(symbols.intProgression) -> INT_PROGRESSION
|
||||||
irType.isSubtypeOfClass(symbols.longProgression) -> ProgressionType.LONG_PROGRESSION
|
irType.isSubtypeOfClass(symbols.longProgression) -> LONG_PROGRESSION
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,26 +107,49 @@ internal class ProgressionHeaderInfo(
|
|||||||
val canOverflow: Boolean by lazy {
|
val canOverflow: Boolean by lazy {
|
||||||
if (canOverflow != null) return@lazy canOverflow
|
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 lastValueAsLong = last.constLongValue ?: return@lazy true // If "last" is not a const Number or Char.
|
||||||
val constLimitAsLong = when (direction) {
|
when (direction) {
|
||||||
ProgressionDirection.UNKNOWN ->
|
ProgressionDirection.UNKNOWN ->
|
||||||
// If we don't know the direction, we can't be sure which limit to use.
|
// If we don't know the direction, we can't be sure which limit to use.
|
||||||
return@lazy true
|
true
|
||||||
ProgressionDirection.DECREASING ->
|
ProgressionDirection.DECREASING -> {
|
||||||
when (progressionType) {
|
val constLimitAsLong = when (progressionType) {
|
||||||
ProgressionType.INT_PROGRESSION -> Int.MIN_VALUE.toLong()
|
ProgressionType.INT_PROGRESSION -> Int.MIN_VALUE.toLong()
|
||||||
ProgressionType.CHAR_PROGRESSION -> Char.MIN_VALUE.toLong()
|
ProgressionType.CHAR_PROGRESSION -> Char.MIN_VALUE.toLong()
|
||||||
ProgressionType.LONG_PROGRESSION -> Long.MIN_VALUE
|
ProgressionType.LONG_PROGRESSION -> Long.MIN_VALUE
|
||||||
}
|
}
|
||||||
ProgressionDirection.INCREASING ->
|
lastValueAsLong < (constLimitAsLong - stepValueAsLong)
|
||||||
when (progressionType) {
|
}
|
||||||
|
ProgressionDirection.INCREASING -> {
|
||||||
|
val constLimitAsLong = when (progressionType) {
|
||||||
ProgressionType.INT_PROGRESSION -> Int.MAX_VALUE.toLong()
|
ProgressionType.INT_PROGRESSION -> Int.MAX_VALUE.toLong()
|
||||||
ProgressionType.CHAR_PROGRESSION -> Char.MAX_VALUE.toLong()
|
ProgressionType.CHAR_PROGRESSION -> Char.MAX_VALUE.toLong()
|
||||||
ProgressionType.LONG_PROGRESSION -> Long.MAX_VALUE
|
ProgressionType.LONG_PROGRESSION -> Long.MAX_VALUE
|
||||||
}
|
}
|
||||||
|
lastValueAsLong > (constLimitAsLong - stepValueAsLong)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
constLimitAsLong == lastValueAsLong
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun asReversed() = ProgressionHeaderInfo(
|
override fun asReversed() = ProgressionHeaderInfo(
|
||||||
@@ -226,7 +249,8 @@ internal class HeaderInfoBuilder(context: CommonBackendContext, private val scop
|
|||||||
CharSequenceIndicesHandler(context),
|
CharSequenceIndicesHandler(context),
|
||||||
UntilHandler(context, progressionElementTypes),
|
UntilHandler(context, progressionElementTypes),
|
||||||
DownToHandler(context, progressionElementTypes),
|
DownToHandler(context, progressionElementTypes),
|
||||||
RangeToHandler(context, progressionElementTypes)
|
RangeToHandler(context, progressionElementTypes),
|
||||||
|
StepHandler(context, this)
|
||||||
)
|
)
|
||||||
|
|
||||||
private val reversedHandler = ReversedHandler(context, this)
|
private val reversedHandler = ReversedHandler(context, this)
|
||||||
|
|||||||
+303
-3
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.backend.common.lower.loops
|
package org.jetbrains.kotlin.backend.common.lower.loops
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.CommonBackendContext
|
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.createIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.common.lower.matchers.Quantifier
|
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.SimpleCalleeMatcher
|
||||||
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.ir.util.*
|
|||||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */
|
/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */
|
||||||
internal class RangeToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
|
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) {
|
// if (inductionVar <= last && B != MIN_VALUE) {
|
||||||
// // Loop is not empty
|
// // Loop is not empty
|
||||||
// do {
|
// do {
|
||||||
// val loopVar = inductionVar
|
// val i = inductionVar
|
||||||
// inductionVar++
|
// inductionVar++
|
||||||
// // Loop body
|
// // Loop body
|
||||||
// } while (inductionVar <= last)
|
// } while (inductionVar <= last)
|
||||||
@@ -108,10 +110,10 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
|
|||||||
// // Standard form of loop over progression
|
// // Standard form of loop over progression
|
||||||
// var inductionVar = untilReceiverValue
|
// var inductionVar = untilReceiverValue
|
||||||
// val last = untilArg - 1
|
// val last = untilArg - 1
|
||||||
// if (inductionVar <= last && untilFunArg != MIN_VALUE) {
|
// if (inductionVar <= last && untilArg != MIN_VALUE) {
|
||||||
// // Loop is not empty
|
// // Loop is not empty
|
||||||
// do {
|
// do {
|
||||||
// val loopVar = inductionVar
|
// val i = inductionVar
|
||||||
// inductionVar++
|
// inductionVar++
|
||||||
// // Loop body
|
// // Loop body
|
||||||
// } while (inductionVar <= last)
|
// } 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. */
|
/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */
|
||||||
internal abstract class IndicesHandler(protected val context: CommonBackendContext) : ProgressionHandler {
|
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) {
|
internal class CollectionIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
|
||||||
|
|
||||||
override val matcher = SimpleCalleeMatcher {
|
override val matcher = SimpleCalleeMatcher {
|
||||||
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isCollection() } }
|
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isCollection() } }
|
||||||
fqName { it == FqName("kotlin.collections.<get-indices>") }
|
fqName { it == FqName("kotlin.collections.<get-indices>") }
|
||||||
|
|||||||
Reference in New Issue
Block a user