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.backend.common.lower.matchers.IrCallMatcher
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrVariable 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.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression 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.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.isUnsigned
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult 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 { internal enum class ProgressionDirection {
DECREASING { DECREASING {
override fun asReversed() = INCREASING override fun asReversed() = INCREASING
@@ -252,24 +102,16 @@ internal class ProgressionHeaderInfo(
// - `0..someLast()` CAN overflow (we don't know the direction) // - `0..someLast()` CAN overflow (we don't know the direction)
// - `someProgression()` 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. // "step" is still signed for unsigned progressions.
val lastValueAsULong = last.constLongValue?.toULong() ?: return@lazy true // If "last" is not a const Number or Char. val lastValueAsULong = last.constLongValue?.toULong() ?: return@lazy true // If "last" is not a const Number or Char.
when (direction) { when (direction) {
ProgressionDirection.DECREASING -> { ProgressionDirection.DECREASING -> {
val constLimitAsULong = when (progressionType) { val constLimitAsULong = progressionType.minValueAsLong.toULong()
ProgressionType.UINT_PROGRESSION -> UInt.MIN_VALUE.toULong()
ProgressionType.ULONG_PROGRESSION -> ULong.MIN_VALUE
else -> error("Unexpected progression type")
}
lastValueAsULong < (constLimitAsULong - stepValueAsLong.toULong()) lastValueAsULong < (constLimitAsULong - stepValueAsLong.toULong())
} }
ProgressionDirection.INCREASING -> { ProgressionDirection.INCREASING -> {
val constLimitAsULong = when (progressionType) { val constLimitAsULong = progressionType.maxValueAsLong.toULong()
ProgressionType.UINT_PROGRESSION -> UInt.MAX_VALUE.toULong()
ProgressionType.ULONG_PROGRESSION -> ULong.MAX_VALUE
else -> error("Unexpected progression type")
}
lastValueAsULong > (constLimitAsULong - stepValueAsLong.toULong()) lastValueAsULong > (constLimitAsULong - stepValueAsLong.toULong())
} }
else -> error("Unexpected progression direction") 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. val lastValueAsLong = last.constLongValue ?: return@lazy true // If "last" is not a const Number or Char.
when (direction) { when (direction) {
ProgressionDirection.DECREASING -> { ProgressionDirection.DECREASING -> {
val constLimitAsLong = when (progressionType) { val constLimitAsLong = progressionType.minValueAsLong
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")
}
lastValueAsLong < (constLimitAsLong - stepValueAsLong) lastValueAsLong < (constLimitAsLong - stepValueAsLong)
} }
ProgressionDirection.INCREASING -> { ProgressionDirection.INCREASING -> {
val constLimitAsLong = when (progressionType) { val constLimitAsLong = progressionType.maxValueAsLong
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")
}
lastValueAsLong > (constLimitAsLong - stepValueAsLong) lastValueAsLong > (constLimitAsLong - stepValueAsLong)
} }
else -> error("Unexpected progression direction") else -> error("Unexpected progression direction")
@@ -317,6 +149,7 @@ internal class ProgressionHeaderInfo(
* The internal induction variable used is an Int. * The internal induction variable used is an Int.
*/ */
internal class IndexedGetHeaderInfo( internal class IndexedGetHeaderInfo(
symbols: Symbols<CommonBackendContext>,
first: IrExpression, first: IrExpression,
last: IrExpression, last: IrExpression,
step: IrExpression, step: IrExpression,
@@ -324,7 +157,7 @@ internal class IndexedGetHeaderInfo(
val objectVariable: IrVariable, val objectVariable: IrVariable,
val expressionHandler: IndexedGetIterationHandler val expressionHandler: IndexedGetIterationHandler
) : NumericHeaderInfo( ) : NumericHeaderInfo(
ProgressionType.INT_PROGRESSION, IntProgressionType(symbols),
first, first,
last, last,
step, step,
@@ -435,6 +268,7 @@ internal abstract class HeaderInfoBuilder(context: CommonBackendContext, private
override fun visitElement(element: IrElement, data: IrCall?): HeaderInfo? = null override fun visitElement(element: IrElement, data: IrCall?): HeaderInfo? = null
/** Builds a [HeaderInfo] for iterable expressions that are calls (e.g., `.reversed()`, `.indices`. */ /** Builds a [HeaderInfo] for iterable expressions that are calls (e.g., `.reversed()`, `.indices`. */
@ExperimentalUnsignedTypes
override fun visitCall(iterable: IrCall, iteratorCall: IrCall?): HeaderInfo? { override fun visitCall(iterable: IrCall, iteratorCall: IrCall?): HeaderInfo? {
// Return the HeaderInfo from the first successful match. // Return the HeaderInfo from the first successful match.
// First, try to match a `reversed()` or `withIndex()` call. // First, try to match a `reversed()` or `withIndex()` call.
@@ -78,51 +78,48 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
get() = field.deepCopyWithSymbols() get() = field.deepCopyWithSymbols()
protected val symbols = context.ir.symbols protected val symbols = context.ir.symbols
private val elementType: IrType
init { init {
with(builder) { 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: // 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
// for (i in first()..last() step step()) // them to non-nullable.
// // TODO: Confirm if casting to non-nullable is still necessary
// We need to cast first(), last(). and step() to conform to the progression type so val last = headerInfo.last.asElementType()
// 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 lastVariableIfCanCacheLast = if (headerInfo.canCacheLast) {
// they are non-nullable (the frontend takes care about this). So we need to cast scope.createTmpVariable(last, nameHint = "last")
// them to non-nullable. } else null
// TODO: Confirm if casting to non-nullable is still necessary
val last = ensureNotNullable(headerInfo.progressionType.castElementIfNecessary(headerInfo.last, context))
lastVariableIfCanCacheLast = if (headerInfo.canCacheLast) { lastExpression = if (headerInfo.canCacheLast) irGet(lastVariableIfCanCacheLast!!) else last
scope.createTmpVariable(
last,
nameHint = "last"
)
} else null
lastExpression = if (headerInfo.canCacheLast) irGet(lastVariableIfCanCacheLast!!) else last val (tmpStepVar, tmpStepExpression) =
createTemporaryVariableIfNecessary(
val stepType = headerInfo.progressionType.stepType(context.irBuiltIns) ensureNotNullable(headerInfo.step.asStepType()),
val (tmpStepVar, tmpStepExpression) = nameHint = "step",
createTemporaryVariableIfNecessary( irType = stepClass.defaultType
ensureNotNullable(headerInfo.progressionType.castStepIfNecessary(headerInfo.step, context)), )
nameHint = "step", stepVariable = tmpStepVar
irType = stepType stepExpression = tmpStepExpression
) }
stepVariable = tmpStepVar
stepExpression = tmpStepExpression
} }
} }
@@ -135,112 +132,117 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
/** Statement used to increment the induction variable. */ /** Statement used to increment the induction variable. */
protected fun incrementInductionVariable(builder: DeclarationIrBuilder): IrStatement = with(builder) { protected fun incrementInductionVariable(builder: DeclarationIrBuilder): IrStatement = with(builder) {
// inductionVariable = inductionVariable + step with(headerInfo.progressionType) {
// NOTE: We cannot use `stepExpression.type` to match the value parameter type because it may be of type `Nothing`. // inductionVariable = inductionVariable + step
// This happens in the case of an illegal step where the "step" is actually a `throw IllegalArgumentException(...)`. // NOTE: We cannot use `stepExpression.type` to match the value parameter type because it may be of type `Nothing`.
val stepType = headerInfo.progressionType.stepType(context.irBuiltIns) // This happens in the case of an illegal step where the "step" is actually a `throw IllegalArgumentException(...)`.
val plusFun = elementType.getClass()!!.functions.single { val stepType = stepClass.defaultType
it.name == OperatorNameConventions.PLUS && val plusFun = elementClass.defaultType.getClass()!!.functions.single {
it.valueParameters.size == 1 && it.name == OperatorNameConventions.PLUS &&
it.valueParameters[0].type == stepType it.valueParameters.size == 1 &&
} it.valueParameters[0].type == stepType
irSetVar( }
inductionVariable.symbol, irCallOp( irSetVar(
plusFun.symbol, plusFun.returnType, inductionVariable.symbol, irCallOp(
irGet(inductionVariable), plusFun.symbol, plusFun.returnType,
stepExpression irGet(inductionVariable),
stepExpression
)
) )
) }
} }
protected fun buildLoopCondition(builder: DeclarationIrBuilder): IrExpression = protected fun buildLoopCondition(builder: DeclarationIrBuilder): IrExpression {
with(builder) { with(builder) {
val builtIns = context.irBuiltIns with(headerInfo.progressionType) {
val progressionType = headerInfo.progressionType val builtIns = context.irBuiltIns
val progressionCompareType = progressionType.compareType(symbols)
// `compareTo` must be used for UInt/ULong; they don't have intrinsic comparison operators. // Bounds are signed for unsigned progressions but bound comparisons should be done as unsigned, to ensure that the
val intCompFun = if (isLastInclusive) { // correct comparison function is used (`UInt/ULongCompare`). Also, `compareTo` must be used for UInt/ULong;
builtIns.lessOrEqualFunByOperandType.getValue(builtIns.intClass) // they don't have intrinsic comparison operators.
} else { val intCompFun = if (isLastInclusive) {
builtIns.lessFunByOperandType.getValue(builtIns.intClass) builtIns.lessOrEqualFunByOperandType.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]
} else { } 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 = val elementCompFun =
// last <= inductionVar (use `<` if last is exclusive) if (isLastInclusive) {
if (progressionType.isUnsigned) { builtIns.lessOrEqualFunByOperandType[elementClass.symbol]
irCall(intCompFun).apply { } else {
putValueArgument(0, irCall(elementCompareToFun).apply { builtIns.lessFunByOperandType[elementClass.symbol]
dispatchReceiver = progressionType.coerceToUnsigned(lastExpression, symbols)
putValueArgument(0, progressionType.coerceToUnsigned(irGet(inductionVariable), symbols))
})
putValueArgument(1, irInt(0))
} }
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, lastExpression)
putValueArgument(1, irGet(inductionVariable))
}
}
fun conditionForIncreasing(): IrExpression = fun conditionForDecreasing(): IrExpression =
// inductionVar <= last (use `<` if last is exclusive) // last <= inductionVar (use `<` if last is exclusive)
if (progressionType.isUnsigned) { if (this is UnsignedProgressionType) {
irCall(intCompFun).apply { irCall(intCompFun).apply {
putValueArgument(0, irCall(elementCompareToFun).apply { putValueArgument(0, irCall(unsignedCompareToFun!!).apply {
dispatchReceiver = progressionType.coerceToUnsigned(irGet(inductionVariable), symbols) dispatchReceiver = lastExpression.asUnsigned()
putValueArgument(0, progressionType.coerceToUnsigned(lastExpression, symbols)) putValueArgument(0, irGet(inductionVariable).asUnsigned())
}) })
putValueArgument(1, irInt(0)) 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. fun conditionForIncreasing(): IrExpression =
when (headerInfo.direction) { // inductionVar <= last (use `<` if last is exclusive)
ProgressionDirection.DECREASING -> conditionForDecreasing() if (this is UnsignedProgressionType) {
ProgressionDirection.INCREASING -> conditionForIncreasing() irCall(intCompFun).apply {
ProgressionDirection.UNKNOWN -> { putValueArgument(0, irCall(unsignedCompareToFun!!).apply {
// If the direction is unknown, we check depending on the "step" value: dispatchReceiver = irGet(inductionVariable).asUnsigned()
// // (use `<` if last is exclusive) putValueArgument(0, lastExpression.asUnsigned())
// (step > 0 && inductionVar <= last) || (step < 0 || last <= inductionVar) })
val stepTypeClassifier = progressionType.stepClassifier(builtIns) putValueArgument(1, irInt(0))
context.oror( }
context.andand( } else {
irCall(builtIns.greaterFunByOperandType.getValue(stepTypeClassifier)).apply { irCall(elementCompFun!!).apply {
putValueArgument(0, stepExpression) putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, if (progressionType.isLong) irLong(0) else irInt(0)) putValueArgument(1, lastExpression)
}, }
conditionForIncreasing() }
),
context.andand( // The default condition depends on the direction.
irCall(builtIns.lessFunByOperandType.getValue(stepTypeClassifier)).apply { return when (headerInfo.direction) {
putValueArgument(0, stepExpression) ProgressionDirection.DECREASING -> conditionForDecreasing()
putValueArgument(1, if (progressionType.isLong) irLong(0) else irInt(0)) ProgressionDirection.INCREASING -> conditionForIncreasing()
}, ProgressionDirection.UNKNOWN -> {
conditionForDecreasing() // 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( internal class ProgressionLoopHeader(
@@ -283,7 +285,14 @@ internal class ProgressionLoopHeader(
isMutable = true isMutable = true
) )
} else { } 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 loopVariable
} }
@@ -307,8 +316,16 @@ internal class ProgressionLoopHeader(
// } while (loopVar != last) // } while (loopVar != last)
// } // }
IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply { 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 label = oldLoop.label
condition = irNotEquals(headerInfo.progressionType.coerceToSigned(irGet(loopVariable!!), symbols), lastExpression) condition = irNotEquals(loopVariableExpression, lastExpression)
body = newBody body = newBody
} }
} else { } else {
@@ -88,100 +88,94 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? = override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { 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. with(data) {
// If B is MIN_VALUE, then `A until B` is an empty range. We handle this special case be adding an additional "not empty" // `A until B` is essentially the same as `A .. (B-1)`. However, B could be MIN_VALUE and hence `(B-1)` could underflow.
// condition in the lowered for-loop. Therefore the following for-loop: // 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 } //
// // for (i in A until B) { // Loop body }
// is lowered into: //
// // is lowered into:
// var inductionVar = A //
// val last = B - 1 // var inductionVar = A
// if (inductionVar <= last && B != MIN_VALUE) { // val last = B - 1
// // Loop is not empty // if (inductionVar <= last && B != MIN_VALUE) {
// do { // // Loop is not empty
// val i = inductionVar // do {
// inductionVar++ // val i = inductionVar
// // Loop body // inductionVar++
// } while (inductionVar <= last) // // 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: // 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 // // Additional variables
// val untilArg = B // val untilReceiverValue = A
// // Standard form of loop over progression // val untilArg = B
// var inductionVar = untilReceiverValue // // Standard form of loop over progression
// val last = untilArg - 1 // var inductionVar = untilReceiverValue
// if (inductionVar <= last && untilArg != MIN_VALUE) { // val last = untilArg - 1
// // Loop is not empty // if (inductionVar <= last && untilArg != MIN_VALUE) {
// do { // // Loop is not empty
// val i = inductionVar // do {
// inductionVar++ // val i = inductionVar
// // Loop body // inductionVar++
// } while (inductionVar <= last) // // Loop body
// } // } while (inductionVar <= last)
val receiverValue = expression.extensionReceiver!! // }
val untilArg = expression.getValueArgument(0)!! val receiverValue = expression.extensionReceiver!!
val untilArg = expression.getValueArgument(0)!!
// Ensure that the argument conforms to the progression type before we decrement. // Ensure that the argument conforms to the progression type before we decrement.
val untilArgCasted = data.castElementIfNecessary(untilArg, this@UntilHandler.context) val untilArgCasted = untilArg.asElementType()
// To reduce local variable usage, we create and use temporary variables only if necessary. // To reduce local variable usage, we create and use temporary variables only if necessary.
var receiverValueVar: IrVariable? = null var receiverValueVar: IrVariable? = null
var untilArgVar: IrVariable? = null var untilArgVar: IrVariable? = null
var additionalVariables = emptyList<IrVariable>() var additionalVariables = emptyList<IrVariable>()
if (untilArg.canHaveSideEffects) { if (untilArg.canHaveSideEffects) {
if (receiverValue.canHaveSideEffects) { if (receiverValue.canHaveSideEffects) {
receiverValueVar = scope.createTmpVariable(receiverValue, nameHint = "untilReceiverValue") 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 { 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.") // if (step > 0) step else throw IllegalArgumentException("Step must be positive, was: $step.")
// //
// We insert this check in the lowered form only if necessary. // We insert this check in the lowered form only if necessary.
val stepType = data.stepType(context.irBuiltIns) val stepType = data.stepClass.defaultType
val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType[data.stepClassifier(context.irBuiltIns)]!! val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType.getValue(data.stepClass.symbol)
val zeroStep = if (data.isLong) irLong(0) else irInt(0) val zeroStep = data.run { zeroStepExpression() }
val throwIllegalStepExceptionCall = { val throwIllegalStepExceptionCall = {
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply { irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply {
val exceptionMessage = irConcat() val exceptionMessage = irConcat()
@@ -477,44 +471,25 @@ internal class StepHandler(
// - getProgressionLastElement(Long, Long, Long): Long // Used by LongProgression // - getProgressionLastElement(Long, Long, Long): Long // Used by LongProgression
// - getProgressionLastElement(UInt, UInt, Int): UInt // Used by UIntProgression (uses Int step) // - getProgressionLastElement(UInt, UInt, Int): UInt // Used by UIntProgression (uses Int step)
// - getProgressionLastElement(ULong, ULong, Long): ULong // Used by ULongProgression (uses Long step) // - getProgressionLastElement(ULong, ULong, Long): ULong // Used by ULongProgression (uses Long step)
// with(progressionType) {
// We make sure to retrieve the correct symbol and use the correct argument types. val getProgressionLastElementFun = getProgressionLastElementFunction
return with(progressionType) { ?: error("No `getProgressionLastElement` for progression type ${progressionType::class.simpleName}")
val returnTypeClassifier = if (progressionType.isUnsigned) { return if (this is UnsignedProgressionType) {
progressionType.elementClassifier(symbols) // 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 { } else {
progressionType.stepClassifier(context.irBuiltIns) irCall(getProgressionLastElementFun).apply {
} // Step type is used for casting because it works for all signed progressions. In particular,
val getProgressionLastElementFun = symbols.getProgressionLastElementByReturnType[returnTypeClassifier] // getProgressionLastElement(Int, Int, Int) is called for CharProgression, which uses an Int step.
?: throw IllegalArgumentException("No `getProgressionLastElement` for return type ${returnTypeClassifier.defaultType}") putValueArgument(0, first.deepCopyWithSymbols().asStepType())
val call = irCall(getProgressionLastElementFun).apply { putValueArgument(1, last.deepCopyWithSymbols().asStepType())
if (isUnsigned) { putValueArgument(2, step.deepCopyWithSymbols().asStepType())
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))
} }
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 private val symbols = context.ir.symbols
@ExperimentalUnsignedTypes
override fun matchIterable(expression: IrExpression) = ProgressionType.fromIrType(expression.type, symbols) != null override fun matchIterable(expression: IrExpression) = ProgressionType.fromIrType(expression.type, symbols) != null
@ExperimentalUnsignedTypes
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? = override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) { with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
// Directly use the `first/last/step` properties of the progression. // Directly use the `first/last/step` properties of the progression.
@@ -666,6 +643,7 @@ internal abstract class IndexedGetIterationHandler(
} }
IndexedGetHeaderInfo( IndexedGetHeaderInfo(
this@IndexedGetIterationHandler.context.ir.symbols,
first = irInt(0), first = irInt(0),
last = last, last = last,
step = irInt(1), 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)
}