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:
committed by
Alexander Udalov
parent
177967258b
commit
b85da8411d
+9
-175
@@ -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.
|
||||
|
||||
+147
-130
@@ -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 {
|
||||
|
||||
+107
-129
@@ -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),
|
||||
|
||||
+205
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user