From 03ef3724f494639b3692156ef2af430619aa94eb Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Thu, 2 Apr 2020 16:32:14 -0700 Subject: [PATCH] ForLoopsLowering: Add support for unsigned progressions. --- .../jetbrains/kotlin/backend/common/ir/Ir.kt | 35 +++- .../common/lower/loops/ForLoopsLowering.kt | 3 +- .../backend/common/lower/loops/HeaderInfo.kt | 147 ++++++++++----- .../common/lower/loops/HeaderProcessor.kt | 174 +++++++++++------- .../common/lower/loops/ProgressionHandlers.kt | 89 ++++++--- .../backend/common/lower/loops/Utils.kt | 13 -- .../jetbrains/kotlin/backend/jvm/JvmLower.kt | 2 +- .../jvm/lower/JvmInlineClassLowering.kt | 5 +- .../forLoop/forInOptimizableUnsignedRange.kt | 3 +- 9 files changed, 313 insertions(+), 158 deletions(-) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt index 21995ac7d13..44361a94ad6 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt @@ -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) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt index 427eb57ed57..c199ff9c812 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt @@ -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) ) } 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 d68d0902e47..d12756e2545 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 @@ -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): IrType = elementClassifier(symbols).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 -> 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): 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), 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 dcc529271a7..5572b6c69ef 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 @@ -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, - symbols: Symbols, builder: DeclarationIrBuilder ): List @@ -61,6 +59,7 @@ internal interface ForLoopHeader { internal abstract class NumericForLoopHeader( protected val headerInfo: T, builder: DeclarationIrBuilder, + context: CommonBackendContext, protected val isLastInclusive: Boolean ) : ForLoopHeader { @@ -68,16 +67,18 @@ internal abstract class NumericForLoopHeader( 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( // 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( // 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( 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( /** 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( 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( 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(headerInfo, builder, isLastInclusive = true) { + builder: DeclarationIrBuilder, + context: CommonBackendContext +) : NumericForLoopHeader(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, - symbols: Symbols, 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(headerInfo, builder, isLastInclusive = false) { + builder: DeclarationIrBuilder, + context: CommonBackendContext +) : NumericForLoopHeader(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, - symbols: Symbols, 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, - symbols: Symbols, 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, - symbols: Symbols, 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) } } 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 afd089202ee..8f6cc32035c 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 @@ -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) : 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)) } } } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/Utils.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/Utils.kt index 812f76c78cf..a47b79ddb16 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/Utils.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/Utils.kt @@ -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) { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index a720c3b1e0a..c14873d48f7 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -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 diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt index 75eb70f7b50..4f1a7492b15 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt @@ -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) ) /** diff --git a/compiler/testData/codegen/bytecodeText/forLoop/forInOptimizableUnsignedRange.kt b/compiler/testData/codegen/bytecodeText/forLoop/forInOptimizableUnsignedRange.kt index 2a11658762f..ce704483297 100644 --- a/compiler/testData/codegen/bytecodeText/forLoop/forInOptimizableUnsignedRange.kt +++ b/compiler/testData/codegen/bytecodeText/forLoop/forInOptimizableUnsignedRange.kt @@ -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