diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt index b66eabd2990..7aa9570bf84 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt @@ -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): 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): 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): 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): 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): 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): 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, 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. diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt index 50f843863ac..66bfb7cc81e 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt @@ -78,51 +78,48 @@ internal abstract class NumericForLoopHeader( 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( /** 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 { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt index 984e014c968..13b97171e84 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt @@ -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() - 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() + 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), diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionType.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionType.kt new file mode 100644 index 00000000000..631a4650e83 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionType.kt @@ -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): 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) : + 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) : + 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) : + 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 `` 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, + 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) : + 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) : + 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) +}