ForLoopsLowering: Convert ProgressionType from enum to sealed class and

move more logic into the class.

This refactoring simplifies their usage with fewer `when` switches and
passing around of Symbols.
This commit is contained in:
Mark Punzalan
2020-05-08 22:49:45 -07:00
committed by Alexander Udalov
parent 177967258b
commit b85da8411d
4 changed files with 468 additions and 434 deletions
@@ -10,163 +10,13 @@ import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.backend.common.lower.matchers.IrCallMatcher
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
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.isUnsigned
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
/** Represents a progression type in the Kotlin stdlib. */
internal enum class ProgressionType(
private val elementCastFunctionName: Name,
private val stepCastFunctionName: Name,
val isLong: Boolean = false,
val isUnsigned: Boolean = false
) {
INT_PROGRESSION(Name.identifier("toInt"), Name.identifier("toInt")),
LONG_PROGRESSION(Name.identifier("toLong"), Name.identifier("toLong"), isLong = true),
CHAR_PROGRESSION(Name.identifier("toChar"), Name.identifier("toInt")),
UINT_PROGRESSION(Name.identifier("toInt"), Name.identifier("toInt"), isUnsigned = true),
ULONG_PROGRESSION(Name.identifier("toLong"), Name.identifier("toLong"), isLong = true, isUnsigned = true);
/** Returns the [IrType] of the `first`/`last` properties and elements in the progression. */
fun elementType(symbols: Symbols<CommonBackendContext>): IrType = when (this) {
INT_PROGRESSION, UINT_PROGRESSION -> symbols.int
LONG_PROGRESSION, ULONG_PROGRESSION -> symbols.long
CHAR_PROGRESSION -> symbols.char
}.defaultType
/** Returns the [IrClassSymbol] of the `first`/`last` properties and elements in the progression. */
fun elementClassifier(symbols: Symbols<CommonBackendContext>): IrClassSymbol = when (this) {
INT_PROGRESSION -> symbols.int
LONG_PROGRESSION -> symbols.long
CHAR_PROGRESSION -> symbols.char
UINT_PROGRESSION -> symbols.uInt!!
ULONG_PROGRESSION -> symbols.uLong!!
}
/** Returns the [IrType] of the `step` property in the progression. */
fun stepType(builtIns: IrBuiltIns): IrType = when (this) {
INT_PROGRESSION, CHAR_PROGRESSION, UINT_PROGRESSION -> builtIns.intType
LONG_PROGRESSION, ULONG_PROGRESSION -> builtIns.longType
}
/** Returns the [IrClassSymbol] of the `step` property type constructor in the progression. */
fun stepClassifier(builtIns: IrBuiltIns): IrClassSymbol = when(this) {
INT_PROGRESSION, CHAR_PROGRESSION , UINT_PROGRESSION-> builtIns.intClass
LONG_PROGRESSION, ULONG_PROGRESSION -> builtIns.longClass
}
/** Returns the [IrType] used in loop conditions (`buildLoopCondition()`) and when calling `getProgressionLastElement()`. */
fun compareType(symbols: Symbols<CommonBackendContext>): IrType = when (this) {
INT_PROGRESSION -> symbols.int
LONG_PROGRESSION -> symbols.long
CHAR_PROGRESSION -> symbols.char
UINT_PROGRESSION -> symbols.uInt!!
ULONG_PROGRESSION -> symbols.uLong!!
}.defaultType
fun castElementIfNecessary(element: IrExpression, context: CommonBackendContext) =
element.castIfNecessary(elementType(context.ir.symbols), elementCastFunctionName)
fun castStepIfNecessary(step: IrExpression, context: CommonBackendContext) =
step.castIfNecessary(stepType(context.irBuiltIns), stepCastFunctionName)
private fun IrExpression.castIfNecessary(targetType: IrType, numberCastFunctionName: Name) =
// This expression's type could be Nothing from an exception throw.
if (type == targetType || type.isNothing()) {
this
} else {
val castFun = type.getClass()!!.functions.single {
it.name == numberCastFunctionName &&
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null && it.valueParameters.isEmpty()
}
IrCallImpl(startOffset, endOffset, castFun.returnType, castFun.symbol)
.apply { dispatchReceiver = this@castIfNecessary }
}
fun coerceToUnsigned(value: IrExpression, symbols: Symbols<CommonBackendContext>): IrExpression {
if (!isUnsigned || value.type.isUnsigned()) return value
val unsafeCoerceIntrinsic = symbols.unsafeCoerceIntrinsic
return if (unsafeCoerceIntrinsic != null) {
val from = when (this) {
UINT_PROGRESSION -> symbols.int.defaultType
ULONG_PROGRESSION -> symbols.long.defaultType
else -> error("Unexpected progression type")
}
val to = when (this) {
UINT_PROGRESSION -> symbols.uInt!!.defaultType
ULONG_PROGRESSION -> symbols.uLong!!.defaultType
else -> error("Unexpected progression type")
}
IrCallImpl(value.startOffset, value.endOffset, to, unsafeCoerceIntrinsic).apply {
putTypeArgument(0, from)
putTypeArgument(1, to)
putValueArgument(0, value)
}
} else {
val conversionFunctionMap = when (this) {
UINT_PROGRESSION -> symbols.toUIntByExtensionReceiver
ULONG_PROGRESSION -> symbols.toULongByExtensionReceiver
else -> error("Unexpected progression type")
}
val from = when (this) {
UINT_PROGRESSION -> symbols.int.defaultType
ULONG_PROGRESSION -> symbols.long.defaultType
else -> error("Unexpected progression type")
}.toKotlinType()
val castFun = conversionFunctionMap.getValue(from)
IrCallImpl(value.startOffset, value.endOffset, castFun.owner.returnType, castFun).apply {
extensionReceiver = value
}
}
}
fun coerceToSigned(value: IrExpression, symbols: Symbols<CommonBackendContext>): IrExpression {
if (!isUnsigned || !value.type.isUnsigned()) return value
val unsafeCoerceIntrinsic = symbols.unsafeCoerceIntrinsic
return if (unsafeCoerceIntrinsic != null) {
val from = when (this) {
UINT_PROGRESSION -> symbols.uInt!!.defaultType
ULONG_PROGRESSION -> symbols.uLong!!.defaultType
else -> error("Unexpected progression type")
}
val to = when (this) {
UINT_PROGRESSION -> symbols.int.defaultType
ULONG_PROGRESSION -> symbols.long.defaultType
else -> error("Unexpected progression type")
}
IrCallImpl(value.startOffset, value.endOffset, to, unsafeCoerceIntrinsic).apply {
putTypeArgument(0, from)
putTypeArgument(1, to)
putValueArgument(0, value)
}
} else {
castElementIfNecessary(value, symbols.context)
}
}
companion object {
fun fromIrType(irType: IrType, symbols: Symbols<CommonBackendContext>): ProgressionType? = when {
irType.isSubtypeOfClass(symbols.charProgression) -> CHAR_PROGRESSION
irType.isSubtypeOfClass(symbols.intProgression) -> INT_PROGRESSION
irType.isSubtypeOfClass(symbols.longProgression) -> LONG_PROGRESSION
symbols.uIntProgression != null && irType.isSubtypeOfClass(symbols.uIntProgression) -> UINT_PROGRESSION
symbols.uLongProgression != null && irType.isSubtypeOfClass(symbols.uLongProgression) -> ULONG_PROGRESSION
else -> null
}
}
}
internal enum class ProgressionDirection {
DECREASING {
override fun asReversed() = INCREASING
@@ -252,24 +102,16 @@ internal class ProgressionHeaderInfo(
// - `0..someLast()` CAN overflow (we don't know the direction)
// - `someProgression()` CAN overflow (we don't know the direction)
if (progressionType.isUnsigned) {
if (progressionType is UnsignedProgressionType) {
// "step" is still signed for unsigned progressions.
val lastValueAsULong = last.constLongValue?.toULong() ?: return@lazy true // If "last" is not a const Number or Char.
when (direction) {
ProgressionDirection.DECREASING -> {
val constLimitAsULong = when (progressionType) {
ProgressionType.UINT_PROGRESSION -> UInt.MIN_VALUE.toULong()
ProgressionType.ULONG_PROGRESSION -> ULong.MIN_VALUE
else -> error("Unexpected progression type")
}
val constLimitAsULong = progressionType.minValueAsLong.toULong()
lastValueAsULong < (constLimitAsULong - stepValueAsLong.toULong())
}
ProgressionDirection.INCREASING -> {
val constLimitAsULong = when (progressionType) {
ProgressionType.UINT_PROGRESSION -> UInt.MAX_VALUE.toULong()
ProgressionType.ULONG_PROGRESSION -> ULong.MAX_VALUE
else -> error("Unexpected progression type")
}
val constLimitAsULong = progressionType.maxValueAsLong.toULong()
lastValueAsULong > (constLimitAsULong - stepValueAsLong.toULong())
}
else -> error("Unexpected progression direction")
@@ -278,21 +120,11 @@ internal class ProgressionHeaderInfo(
val lastValueAsLong = last.constLongValue ?: return@lazy true // If "last" is not a const Number or Char.
when (direction) {
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
else -> error("Unexpected progression type")
}
val constLimitAsLong = progressionType.minValueAsLong
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
else -> error("Unexpected progression type")
}
val constLimitAsLong = progressionType.maxValueAsLong
lastValueAsLong > (constLimitAsLong - stepValueAsLong)
}
else -> error("Unexpected progression direction")
@@ -317,6 +149,7 @@ internal class ProgressionHeaderInfo(
* The internal induction variable used is an Int.
*/
internal class IndexedGetHeaderInfo(
symbols: Symbols<CommonBackendContext>,
first: IrExpression,
last: IrExpression,
step: IrExpression,
@@ -324,7 +157,7 @@ internal class IndexedGetHeaderInfo(
val objectVariable: IrVariable,
val expressionHandler: IndexedGetIterationHandler
) : NumericHeaderInfo(
ProgressionType.INT_PROGRESSION,
IntProgressionType(symbols),
first,
last,
step,
@@ -435,6 +268,7 @@ internal abstract class HeaderInfoBuilder(context: CommonBackendContext, private
override fun visitElement(element: IrElement, data: IrCall?): HeaderInfo? = null
/** Builds a [HeaderInfo] for iterable expressions that are calls (e.g., `.reversed()`, `.indices`. */
@ExperimentalUnsignedTypes
override fun visitCall(iterable: IrCall, iteratorCall: IrCall?): HeaderInfo? {
// Return the HeaderInfo from the first successful match.
// First, try to match a `reversed()` or `withIndex()` call.
@@ -78,51 +78,48 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
get() = field.deepCopyWithSymbols()
protected val symbols = context.ir.symbols
private val elementType: IrType
init {
with(builder) {
elementType = headerInfo.progressionType.elementType(symbols)
with(headerInfo.progressionType) {
// For this loop:
//
// for (i in first()..last() step step())
//
// We need to cast first(), last(). and step() to conform to the progression type so
// that operations on the induction variable within the loop are more efficient.
//
// In the above example, if first() is a Long and last() is an Int, this creates a
// LongProgression so last() should be cast to a Long.
inductionVariable =
scope.createTmpVariable(
headerInfo.first.asElementType(),
nameHint = "inductionVariable",
isMutable = true,
irType = elementClass.defaultType
)
// For this loop:
//
// for (i in first()..last() step step())
//
// We need to cast first(), last(). and step() to conform to the progression type so
// that operations on the induction variable within the loop are more efficient.
//
// In the above example, if first() is a Long and last() is an Int, this creates a
// LongProgression so last() should be cast to a Long.
inductionVariable = scope.createTmpVariable(
headerInfo.progressionType.castElementIfNecessary(headerInfo.first, context),
nameHint = "inductionVariable",
isMutable = true
)
// Due to features of PSI2IR we can obtain nullable arguments here while actually
// they are non-nullable (the frontend takes care about this). So we need to cast
// them to non-nullable.
// TODO: Confirm if casting to non-nullable is still necessary
val last = headerInfo.last.asElementType()
// Due to features of PSI2IR we can obtain nullable arguments here while actually
// they are non-nullable (the frontend takes care about this). So we need to cast
// them to non-nullable.
// TODO: Confirm if casting to non-nullable is still necessary
val last = ensureNotNullable(headerInfo.progressionType.castElementIfNecessary(headerInfo.last, context))
lastVariableIfCanCacheLast = if (headerInfo.canCacheLast) {
scope.createTmpVariable(last, nameHint = "last")
} else null
lastVariableIfCanCacheLast = if (headerInfo.canCacheLast) {
scope.createTmpVariable(
last,
nameHint = "last"
)
} else null
lastExpression = if (headerInfo.canCacheLast) irGet(lastVariableIfCanCacheLast!!) else last
lastExpression = if (headerInfo.canCacheLast) irGet(lastVariableIfCanCacheLast!!) else last
val stepType = headerInfo.progressionType.stepType(context.irBuiltIns)
val (tmpStepVar, tmpStepExpression) =
createTemporaryVariableIfNecessary(
ensureNotNullable(headerInfo.progressionType.castStepIfNecessary(headerInfo.step, context)),
nameHint = "step",
irType = stepType
)
stepVariable = tmpStepVar
stepExpression = tmpStepExpression
val (tmpStepVar, tmpStepExpression) =
createTemporaryVariableIfNecessary(
ensureNotNullable(headerInfo.step.asStepType()),
nameHint = "step",
irType = stepClass.defaultType
)
stepVariable = tmpStepVar
stepExpression = tmpStepExpression
}
}
}
@@ -135,112 +132,117 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
/** Statement used to increment the induction variable. */
protected fun incrementInductionVariable(builder: DeclarationIrBuilder): IrStatement = with(builder) {
// inductionVariable = inductionVariable + step
// NOTE: We cannot use `stepExpression.type` to match the value parameter type because it may be of type `Nothing`.
// This happens in the case of an illegal step where the "step" is actually a `throw IllegalArgumentException(...)`.
val stepType = headerInfo.progressionType.stepType(context.irBuiltIns)
val plusFun = elementType.getClass()!!.functions.single {
it.name == OperatorNameConventions.PLUS &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type == stepType
}
irSetVar(
inductionVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(inductionVariable),
stepExpression
with(headerInfo.progressionType) {
// inductionVariable = inductionVariable + step
// NOTE: We cannot use `stepExpression.type` to match the value parameter type because it may be of type `Nothing`.
// This happens in the case of an illegal step where the "step" is actually a `throw IllegalArgumentException(...)`.
val stepType = stepClass.defaultType
val plusFun = elementClass.defaultType.getClass()!!.functions.single {
it.name == OperatorNameConventions.PLUS &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type == stepType
}
irSetVar(
inductionVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(inductionVariable),
stepExpression
)
)
)
}
}
protected fun buildLoopCondition(builder: DeclarationIrBuilder): IrExpression =
protected fun buildLoopCondition(builder: DeclarationIrBuilder): IrExpression {
with(builder) {
val builtIns = context.irBuiltIns
val progressionType = headerInfo.progressionType
val progressionCompareType = progressionType.compareType(symbols)
with(headerInfo.progressionType) {
val builtIns = context.irBuiltIns
// `compareTo` must be used for UInt/ULong; they don't have intrinsic comparison operators.
val intCompFun = if (isLastInclusive) {
builtIns.lessOrEqualFunByOperandType.getValue(builtIns.intClass)
} else {
builtIns.lessFunByOperandType.getValue(builtIns.intClass)
}
val elementCompareToFun = progressionCompareType.getClass()!!.functions.single {
it.name == OperatorNameConventions.COMPARE_TO &&
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null &&
it.valueParameters.size == 1 && it.valueParameters[0].type == progressionCompareType
}
val elementCompFun =
if (isLastInclusive) {
builtIns.lessOrEqualFunByOperandType[progressionCompareType.classifierOrFail]
// Bounds are signed for unsigned progressions but bound comparisons should be done as unsigned, to ensure that the
// correct comparison function is used (`UInt/ULongCompare`). Also, `compareTo` must be used for UInt/ULong;
// they don't have intrinsic comparison operators.
val intCompFun = if (isLastInclusive) {
builtIns.lessOrEqualFunByOperandType.getValue(builtIns.intClass)
} else {
builtIns.lessFunByOperandType[progressionCompareType.classifierOrFail]
builtIns.lessFunByOperandType.getValue(builtIns.intClass)
}
val unsignedCompareToFun = if (this is UnsignedProgressionType) {
unsignedType.getClass()!!.functions.single {
it.name == OperatorNameConventions.COMPARE_TO &&
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null &&
it.valueParameters.size == 1 && it.valueParameters[0].type == unsignedType
}
} else null
fun conditionForDecreasing(): IrExpression =
// last <= inductionVar (use `<` if last is exclusive)
if (progressionType.isUnsigned) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(elementCompareToFun).apply {
dispatchReceiver = progressionType.coerceToUnsigned(lastExpression, symbols)
putValueArgument(0, progressionType.coerceToUnsigned(irGet(inductionVariable), symbols))
})
putValueArgument(1, irInt(0))
val elementCompFun =
if (isLastInclusive) {
builtIns.lessOrEqualFunByOperandType[elementClass.symbol]
} else {
builtIns.lessFunByOperandType[elementClass.symbol]
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, lastExpression)
putValueArgument(1, irGet(inductionVariable))
}
}
fun conditionForIncreasing(): IrExpression =
// inductionVar <= last (use `<` if last is exclusive)
if (progressionType.isUnsigned) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(elementCompareToFun).apply {
dispatchReceiver = progressionType.coerceToUnsigned(irGet(inductionVariable), symbols)
putValueArgument(0, progressionType.coerceToUnsigned(lastExpression, symbols))
})
putValueArgument(1, irInt(0))
fun conditionForDecreasing(): IrExpression =
// last <= inductionVar (use `<` if last is exclusive)
if (this is UnsignedProgressionType) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(unsignedCompareToFun!!).apply {
dispatchReceiver = lastExpression.asUnsigned()
putValueArgument(0, irGet(inductionVariable).asUnsigned())
})
putValueArgument(1, irInt(0))
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, lastExpression)
putValueArgument(1, irGet(inductionVariable))
}
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, lastExpression)
}
}
// The default condition depends on the direction.
when (headerInfo.direction) {
ProgressionDirection.DECREASING -> conditionForDecreasing()
ProgressionDirection.INCREASING -> conditionForIncreasing()
ProgressionDirection.UNKNOWN -> {
// If the direction is unknown, we check depending on the "step" value:
// // (use `<` if last is exclusive)
// (step > 0 && inductionVar <= last) || (step < 0 || last <= inductionVar)
val stepTypeClassifier = progressionType.stepClassifier(builtIns)
context.oror(
context.andand(
irCall(builtIns.greaterFunByOperandType.getValue(stepTypeClassifier)).apply {
putValueArgument(0, stepExpression)
putValueArgument(1, if (progressionType.isLong) irLong(0) else irInt(0))
},
conditionForIncreasing()
),
context.andand(
irCall(builtIns.lessFunByOperandType.getValue(stepTypeClassifier)).apply {
putValueArgument(0, stepExpression)
putValueArgument(1, if (progressionType.isLong) irLong(0) else irInt(0))
},
conditionForDecreasing()
fun conditionForIncreasing(): IrExpression =
// inductionVar <= last (use `<` if last is exclusive)
if (this is UnsignedProgressionType) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(unsignedCompareToFun!!).apply {
dispatchReceiver = irGet(inductionVariable).asUnsigned()
putValueArgument(0, lastExpression.asUnsigned())
})
putValueArgument(1, irInt(0))
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, lastExpression)
}
}
// The default condition depends on the direction.
return when (headerInfo.direction) {
ProgressionDirection.DECREASING -> conditionForDecreasing()
ProgressionDirection.INCREASING -> conditionForIncreasing()
ProgressionDirection.UNKNOWN -> {
// If the direction is unknown, we check depending on the "step" value:
// // (use `<` if last is exclusive)
// (step > 0 && inductionVar <= last) || (step < 0 || last <= inductionVar)
context.oror(
context.andand(
irCall(builtIns.greaterFunByOperandType.getValue(stepClass.symbol)).apply {
putValueArgument(0, stepExpression)
putValueArgument(1, zeroStepExpression())
},
conditionForIncreasing()
),
context.andand(
irCall(builtIns.lessFunByOperandType.getValue(stepClass.symbol)).apply {
putValueArgument(0, stepExpression)
putValueArgument(1, zeroStepExpression())
},
conditionForDecreasing()
)
)
)
}
}
}
}
}
}
internal class ProgressionLoopHeader(
@@ -283,7 +285,14 @@ internal class ProgressionLoopHeader(
isMutable = true
)
} else {
loopVariable?.initializer = headerInfo.progressionType.coerceToUnsigned(irGet(inductionVariable), symbols)
loopVariable?.initializer = irGet(inductionVariable).let {
headerInfo.progressionType.run {
if (this is UnsignedProgressionType) {
// The induction variable is signed for unsigned progressions but the loop variable should be unsigned.
it.asUnsigned()
} else it
}
}
loopVariable
}
@@ -307,8 +316,16 @@ internal class ProgressionLoopHeader(
// } while (loopVar != last)
// }
IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
val loopVariableExpression = irGet(loopVariable!!).let {
headerInfo.progressionType.run {
if (this is UnsignedProgressionType) {
// The loop variable is signed but bounds are signed for unsigned progressions.
it.asSigned()
} else it
}
}
label = oldLoop.label
condition = irNotEquals(headerInfo.progressionType.coerceToSigned(irGet(loopVariable!!), symbols), lastExpression)
condition = irNotEquals(loopVariableExpression, lastExpression)
body = newBody
}
} else {
@@ -88,100 +88,94 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
@ExperimentalUnsignedTypes
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// `A until B` is essentially the same as `A .. (B-1)`. However, B could be MIN_VALUE and hence `(B-1)` could underflow.
// If B is MIN_VALUE, then `A until B` is an empty range. We handle this special case be adding an additional "not empty"
// condition in the lowered for-loop. Therefore the following for-loop:
//
// for (i in A until B) { // Loop body }
//
// is lowered into:
//
// var inductionVar = A
// val last = B - 1
// if (inductionVar <= last && B != MIN_VALUE) {
// // Loop is not empty
// do {
// val 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)!!
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 = data.castElementIfNecessary(untilArg, this@UntilHandler.context)
// 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")
// 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)
}
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
)
}
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 (minValueAsLong, minValueIrConst) =
when (data) {
ProgressionType.INT_PROGRESSION -> Pair(Int.MIN_VALUE.toLong(), irInt(Int.MIN_VALUE))
ProgressionType.CHAR_PROGRESSION -> Pair(Char.MIN_VALUE.toLong(), irChar(Char.MIN_VALUE))
ProgressionType.LONG_PROGRESSION -> Pair(Long.MIN_VALUE, irLong(Long.MIN_VALUE))
ProgressionType.UINT_PROGRESSION -> Pair(UInt.MIN_VALUE.toLong(), irInt(UInt.MIN_VALUE.toInt()))
ProgressionType.ULONG_PROGRESSION -> Pair(ULong.MIN_VALUE.toLong(), irLong(ULong.MIN_VALUE.toLong()))
}
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(), minValueIrConst)
it == minValueAsLong ->
// Hardcode "false" as additional condition so that the progression is considered empty.
// The entire lowered loop becomes a candidate for dead code elimination, depending on backend.
irFalse()
else ->
// We know that untilArg != MIN_VALUE, so the additional condition is not necessary.
null
}
}
ProgressionHeaderInfo(
data,
first = first,
last = last,
step = irInt(1),
canOverflow = false,
additionalVariables = additionalVariables,
additionalNotEmptyCondition = additionalNotEmptyCondition,
direction = ProgressionDirection.INCREASING
)
}
private fun isAdditionalNotEmptyConditionNeeded(receiverType: IrType, argType: IrType): Boolean {
@@ -270,9 +264,9 @@ internal class StepHandler(
// 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[data.stepClassifier(context.irBuiltIns)]!!
val zeroStep = if (data.isLong) irLong(0) else irInt(0)
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()
@@ -477,44 +471,25 @@ internal class StepHandler(
// - 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)
//
// We make sure to retrieve the correct symbol and use the correct argument types.
return with(progressionType) {
val returnTypeClassifier = if (progressionType.isUnsigned) {
progressionType.elementClassifier(symbols)
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 {
progressionType.stepClassifier(context.irBuiltIns)
}
val getProgressionLastElementFun = symbols.getProgressionLastElementByReturnType[returnTypeClassifier]
?: throw IllegalArgumentException("No `getProgressionLastElement` for return type ${returnTypeClassifier.defaultType}")
val call = irCall(getProgressionLastElementFun).apply {
if (isUnsigned) {
putValueArgument(
0,
coerceToUnsigned(
castElementIfNecessary(first.deepCopyWithSymbols(), this@StepHandler.context),
symbols
)
)
putValueArgument(
1,
coerceToUnsigned(
castElementIfNecessary(last.deepCopyWithSymbols(), this@StepHandler.context),
symbols
)
)
} else {
putValueArgument(0, castStepIfNecessary(first.deepCopyWithSymbols(), this@StepHandler.context))
putValueArgument(1, castStepIfNecessary(last.deepCopyWithSymbols(), this@StepHandler.context))
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())
}
putValueArgument(2, castStepIfNecessary(step.deepCopyWithSymbols(), this@StepHandler.context))
}
if (isUnsigned) {
// Bounds are signed for unsigned progressions.
coerceToSigned(call, symbols)
} else {
call
}
}
}
@@ -608,8 +583,10 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte
private val symbols = context.ir.symbols
@ExperimentalUnsignedTypes
override fun matchIterable(expression: IrExpression) = ProgressionType.fromIrType(expression.type, symbols) != null
@ExperimentalUnsignedTypes
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.
@@ -666,6 +643,7 @@ internal abstract class IndexedGetIterationHandler(
}
IndexedGetHeaderInfo(
this@IndexedGetIterationHandler.context.ir.symbols,
first = irInt(0),
last = last,
step = irInt(1),
@@ -0,0 +1,205 @@
/*
* 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
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.ir.builders.irChar
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.builders.irLong
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.name.Name
/** Represents a progression type in the Kotlin stdlib. */
internal sealed class ProgressionType(
val elementClass: IrClass,
val stepClass: IrClass,
val minValueAsLong: Long,
val maxValueAsLong: Long,
val getProgressionLastElementFunction: IrSimpleFunctionSymbol?
) {
abstract fun DeclarationIrBuilder.minValueExpression(): IrExpression
abstract fun DeclarationIrBuilder.zeroStepExpression(): IrExpression
fun IrExpression.asElementType() = castIfNecessary(elementClass)
fun IrExpression.asStepType() = castIfNecessary(stepClass)
private fun IrExpression.castIfNecessary(targetClass: IrClass) =
// This expression's type could be Nothing from an exception throw.
if (type == targetClass.defaultType || type.isNothing()) {
this
} else {
val numberCastFunctionName = Name.identifier("to${targetClass.name.asString()}")
val castFun = type.getClass()!!.functions.single {
it.name == numberCastFunctionName &&
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null && it.valueParameters.isEmpty()
}
IrCallImpl(startOffset, endOffset, castFun.returnType, castFun.symbol)
.apply { dispatchReceiver = this@castIfNecessary }
}
companion object {
@ExperimentalUnsignedTypes
fun fromIrType(irType: IrType, symbols: Symbols<CommonBackendContext>): ProgressionType? = when {
irType.isSubtypeOfClass(symbols.charProgression) -> CharProgressionType(symbols)
irType.isSubtypeOfClass(symbols.intProgression) -> IntProgressionType(symbols)
irType.isSubtypeOfClass(symbols.longProgression) -> LongProgressionType(symbols)
symbols.uIntProgression != null && irType.isSubtypeOfClass(symbols.uIntProgression) -> UIntProgressionType(symbols)
symbols.uLongProgression != null && irType.isSubtypeOfClass(symbols.uLongProgression) -> ULongProgressionType(symbols)
else -> null
}
}
}
internal class IntProgressionType(symbols: Symbols<CommonBackendContext>) :
ProgressionType(
elementClass = symbols.int.owner,
stepClass = symbols.int.owner,
minValueAsLong = Int.MIN_VALUE.toLong(),
maxValueAsLong = Int.MAX_VALUE.toLong(),
// Uses `getProgressionLastElement(Int, Int, Int): Int`
getProgressionLastElementFunction = symbols.getProgressionLastElementByReturnType[symbols.int]
) {
override fun DeclarationIrBuilder.minValueExpression() = irInt(Int.MIN_VALUE)
override fun DeclarationIrBuilder.zeroStepExpression() = irInt(0)
}
internal class LongProgressionType(symbols: Symbols<CommonBackendContext>) :
ProgressionType(
elementClass = symbols.long.owner,
stepClass = symbols.long.owner,
minValueAsLong = Long.MIN_VALUE,
maxValueAsLong = Long.MAX_VALUE,
// Uses `getProgressionLastElement(Long, Long, Long): Long`
getProgressionLastElementFunction = symbols.getProgressionLastElementByReturnType[symbols.long]
) {
override fun DeclarationIrBuilder.minValueExpression() = irLong(Long.MIN_VALUE)
override fun DeclarationIrBuilder.zeroStepExpression() = irLong(0)
}
internal class CharProgressionType(symbols: Symbols<CommonBackendContext>) :
ProgressionType(
elementClass = symbols.char.owner,
stepClass = symbols.int.owner,
minValueAsLong = Char.MIN_VALUE.toLong(),
maxValueAsLong = Char.MAX_VALUE.toLong(),
// Uses `getProgressionLastElement(Int, Int, Int): Int`
getProgressionLastElementFunction = symbols.getProgressionLastElementByReturnType[symbols.int]
) {
override fun DeclarationIrBuilder.minValueExpression() = irChar(Char.MIN_VALUE)
override fun DeclarationIrBuilder.zeroStepExpression() = irInt(0)
}
// A note on how ForLoopsLowering handles unsigned progressions:
//
// We use signed numbers for the elements (induction variable and bounds) to limit calls to UInt/ULong constructors. For example,
// `inductionVar += step.toUInt()` would cause instantiation during `toUInt()` (conversion necessary since `step` is signed) and on
// assignment to `inductionVar`. There are a few places where we _have_ to convert either the induction variable or bounds to unsigned:
//
// 1. When comparing in the loop conditions (e.g., `inductionVar <= last`). This ensures that the correct comparison function is used
// (`UInt/ULongCompare`) instead of regular Int/Long comparisons.
// 2. When assigning to the loop variable, which should have an unsigned type.
// 3. When calling `getProgressionLastElement` for stepped progressions. There are overloads which take unsigned numbers, which should
// be used to ensure the calculation is correct.
//
// We use the `<unsafe-coerce>` intrinsic if available (currently JVM-only) to perform the conversions, and fallback to calling
// `to(U)Int/(U)Long()` functions otherwise.
internal abstract class UnsignedProgressionType(
symbols: Symbols<CommonBackendContext>,
elementClass: IrClass,
stepClass: IrClass,
minValueAsLong: Long,
maxValueAsLong: Long,
getProgressionLastElementFunction: IrSimpleFunctionSymbol?,
val unsignedType: IrType,
private val unsignedConversionFunction: IrSimpleFunctionSymbol
) : ProgressionType(elementClass, stepClass, minValueAsLong, maxValueAsLong, getProgressionLastElementFunction) {
private val unsafeCoerceIntrinsic = symbols.unsafeCoerceIntrinsic
fun IrExpression.asUnsigned(): IrExpression {
val fromType = type
if (type == unsignedType) return this
return if (unsafeCoerceIntrinsic != null) {
IrCallImpl(startOffset, endOffset, unsignedType, unsafeCoerceIntrinsic).apply {
putTypeArgument(0, fromType)
putTypeArgument(1, unsignedType)
putValueArgument(0, this@asUnsigned)
}
} else {
// Fallback to calling `toUInt/ULong()` extension function.
IrCallImpl(startOffset, endOffset, unsignedConversionFunction.owner.returnType, unsignedConversionFunction).apply {
extensionReceiver = this@asUnsigned
}
}
}
fun IrExpression.asSigned(): IrExpression {
val toType = elementClass.defaultType
if (type == toType) return this
return if (unsafeCoerceIntrinsic != null) {
IrCallImpl(startOffset, endOffset, toType, unsafeCoerceIntrinsic).apply {
putTypeArgument(0, unsignedType)
putTypeArgument(1, toType)
putValueArgument(0, this@asSigned)
}
} else {
// Fallback to calling `toInt/Long()` function.
asElementType()
}
}
}
@ExperimentalUnsignedTypes
internal class UIntProgressionType(symbols: Symbols<CommonBackendContext>) :
UnsignedProgressionType(
symbols,
elementClass = symbols.int.owner,
stepClass = symbols.int.owner,
minValueAsLong = UInt.MIN_VALUE.toLong(),
maxValueAsLong = UInt.MAX_VALUE.toLong(),
// Uses `getProgressionLastElement(UInt, UInt, Int): UInt`
getProgressionLastElementFunction = symbols.getProgressionLastElementByReturnType[symbols.uInt!!],
unsignedType = symbols.uInt!!.defaultType,
unsignedConversionFunction = symbols.toUIntByExtensionReceiver.getValue(symbols.int.defaultType.toKotlinType())
) {
override fun DeclarationIrBuilder.minValueExpression() = irInt(UInt.MIN_VALUE.toInt())
override fun DeclarationIrBuilder.zeroStepExpression() = irInt(0)
}
@ExperimentalUnsignedTypes
internal class ULongProgressionType(symbols: Symbols<CommonBackendContext>) :
UnsignedProgressionType(
symbols,
elementClass = symbols.long.owner,
stepClass = symbols.long.owner,
minValueAsLong = ULong.MIN_VALUE.toLong(),
maxValueAsLong = ULong.MAX_VALUE.toLong(),
// Uses `getProgressionLastElement(ULong, ULong, Long): ULong`
getProgressionLastElementFunction = symbols.getProgressionLastElementByReturnType[symbols.uLong!!],
unsignedType = symbols.uLong!!.defaultType,
unsignedConversionFunction = symbols.toULongByExtensionReceiver.getValue(symbols.long.defaultType.toKotlinType())
) {
override fun DeclarationIrBuilder.minValueExpression() = irLong(ULong.MIN_VALUE.toLong())
override fun DeclarationIrBuilder.zeroStepExpression() = irLong(0)
}