ForLoopsLowering: Add support for unsigned progressions.

This commit is contained in:
Mark Punzalan
2020-04-02 16:32:14 -07:00
committed by Alexander Udalov
parent 9fdb1ff4f8
commit 03ef3724f4
9 changed files with 313 additions and 158 deletions
@@ -64,9 +64,13 @@ open class BuiltinSymbolsBase(protected val irBuiltIns: IrBuiltIns, protected va
)
private fun getClass(name: Name, vararg packageNameSegments: String = arrayOf("kotlin")): IrClassSymbol =
symbolTable.referenceClass(
builtInsPackage(*packageNameSegments).getContributedClassifier(name, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
)
getClassOrNull(name, *packageNameSegments) ?: error("Class '$name' not found in package '${packageNameSegments.joinToString(".")}'")
private fun getClassOrNull(name: Name, vararg packageNameSegments: String = arrayOf("kotlin")): IrClassSymbol? =
(builtInsPackage(*packageNameSegments).getContributedClassifier(
name,
NoLookupLocation.FROM_BACKEND
) as? ClassDescriptor)?.let { symbolTable.referenceClass(it) }
/**
* Use this table to reference external dependencies.
@@ -89,11 +93,22 @@ open class BuiltinSymbolsBase(protected val irBuiltIns: IrBuiltIns, protected va
.map { symbolTable.referenceFunction(it) }
private fun progression(name: String) = getClass(Name.identifier(name), "kotlin", "ranges")
private fun progressionOrNull(name: String) = getClassOrNull(Name.identifier(name), "kotlin", "ranges")
// The "...OrNull" variants are used for unsigned (and progressions) because the minimal stdlib used in tests do not include those
// those classes. It was not feasible to add them to the JS reduced runtime because all its transitive dependencies also need to be
// added, which would include a lot of the full stdlib.
open val uByte = getClassOrNull(Name.identifier("UByte"), "kotlin")
open val uShort = getClassOrNull(Name.identifier("UShort"), "kotlin")
open val uInt = getClassOrNull(Name.identifier("UInt"), "kotlin")
open val uLong = getClassOrNull(Name.identifier("ULong"), "kotlin")
val uIntProgression = progressionOrNull("UIntProgression")
val uLongProgression = progressionOrNull("ULongProgression")
val charProgression = progression("CharProgression")
val intProgression = progression("IntProgression")
val longProgression = progression("LongProgression")
val progressionClasses = listOf(charProgression, intProgression, longProgression)
val progressionClasses = listOfNotNull(charProgression, intProgression, longProgression, uIntProgression, uLongProgression)
val progressionClassesTypes = progressionClasses.map { it.descriptor.defaultType }.toSet()
val getProgressionLastElementByReturnType = builtInsPackage("kotlin", "internal")
@@ -105,6 +120,18 @@ open class BuiltinSymbolsBase(protected val irBuiltIns: IrBuiltIns, protected va
c to f
}.toMap()
val toUIntByExtensionReceiver = builtInsPackage("kotlin").getContributedFunctions(
Name.identifier("toUInt"),
NoLookupLocation.FROM_BACKEND
).filter { it.containingDeclaration !is BuiltInsPackageFragment && it.extensionReceiverParameter != null }
.map { Pair(it.extensionReceiverParameter!!.type, symbolTable.referenceSimpleFunction(it)) }.toMap()
val toULongByExtensionReceiver = builtInsPackage("kotlin").getContributedFunctions(
Name.identifier("toULong"),
NoLookupLocation.FROM_BACKEND
).filter { it.containingDeclaration !is BuiltInsPackageFragment && it.extensionReceiverParameter != null }
.map { Pair(it.extensionReceiverParameter!!.type, symbolTable.referenceSimpleFunction(it)) }.toMap()
val any = symbolTable.referenceClass(builtIns.any)
val unit = symbolTable.referenceClass(builtIns.unit)
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
@@ -238,7 +237,7 @@ private class RangeLoopTransformer(
mainLoopVariable.endOffset,
context.irBuiltIns.unitType,
IrStatementOrigin.FOR_LOOP_NEXT,
loopHeader.initializeIteration(mainLoopVariable, loopVariableComponents, symbols, this)
loopHeader.initializeIteration(mainLoopVariable, loopVariableComponents, this)
)
}
@@ -14,46 +14,77 @@ 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.IrType
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
// TODO: Handle withIndex()
// TODO: Handle UIntProgression, ULongProgression
/** Represents a progression type in the Kotlin stdlib. */
internal enum class ProgressionType(val elementCastFunctionName: Name, val stepCastFunctionName: Name) {
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")),
CHAR_PROGRESSION(Name.identifier("toChar"), 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("toUInt"), Name.identifier("toInt"), isUnsigned = true),
ULONG_PROGRESSION(Name.identifier("toULong"), Name.identifier("toLong"), isLong = true, isUnsigned = true);
/** Returns the [IrType] of the `first`/`last` properties and elements in the progression. */
fun elementType(builtIns: IrBuiltIns): IrType = when (this) {
INT_PROGRESSION -> builtIns.intType
LONG_PROGRESSION -> builtIns.longType
CHAR_PROGRESSION -> builtIns.charType
fun elementType(symbols: Symbols<CommonBackendContext>): IrType = elementClassifier(symbols).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 -> builtIns.intType
LONG_PROGRESSION -> builtIns.longType
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 -> builtIns.intClass
LONG_PROGRESSION -> builtIns.longClass
INT_PROGRESSION, CHAR_PROGRESSION , UINT_PROGRESSION-> builtIns.intClass
LONG_PROGRESSION, ULONG_PROGRESSION -> builtIns.longClass
}
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 }
}
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
}
}
@@ -113,12 +144,18 @@ internal class ProgressionHeaderInfo(
additionalNotEmptyCondition = additionalNotEmptyCondition
) {
@ExperimentalUnsignedTypes
val canOverflow: Boolean by lazy {
if (canOverflow != null) return@lazy canOverflow
// We can't determine the safe limit at compile-time if "step" is not const.
val stepValueAsLong = step.constLongValue ?: return@lazy true
if (direction == ProgressionDirection.UNKNOWN) {
// If we don't know the direction, we can't be sure which limit to use.
return@lazy true
}
// Induction variable can NOT overflow if "last" is const and is <= (MAX/MIN_VALUE - step) (depending on direction).
//
// Examples that can NOT overflow:
@@ -137,26 +174,51 @@ internal class ProgressionHeaderInfo(
// - `0..10 step someStep()` CAN overflow (we don't know the step and hence can't determine the safe limit)
// - `0..someLast()` CAN overflow (we don't know the direction)
// - `someProgression()` CAN overflow (we don't know the direction)
val lastValueAsLong = last.constLongValue ?: return@lazy true // If "last" is not a const Number or Char.
when (direction) {
ProgressionDirection.UNKNOWN ->
// If we don't know the direction, we can't be sure which limit to use.
true
ProgressionDirection.DECREASING -> {
val constLimitAsLong = when (progressionType) {
ProgressionType.INT_PROGRESSION -> Int.MIN_VALUE.toLong()
ProgressionType.CHAR_PROGRESSION -> Char.MIN_VALUE.toLong()
ProgressionType.LONG_PROGRESSION -> Long.MIN_VALUE
if (progressionType.isUnsigned) {
// "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")
}
lastValueAsULong < (constLimitAsULong - stepValueAsLong.toULong())
}
lastValueAsLong < (constLimitAsLong - stepValueAsLong)
ProgressionDirection.INCREASING -> {
val constLimitAsULong = when (progressionType) {
ProgressionType.UINT_PROGRESSION -> UInt.MAX_VALUE.toULong()
ProgressionType.ULONG_PROGRESSION -> ULong.MAX_VALUE
else -> error("Unexpected progression type")
}
lastValueAsULong > (constLimitAsULong - stepValueAsLong.toULong())
}
else -> error("Unexpected progression direction")
}
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 {
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")
}
lastValueAsLong < (constLimitAsLong - stepValueAsLong)
}
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")
}
lastValueAsLong > (constLimitAsLong - stepValueAsLong)
}
else -> error("Unexpected progression direction")
}
}
}
@@ -268,14 +330,17 @@ internal abstract class HeaderInfoBuilder(context: CommonBackendContext, private
private val symbols = context.ir.symbols
// TODO: Include unsigned types
private val progressionElementTypes = listOf(
context.irBuiltIns.byteType,
context.irBuiltIns.shortType,
context.irBuiltIns.intType,
context.irBuiltIns.longType,
context.irBuiltIns.charType
)
private val progressionElementTypes = listOfNotNull(
symbols.byte,
symbols.short,
symbols.int,
symbols.long,
symbols.char,
symbols.uByte,
symbols.uShort,
symbols.uInt,
symbols.uLong
).map { it.defaultType }
private val progressionHandlers = listOf(
CollectionIndicesHandler(context),
@@ -6,7 +6,6 @@
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.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irIfThen
@@ -50,7 +49,6 @@ internal interface ForLoopHeader {
fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
symbols: Symbols<CommonBackendContext>,
builder: DeclarationIrBuilder
): List<IrStatement>
@@ -61,6 +59,7 @@ internal interface ForLoopHeader {
internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
protected val headerInfo: T,
builder: DeclarationIrBuilder,
context: CommonBackendContext,
protected val isLastInclusive: Boolean
) : ForLoopHeader {
@@ -68,16 +67,18 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
val inductionVariable: IrVariable
val stepVariable: IrVariable
val stepVariableForIncrement: IrVariable?
protected val lastVariableIfCanCacheLast: IrVariable?
protected val lastExpression: IrExpression
// Always copy `lastExpression` is it may be used in multiple conditions.
get() = field.deepCopyWithSymbols()
private val symbols = context.ir.symbols
private val elementType: IrType
init {
with(builder) {
elementType = headerInfo.progressionType.elementType(context.irBuiltIns)
elementType = headerInfo.progressionType.elementType(symbols)
// For this loop:
//
@@ -89,10 +90,7 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
// 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.createTemporaryVariable(
headerInfo.first.castIfNecessary(
elementType,
headerInfo.progressionType.elementCastFunctionName
),
headerInfo.progressionType.castElementIfNecessary(headerInfo.first, context),
nameHint = "inductionVariable",
isMutable = true
)
@@ -101,12 +99,7 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
// 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.last.castIfNecessary(
elementType,
headerInfo.progressionType.elementCastFunctionName
)
)
val last = ensureNotNullable(headerInfo.progressionType.castElementIfNecessary(headerInfo.last, context))
lastVariableIfCanCacheLast = if (headerInfo.canCacheLast) {
scope.createTemporaryVariable(
@@ -117,17 +110,29 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
lastExpression = if (headerInfo.canCacheLast) irGet(lastVariableIfCanCacheLast!!) else last
stepVariable = headerInfo.progressionType.stepType(context.irBuiltIns).let {
stepVariable =
scope.createTemporaryVariable(
ensureNotNullable(
headerInfo.step.castIfNecessary(
it,
headerInfo.progressionType.stepCastFunctionName
)
),
ensureNotNullable(headerInfo.progressionType.castStepIfNecessary(headerInfo.step, context)),
nameHint = "step",
irType = it
irType = headerInfo.progressionType.stepType(context.irBuiltIns)
)
stepVariableForIncrement = when (headerInfo.progressionType) {
ProgressionType.UINT_PROGRESSION -> {
val castFun = context.ir.symbols.toUIntByExtensionReceiver.getValue(stepVariable.type.toKotlinType())
scope.createTemporaryVariable(
irCall(castFun).apply { extensionReceiver = irGet(stepVariable) },
nameHint = "stepForIncrement"
)
}
ProgressionType.ULONG_PROGRESSION -> {
val castFun = context.ir.symbols.toULongByExtensionReceiver.getValue(stepVariable.type.toKotlinType())
scope.createTemporaryVariable(
irCall(castFun).apply { extensionReceiver = irGet(stepVariable) },
nameHint = "stepForIncrement"
)
}
else -> null
}
}
}
@@ -142,16 +147,17 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
/** Statement used to increment the induction variable. */
protected fun incrementInductionVariable(builder: DeclarationIrBuilder): IrStatement = with(builder) {
// inductionVariable = inductionVariable + step
val stepVariableToUse = stepVariableForIncrement ?: stepVariable
val plusFun = elementType.getClass()!!.functions.single {
it.name == OperatorNameConventions.PLUS &&
it.valueParameters.size == 1 &&
it.valueParameters[0].type == stepVariable.type
it.valueParameters[0].type == stepVariableToUse.type
}
irSetVar(
inductionVariable.symbol, irCallOp(
plusFun.symbol, plusFun.returnType,
irGet(inductionVariable),
irGet(stepVariable)
irGet(stepVariableToUse)
)
)
}
@@ -160,25 +166,65 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
with(builder) {
val builtIns = context.irBuiltIns
val progressionType = headerInfo.progressionType
val progressionElementType = progressionType.elementType(builtIns)
val compFun =
if (isLastInclusive) builtIns.lessOrEqualFunByOperandType[progressionElementType.classifierOrFail]!!
else builtIns.lessFunByOperandType[progressionElementType.classifierOrFail]!!
val progressionElementType = progressionType.elementType(symbols)
// The default condition depends on the direction.
when (headerInfo.direction) {
ProgressionDirection.DECREASING ->
// last <= inductionVar (use `<` if last is exclusive)
irCall(compFun).apply {
// `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 = progressionElementType.getClass()!!.functions.single {
it.name == OperatorNameConventions.COMPARE_TO &&
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null &&
it.valueParameters.size == 1 && it.valueParameters[0].type == progressionElementType
}
val elementCompFun =
if (isLastInclusive) {
builtIns.lessOrEqualFunByOperandType[progressionElementType.classifierOrFail]
} else {
builtIns.lessFunByOperandType[progressionElementType.classifierOrFail]
}
fun conditionForDecreasing(): IrExpression =
// last <= inductionVar (use `<` if last is exclusive)
if (progressionType.isUnsigned) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(elementCompareToFun).apply {
dispatchReceiver = lastExpression
putValueArgument(0, irGet(inductionVariable))
})
putValueArgument(1, irInt(0))
}
} else {
irCall(elementCompFun!!).apply {
putValueArgument(0, lastExpression)
putValueArgument(1, irGet(inductionVariable))
}
ProgressionDirection.INCREASING ->
// inductionVar <= last (use `<` if last is exclusive)
irCall(compFun).apply {
}
fun conditionForIncreasing(): IrExpression =
// inductionVar <= last (use `<` if last is exclusive)
if (progressionType.isUnsigned) {
irCall(intCompFun).apply {
putValueArgument(0, irCall(elementCompareToFun).apply {
dispatchReceiver = irGet(inductionVariable)
putValueArgument(0, lastExpression)
})
putValueArgument(1, irInt(0))
}
} 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)
@@ -187,33 +233,31 @@ internal abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
val isLong = progressionType == ProgressionType.LONG_PROGRESSION
context.oror(
context.andand(
irCall(builtIns.greaterFunByOperandType[stepTypeClassifier]!!).apply {
irCall(builtIns.greaterFunByOperandType.getValue(stepTypeClassifier)).apply {
putValueArgument(0, irGet(stepVariable))
putValueArgument(1, if (isLong) irLong(0) else irInt(0))
putValueArgument(1, if (progressionType.isLong) irLong(0) else irInt(0))
},
irCall(compFun).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, lastExpression)
}),
conditionForIncreasing()
),
context.andand(
irCall(builtIns.lessFunByOperandType[stepTypeClassifier]!!).apply {
irCall(builtIns.lessFunByOperandType.getValue(stepTypeClassifier)).apply {
putValueArgument(0, irGet(stepVariable))
putValueArgument(1, if (isLong) irLong(0) else irInt(0))
putValueArgument(1, if (progressionType.isLong) irLong(0) else irInt(0))
},
irCall(compFun).apply {
putValueArgument(0, lastExpression)
putValueArgument(1, irGet(inductionVariable))
})
conditionForDecreasing()
)
)
}
}
}
}
internal class ProgressionLoopHeader(
headerInfo: ProgressionHeaderInfo,
builder: DeclarationIrBuilder
) : NumericForLoopHeader<ProgressionHeaderInfo>(headerInfo, builder, isLastInclusive = true) {
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : NumericForLoopHeader<ProgressionHeaderInfo>(headerInfo, builder, context, isLastInclusive = true) {
// For this loop:
//
@@ -230,14 +274,14 @@ internal class ProgressionLoopHeader(
else
listOfNotNull(inductionVariable, lastVariableIfCanCacheLast)
) +
stepVariable
listOfNotNull(stepVariable, stepVariableForIncrement)
private var loopVariable: IrVariable? = null
@ExperimentalUnsignedTypes
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
symbols: Symbols<CommonBackendContext>,
builder: DeclarationIrBuilder
) =
with(builder) {
@@ -258,6 +302,7 @@ internal class ProgressionLoopHeader(
listOfNotNull(loopVariable, incrementInductionVariable(this))
}
@ExperimentalUnsignedTypes
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
with(builder) {
val newLoop = if (headerInfo.canOverflow) {
@@ -322,15 +367,16 @@ private class InitializerCallReplacer(symbolRemapper: SymbolRemapper, typeRemapp
internal class IndexedGetLoopHeader(
headerInfo: IndexedGetHeaderInfo,
builder: DeclarationIrBuilder
) : NumericForLoopHeader<IndexedGetHeaderInfo>(headerInfo, builder, isLastInclusive = false) {
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : NumericForLoopHeader<IndexedGetHeaderInfo>(headerInfo, builder, context, isLastInclusive = false) {
override val loopInitStatements = listOfNotNull(headerInfo.objectVariable, inductionVariable, lastVariableIfCanCacheLast, stepVariable)
override val loopInitStatements =
listOfNotNull(headerInfo.objectVariable, inductionVariable, lastVariableIfCanCacheLast, stepVariable, stepVariableForIncrement)
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
symbols: Symbols<CommonBackendContext>,
builder: DeclarationIrBuilder
) =
with(builder) {
@@ -371,7 +417,8 @@ internal class IndexedGetLoopHeader(
internal class WithIndexLoopHeader(
headerInfo: WithIndexHeaderInfo,
builder: DeclarationIrBuilder
builder: DeclarationIrBuilder,
context: CommonBackendContext
) : ForLoopHeader {
private val nestedLoopHeader: ForLoopHeader
@@ -384,8 +431,8 @@ internal class WithIndexLoopHeader(
// To build the optimized/lowered `for` loop over a `withIndex()` call, we first need the header for the underlying iterable so
// so that we know how to build the loop for that iterable. More info in comments in initializeIteration().
nestedLoopHeader = when (val nestedInfo = headerInfo.nestedInfo) {
is IndexedGetHeaderInfo -> IndexedGetLoopHeader(nestedInfo, this@with)
is ProgressionHeaderInfo -> ProgressionLoopHeader(nestedInfo, this@with)
is IndexedGetHeaderInfo -> IndexedGetLoopHeader(nestedInfo, this@with, context)
is ProgressionHeaderInfo -> ProgressionLoopHeader(nestedInfo, this@with, context)
is IterableHeaderInfo -> IterableLoopHeader(nestedInfo)
is WithIndexHeaderInfo -> throw IllegalStateException("Nested WithIndexHeaderInfo not allowed for WithIndexLoopHeader")
}
@@ -409,7 +456,7 @@ internal class WithIndexLoopHeader(
)
ownsIndexVariable = true
// `index++` during iteration initialization
// TODO: MUSTDO: Check for overflow for Iterable and Sequence (call to checkIndexOverflow()).
// TODO: KT-34665: Check for overflow for Iterable and Sequence (call to checkIndexOverflow()).
val plusFun = indexVariable.type.getClass()!!.functions.first {
it.name == OperatorNameConventions.PLUS &&
it.valueParameters.size == 1 &&
@@ -435,7 +482,6 @@ internal class WithIndexLoopHeader(
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
symbols: Symbols<CommonBackendContext>,
builder: DeclarationIrBuilder
) =
with(builder) {
@@ -508,7 +554,6 @@ internal class WithIndexLoopHeader(
listOfNotNull(loopVariableComponents[1], incrementIndexStatement) + nestedLoopHeader.initializeIteration(
loopVariableComponents[2],
linkedMapOf(),
symbols,
builder
)
}
@@ -528,7 +573,6 @@ internal class IterableLoopHeader(
override fun initializeIteration(
loopVariable: IrVariable?,
loopVariableComponents: Map<Int, IrVariable>,
symbols: Symbols<CommonBackendContext>,
builder: DeclarationIrBuilder
) =
with(builder) {
@@ -615,9 +659,9 @@ internal class HeaderProcessor(
val builder = context.createIrBuilder(scopeOwnerSymbol(), variable.startOffset, variable.endOffset)
return when (headerInfo) {
is IndexedGetHeaderInfo -> IndexedGetLoopHeader(headerInfo, builder)
is ProgressionHeaderInfo -> ProgressionLoopHeader(headerInfo, builder)
is WithIndexHeaderInfo -> WithIndexLoopHeader(headerInfo, builder)
is IndexedGetHeaderInfo -> IndexedGetLoopHeader(headerInfo, builder, context)
is ProgressionHeaderInfo -> ProgressionLoopHeader(headerInfo, builder, context)
is WithIndexHeaderInfo -> WithIndexLoopHeader(headerInfo, builder, context)
is IterableHeaderInfo -> IterableLoopHeader(headerInfo)
}
}
@@ -17,10 +17,12 @@ import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.asSimpleType
import org.jetbrains.kotlin.util.OperatorNameConventions
import kotlin.math.absoluteValue
@@ -73,12 +75,19 @@ internal class DownToHandler(private val context: CommonBackendContext, private
internal class UntilHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
ProgressionHandler {
private val symbols = context.ir.symbols
private val uByteType = symbols.uByte?.defaultType
private val uShortType = symbols.uShort?.defaultType
private val uIntType = symbols.uInt?.defaultType
private val uLongType = symbols.uLong?.defaultType
override val matcher = SimpleCalleeMatcher {
singleArgumentExtension(FqName("kotlin.ranges.until"), progressionElementTypes)
parameterCount { it == 1 }
parameter(0) { it.type in progressionElementTypes }
}
@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.
@@ -121,10 +130,7 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
val untilArg = expression.getValueArgument(0)!!
// Ensure that the argument conforms to the progression type before we decrement.
val untilArgCasted = untilArg.castIfNecessary(
data.elementType(context.irBuiltIns),
data.elementCastFunctionName
)
val untilArgCasted = data.castElementIfNecessary(untilArg, this@UntilHandler.context)
// To reduce local variable usage, we create and use temporary variables only if necessary.
var receiverValueVar: IrVariable? = null
@@ -147,6 +153,24 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
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(),
IrConstImpl.int(
startOffset,
endOffset,
symbols.uInt!!.defaultType,
UInt.MIN_VALUE.toInt()
)
)
ProgressionType.ULONG_PROGRESSION -> Pair(
ULong.MIN_VALUE.toLong(),
IrConstImpl.long(
startOffset,
endOffset,
symbols.uLong!!.defaultType,
ULong.MIN_VALUE.toLong()
)
)
}
val additionalNotEmptyCondition = untilArg.constLongValue.let {
when {
@@ -197,11 +221,14 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
// infix fun Long.until(to: Short): LongRange
// infix fun Long.until(to: Int): LongRange
// infix fun Long.until(to: Long): LongRange
// infix fun UByte.until(to: UByte): UIntRange
// infix fun UShort.until(to: UShort): UIntRange
// infix fun UInt.until(to: UInt): UIntRange
// infix fun ULong.until(to: ULong): ULongRange
//
// The combinations where the range element type is strictly larger than the argument type do NOT need the additional condition.
// In such combinations, there is no possibility of underflow when the argument (casted to the range element type) is decremented.
// For unexpected combinations that currently don't exist (e.g., Int until Char), we assume the check is needed to be safe.
// TODO: Include unsigned types
return with(context.irBuiltIns) {
when (receiverType) {
charType -> true
@@ -213,7 +240,11 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
byteType, shortType, intType -> false
else -> true
}
else -> true
uByteType -> false
uShortType -> false
uIntType -> true
uLongType -> true
else -> true // Default in case a new `until` overload is added to stdlib and this function was not updated.
}
}
}
@@ -258,7 +289,7 @@ internal class StepHandler(
// 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 == ProgressionType.LONG_PROGRESSION) irLong(0) else irInt(0)
val zeroStep = if (data.isLong) irLong(0) else irInt(0)
val throwIllegalStepExceptionCall = {
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply {
val exceptionMessage = irConcat()
@@ -458,29 +489,29 @@ internal class StepHandler(
return last
}
// Call `getProgressionLastElement(first, last, step)`
val stepTypeClassifier = progressionType.stepClassifier(context.irBuiltIns)
val getProgressionLastElementFun = symbols.getProgressionLastElementByReturnType[stepTypeClassifier]
?: throw IllegalArgumentException("No `getProgressionLastElement` for step type $stepTypeClassifier")
// Call `getProgressionLastElement(first, last, step)`. The following overloads are present in the stdlib:
// - getProgressionLastElement(Int, Int, Int): Int // Used by IntProgression and CharProgression (uses Int step)
// - 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.
val returnTypeClassifier = if (progressionType.isUnsigned) {
progressionType.elementClassifier(symbols)
} else {
progressionType.stepClassifier(context.irBuiltIns)
}
val getProgressionLastElementFun = symbols.getProgressionLastElementByReturnType[returnTypeClassifier]
?: throw IllegalArgumentException("No `getProgressionLastElement` for return type ${returnTypeClassifier.defaultType}")
return irCall(getProgressionLastElementFun).apply {
putValueArgument(
0, first.deepCopyWithSymbols().castIfNecessary(
progressionType.stepType(context.irBuiltIns),
progressionType.stepCastFunctionName
)
)
putValueArgument(
1, last.deepCopyWithSymbols().castIfNecessary(
progressionType.stepType(context.irBuiltIns),
progressionType.stepCastFunctionName
)
)
putValueArgument(
2, step.deepCopyWithSymbols().castIfNecessary(
progressionType.stepType(context.irBuiltIns),
progressionType.stepCastFunctionName
)
)
if (progressionType.isUnsigned) {
putValueArgument(0, progressionType.castElementIfNecessary(first.deepCopyWithSymbols(), this@StepHandler.context))
putValueArgument(1, progressionType.castElementIfNecessary(last.deepCopyWithSymbols(), this@StepHandler.context))
} else {
putValueArgument(0, progressionType.castStepIfNecessary(first.deepCopyWithSymbols(), this@StepHandler.context))
putValueArgument(1, progressionType.castStepIfNecessary(last.deepCopyWithSymbols(), this@StepHandler.context))
}
putValueArgument(2, progressionType.castStepIfNecessary(step.deepCopyWithSymbols(), this@StepHandler.context))
}
}
}
@@ -14,24 +14,11 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.isNothing
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
internal fun IrExpression.castIfNecessary(targetType: IrType, numberCastFunctionName: Name): IrExpression {
// This expression's type could be Nothing from an exception throw.
return if (type == targetType || type.isNothing()) {
this
} else {
val castFun = type.getClass()!!.functions.single { it.name == numberCastFunctionName && it.valueParameters.isEmpty() }
IrCallImpl(startOffset, endOffset, castFun.returnType, castFun.symbol)
.apply { dispatchReceiver = this@castIfNecessary }
}
}
/** Return the negated value if the expression is const, otherwise call unaryMinus(). */
internal fun IrExpression.negate(): IrExpression {
return when (val value = (this as? IrConst<*>)?.value as? Number) {
@@ -299,6 +299,7 @@ private val jvmFilePhases =
anonymousObjectSuperConstructorPhase then
tailrecPhase then
forLoopsPhase then
jvmInlineClassPhase then
sharedVariablesPhase then
@@ -318,7 +319,6 @@ private val jvmFilePhases =
jvmDefaultConstructorPhase then
forLoopsPhase then
flattenStringConcatenationPhase then
foldConstantLoweringPhase then
computeStringTrimPhase then
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.common.ir.copyParameterDeclarationsFrom
import org.jetbrains.kotlin.backend.common.ir.passTypeArgumentsFrom
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlockBody
import org.jetbrains.kotlin.backend.common.lower.loops.forLoopsPhase
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
@@ -42,7 +43,9 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
val jvmInlineClassPhase = makeIrFilePhase(
::JvmInlineClassLowering,
name = "Inline Classes",
description = "Lower inline classes"
description = "Lower inline classes",
// forLoopsPhase may produce UInt and ULong which are inline classes.
prerequisite = setOf(forLoopsPhase)
)
/**
@@ -1,5 +1,4 @@
// IGNORE_BACKEND: JVM_IR
// TODO KT-36773 Use counter loop when generating 'for' loop over an unsigned range in JVM_IR
// WITH_RUNTIME
fun testUIntRangeLiteral(a: UInt, b: UInt): Int {
var s = 0