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 new file mode 100644 index 00000000000..26d6c4e459a --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ForLoopsLowering.kt @@ -0,0 +1,257 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.FileLoweringPass +import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext +import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.irIfThen +import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol +import org.jetbrains.kotlin.ir.types.toKotlinType +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.util.OperatorNameConventions + +val forLoopsPhase = makeIrFilePhase( + ::ForLoopsLowering, + name = "ForLoopsLowering", + description = "For loops lowering" +) + +/** + * This lowering pass optimizes for-loops. + * + * Replace iteration over ranges (X.indices, a..b, etc.) and arrays with + * simple while loop over primitive induction variable. + */ +internal class ForLoopsLowering(val context: CommonBackendContext) : FileLoweringPass { + + private val headerInfoBuilder = HeaderInfoBuilder(context) + + override fun lower(irFile: IrFile) { + val oldLoopToNewLoop = mutableMapOf() + val transformer = RangeLoopTransformer(context, oldLoopToNewLoop, headerInfoBuilder) + irFile.transformChildrenVoid(transformer) + // Update references in break/continue. + irFile.transformChildrenVoid(object : IrElementTransformerVoid() { + override fun visitBreakContinue(jump: IrBreakContinue): IrExpression { + oldLoopToNewLoop[jump.loop]?.let { jump.loop = it } + return jump + } + }) + } +} + +private class RangeLoopTransformer( + val context: CommonBackendContext, + val oldLoopToNewLoop: MutableMap, + headerInfoBuilder: HeaderInfoBuilder +) : IrElementTransformerVoidWithContext() { + + private val symbols = context.ir.symbols + private val iteratorToLoopHeader = mutableMapOf() + private val headerProcessor = HeaderProcessor(context, headerInfoBuilder, this::getScopeOwnerSymbol) + + fun getScopeOwnerSymbol() = currentScope!!.scope.scopeOwnerSymbol + + override fun visitVariable(declaration: IrVariable): IrStatement { + val initializer = declaration.initializer + if (initializer == null || initializer !is IrCall) { + return super.visitVariable(declaration) + } + return when (initializer.origin) { + IrStatementOrigin.FOR_LOOP_ITERATOR -> + processHeader(declaration) + IrStatementOrigin.FOR_LOOP_NEXT -> + processNext(declaration) + else -> null + } ?: super.visitVariable(declaration) + } + + private fun processHeader(variable: IrVariable): IrStatement? { + assert(variable.symbol !in iteratorToLoopHeader) + val forLoopInfo = headerProcessor.processHeader(variable) + ?: return null + iteratorToLoopHeader[variable.symbol] = forLoopInfo + return IrCompositeImpl( + variable.startOffset, + variable.endOffset, + context.irBuiltIns.unitType, + null, + forLoopInfo.declarations + ) + } + + /** + * This loop + * + * for (i in first..last step foo) { ... } + * + * is represented in IR in such a manner: + * + * val it = (first..last step foo).iterator() + * while (it.hasNext()) { + * val i = it.next() + * ... + * } + * + * We transform it into the following loop: + * + * var it = first + * if (it <= last) { // (it >= last if the progression is decreasing) + * do { + * val i = it++ + * ... + * } while (i != last) + * } + * + * In case of iteration over array we transform it into following: + * while (i <= array.size - 1) { + * val element = array[i] + * i++ + * ... + * } + */ + // TODO: Lower `for (i in a until b)` to loop with precondition: for (i = a; i < b; a++); + override fun visitWhileLoop(loop: IrWhileLoop): IrExpression { + if (loop.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE) { + return super.visitWhileLoop(loop) + } + return with(context.createIrBuilder(getScopeOwnerSymbol(), loop.startOffset, loop.endOffset)) { + val newBody = loop.body?.transform(this@RangeLoopTransformer, null)?.let { + if (it is IrContainerExpression && !it.isTransparentScope) { + IrCompositeImpl(startOffset, endOffset, it.type, it.origin, it.statements) + } else { + it + } + } + val loopHeader = getLoopHeader(loop.condition) + ?: return super.visitWhileLoop(loop) + val newLoop = loopHeader.buildBody(this, loop, newBody) + oldLoopToNewLoop[loop] = newLoop + // Build a check for an empty progression before the loop. + if (loopHeader is ProgressionLoopHeader) { + buildEmptinessCheck(newLoop, loopHeader) + } else { + newLoop + } + } + } + + private fun DeclarationIrBuilder.buildMinValueCondition(forLoopHeader: ForLoopHeader): IrExpression { + // Condition for a corner case: for (i in a until Int.MIN_VALUE) {}. + // Check if forLoopHeader.bound > MIN_VALUE. + val progressionType = forLoopHeader.progressionType + val irBuiltIns = context.irBuiltIns + val minConst = when (progressionType) { + ProgressionType.INT_PROGRESSION -> IrConstImpl + .int(startOffset, endOffset, irBuiltIns.intType, Int.MIN_VALUE) + ProgressionType.CHAR_PROGRESSION -> IrConstImpl + .char(startOffset, endOffset, irBuiltIns.charType, 0.toChar()) + ProgressionType.LONG_PROGRESSION -> IrConstImpl + .long(startOffset, endOffset, irBuiltIns.longType, Long.MIN_VALUE) + } + val compareTo = symbols.getBinaryOperator( + OperatorNameConventions.COMPARE_TO, + forLoopHeader.bound.type.toKotlinType(), + minConst.type.toKotlinType() + ) + return irCall(irBuiltIns.greaterFunByOperandType[irBuiltIns.int]?.symbol!!).apply { + val compareToCall = irCall(compareTo).apply { + dispatchReceiver = irGet(forLoopHeader.bound) + putValueArgument(0, minConst) + } + putValueArgument(0, compareToCall) + putValueArgument(1, irInt(0)) + } + } + + private fun DeclarationIrBuilder.buildEmptinessCheck(loop: IrLoop, loopHeader: ProgressionLoopHeader): IrExpression { + val builtIns = context.irBuiltIns + val comparingBuiltIn = loopHeader.comparingFunction(builtIns) + + // Check if inductionVariable <= last (or >= in case of downTo). + // TODO: Use comparingBuiltIn directly + val compareTo = symbols.getBinaryOperator( + OperatorNameConventions.COMPARE_TO, + loopHeader.inductionVariable.type.toKotlinType(), + loopHeader.last.type.toKotlinType() + ) + + val check = irCall(comparingBuiltIn).apply { + putValueArgument( + 0, + irCallOp(compareTo, compareTo.owner.returnType, irGet(loopHeader.inductionVariable), irGet(loopHeader.last)) + ) + putValueArgument(1, irInt(0)) + } + + // Process closed and open ranges in different manners. + return if (loopHeader.closed) { + irIfThen(check, loop) // if (inductionVariable <= last) { loop } + } else { + // Take into account a corner case: for (i in a until Int.MIN_VALUE) {}. + // if (inductionVariable <= last && bound > MIN_VALUE) { loop } + irIfThen(check, irIfThen(buildMinValueCondition(loopHeader), loop)) + } + } + + private fun getLoopHeader(oldCondition: IrExpression): ForLoopHeader? { + if (oldCondition !is IrCall || oldCondition.origin != IrStatementOrigin.FOR_LOOP_HAS_NEXT) { + return null + } + val irIteratorAccess = oldCondition.dispatchReceiver as? IrGetValue + ?: throw AssertionError() + // Return null if we didn't lower the corresponding header. + return iteratorToLoopHeader[irIteratorAccess.symbol] + } + + // Lower getting a next induction variable value. + fun processNext(variable: IrVariable): IrExpression? { + val initializer = variable.initializer as IrCall + + val iterator = initializer.dispatchReceiver as? IrGetValue + ?: throw AssertionError() + val forLoopInfo = iteratorToLoopHeader[iterator.symbol] + ?: return null // If we didn't lower a corresponding header. + // TODO: Use PLUS_ASSIGN to increment + val plusOperator = symbols.getBinaryOperator( + OperatorNameConventions.PLUS, + forLoopInfo.inductionVariable.type.toKotlinType(), + forLoopInfo.step.type.toKotlinType() + ) + if (forLoopInfo is ProgressionLoopHeader) { + forLoopInfo.loopVariable = variable + } + return with(context.createIrBuilder(getScopeOwnerSymbol(), initializer.startOffset, initializer.endOffset)) { + variable.initializer = forLoopInfo.initializeLoopVariable(symbols, this) + val increment = irSetVar( + forLoopInfo.inductionVariable.symbol, irCallOp( + plusOperator, plusOperator.owner.returnType, + irGet(forLoopInfo.inductionVariable), + irGet(forLoopInfo.step) + ) + ) + IrCompositeImpl( + variable.startOffset, + variable.endOffset, + context.irBuiltIns.unitType, + IrStatementOrigin.FOR_LOOP_NEXT, + listOf(variable, increment) + ) + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..7e410f1d374 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.lower.matchers.IrCallMatcher +import org.jetbrains.kotlin.backend.common.utils.isSubtypeOf +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration +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.types.IrType +import org.jetbrains.kotlin.ir.util.defaultType +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 reversed() +// TODO: Handle direct iteration on Ranges/Progressions (e.g., NOT using rangeTo or step) +// TODO: Handle Strings/CharSequences + +enum class ProgressionType(val numberCastFunctionName: Name) { + INT_PROGRESSION(Name.identifier("toInt")), + LONG_PROGRESSION(Name.identifier("toLong")), + CHAR_PROGRESSION(Name.identifier("toChar")); +} + +// Information about loop that is required by HeaderProcessor to build ForLoopHeader +internal sealed class HeaderInfo( + val progressionType: ProgressionType, + val lowerBound: IrExpression, + val upperBound: IrExpression, + val step: IrExpression?, // null value denotes default step (1) + val increasing: Boolean, + val needLastCalculation: Boolean, + val closed: Boolean +) + +internal class ProgressionHeaderInfo( + progressionType: ProgressionType, + lowerBound: IrExpression, + upperBound: IrExpression, + step: IrExpression? = null, + increasing: Boolean = true, + needLastCalculation: Boolean = false, + closed: Boolean = true +) : HeaderInfo(progressionType, lowerBound, upperBound, step, increasing, needLastCalculation, closed) + +internal class ArrayHeaderInfo( + lowerBound: IrExpression, + upperBound: IrExpression, + val arrayDeclaration: IrValueDeclaration +) : HeaderInfo( + ProgressionType.INT_PROGRESSION, + lowerBound, + upperBound, + step = null, + increasing = true, + needLastCalculation = false, + closed = false +) + +internal interface HeaderInfoHandler { + val matcher: IrCallMatcher + + fun build(call: IrCall, data: T): HeaderInfo? + + fun handle(irCall: IrCall, data: T) = if (matcher(irCall)) { + build(irCall, data) + } else { + null + } +} +internal typealias ProgressionHandler = HeaderInfoHandler + +// We need to wrap builder into visitor because of `StepHandler` which has to visit its subtree +// to get information about underlying progression. +private class ProgressionHeaderInfoBuilder(val context: CommonBackendContext) : IrElementVisitor { + + private val symbols = context.ir.symbols + + private val progressionElementTypes = symbols.integerClassesTypes + symbols.char.descriptor.defaultType + + private val progressionHandlers = listOf( + IndicesHandler(context), + UntilHandler(progressionElementTypes), + DownToHandler(progressionElementTypes), + StepHandler(context, this), + RangeToHandler(progressionElementTypes) + ) + + private fun IrType.getProgressionType(): ProgressionType? = when { + isSubtypeOf(symbols.charProgression.owner.defaultType) -> ProgressionType.CHAR_PROGRESSION + isSubtypeOf(symbols.intProgression.owner.defaultType) -> ProgressionType.INT_PROGRESSION + isSubtypeOf(symbols.longProgression.owner.defaultType) -> ProgressionType.LONG_PROGRESSION + else -> null + } + + override fun visitElement(element: IrElement, data: Nothing?): HeaderInfo? = null + + override fun visitCall(expression: IrCall, data: Nothing?): HeaderInfo? { + val progressionType = expression.type.getProgressionType() + ?: return null + return progressionHandlers.firstNotNullResult { it.handle(expression, progressionType) } + } +} + +internal class HeaderInfoBuilder(context: CommonBackendContext) { + private val progressionInfoBuilder = ProgressionHeaderInfoBuilder(context) + private val arrayIterationHandler = ArrayIterationHandler(context) + + fun build(variable: IrVariable): HeaderInfo? { + val initializer = variable.initializer!! as IrCall + return arrayIterationHandler.handle(initializer, null) + ?: initializer.dispatchReceiver?.accept(progressionInfoBuilder, null) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..ac8d7634980 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt @@ -0,0 +1,300 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.utils.isSubtypeOf +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.builders.irImplicitCast +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrLoop +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrDoWhileLoopImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrWhileLoopImpl +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.types.impl.IrStarProjectionImpl +import org.jetbrains.kotlin.ir.util.functions +import org.jetbrains.kotlin.ir.util.isNullable +import org.jetbrains.kotlin.util.OperatorNameConventions + +/** Contains information about variables used in the loop. */ +internal sealed class ForLoopHeader( + val inductionVariable: IrVariable, + val bound: IrVariable, + val last: IrVariable, + val step: IrVariable, + val progressionType: ProgressionType +) { + abstract fun initializeLoopVariable(symbols: Symbols, builder: DeclarationIrBuilder): IrExpression + + abstract val declarations: List + + abstract fun buildBody(builder: DeclarationIrBuilder, loop: IrLoop, newBody: IrExpression?): IrLoop +} + +internal class ProgressionLoopHeader( + headerInfo: ProgressionHeaderInfo, + inductionVariable: IrVariable, + bound: IrVariable, + last: IrVariable, + step: IrVariable, + var loopVariable: IrVariable? = null +) : ForLoopHeader(inductionVariable, bound, last, step, headerInfo.progressionType) { + + val closed = headerInfo.closed + + private val increasing = headerInfo.increasing + + fun comparingFunction(builtIns: IrBuiltIns) = if (increasing) + builtIns.lessOrEqualFunByOperandType[builtIns.int]?.symbol!! + else + builtIns.greaterOrEqualFunByOperandType[builtIns.int]?.symbol!! + + override fun initializeLoopVariable(symbols: Symbols, builder: DeclarationIrBuilder) = with(builder) { + irGet(inductionVariable) + } + + override val declarations: List + get() = listOf(inductionVariable, step, bound, last) + + override fun buildBody(builder: DeclarationIrBuilder, loop: IrLoop, newBody: IrExpression?): IrLoop = with(builder) { + assert(loopVariable != null) + val newCondition = irCall(context.irBuiltIns.booleanNotSymbol).apply { + putValueArgument(0, irCall(context.irBuiltIns.eqeqSymbol).apply { + putValueArgument(0, irGet(loopVariable!!)) + putValueArgument(1, irGet(last)) + }) + } + IrDoWhileLoopImpl(loop.startOffset, loop.endOffset, loop.type, loop.origin).apply { + label = loop.label + condition = newCondition + body = newBody + } + } +} + +internal class ArrayLoopHeader( + headerInfo: ArrayHeaderInfo, + inductionVariable: IrVariable, + bound: IrVariable, + last: IrVariable, + step: IrVariable +) : ForLoopHeader(inductionVariable, bound, last, step, ProgressionType.INT_PROGRESSION) { + + private val arrayDeclaration = headerInfo.arrayDeclaration + + override fun initializeLoopVariable(symbols: Symbols, builder: DeclarationIrBuilder) = with(builder) { + val arrayClass = (arrayDeclaration.type.classifierOrNull) as IrClassSymbol + val arrayGetFun = arrayClass.owner.functions.find { it.name.toString() == "get" }!! + irCall(arrayGetFun).apply { + dispatchReceiver = irGet(arrayDeclaration) + putValueArgument(0, irGet(inductionVariable)) + } + } + + override val declarations: List + get() = listOf(arrayDeclaration, inductionVariable, step, bound, last) + + override fun buildBody(builder: DeclarationIrBuilder, loop: IrLoop, newBody: IrExpression?): IrLoop = with(builder) { + val builtIns = context.irBuiltIns + val callee = builtIns.lessOrEqualFunByOperandType[builtIns.int]?.symbol!! + val newCondition = irCall(callee).apply { + putValueArgument(0, irGet(inductionVariable)) + putValueArgument(1, irGet(last)) + } + IrWhileLoopImpl(loop.startOffset, loop.endOffset, loop.type, loop.origin).apply { + label = loop.label + condition = newCondition + body = newBody + } + } +} + +private fun ProgressionType.elementType(context: CommonBackendContext): IrType = when (this) { + ProgressionType.INT_PROGRESSION -> context.irBuiltIns.intType + ProgressionType.LONG_PROGRESSION -> context.irBuiltIns.longType + ProgressionType.CHAR_PROGRESSION -> context.irBuiltIns.charType +} + +// Given the for loop iterator variable, extract information about iterable subject +// and create ForLoopHeader from it. +internal class HeaderProcessor( + private val context: CommonBackendContext, + private val headerInfoBuilder: HeaderInfoBuilder, + private val scopeOwnerSymbol: () -> IrSymbol +) { + + private val symbols = context.ir.symbols + + fun processHeader(variable: IrVariable): ForLoopHeader? { + + assert(variable.origin == IrDeclarationOrigin.FOR_LOOP_ITERATOR) + // Create iterator type with star projections. + val iteratorType = symbols.iterator.run { + createType( + hasQuestionMark = false, + arguments = descriptor.declaredTypeParameters.map { IrStarProjectionImpl } + ) + } + + if (!variable.type.isSubtypeOf(iteratorType)) { + return null + } + val builder = context.createIrBuilder(scopeOwnerSymbol(), variable.startOffset, variable.endOffset) + // Collect loop info and form the loop header composite. + val progressionInfo = headerInfoBuilder.build(variable) + ?: return null + if (progressionInfo is ArrayHeaderInfo) { + progressionInfo.arrayDeclaration.parent = variable.parent + } + with(builder) { + with(progressionInfo) { + /** + * For this loop: + * `for (i in a() .. b() step c() step d())` + * We need to call functions in the following order: a, b, c, d. + * So we call b() before step calculations and then call last element calculation function (if required). + */ + // 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. + val inductionVariable = scope.createTemporaryVariable( + lowerBound.castIfNecessary(progressionType), + nameHint = "inductionVariable", + isMutable = true, + origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE + ) + + val upperBoundTmpVariable = scope.createTemporaryVariable( + ensureNotNullable(upperBound.castIfNecessary(progressionType)), + nameHint = "upperBound", + origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE + ) + + val stepExpression = if (step != null) { + ensureNotNullable(if (increasing) step else step.unaryMinus()) + } else { + defaultStep(startOffset, endOffset) + } + + val stepValue = scope.createTemporaryVariable( + stepExpression, + nameHint = "step", + origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE + ) + + // Calculate the last element of the progression + // The last element can be: + // boundValue, if step is 1 and the range is closed. + // boundValue - 1, if step is 1 and the range is open. + // getProgressionLast(inductionVariable, boundValue, step), if step != 1 and the range is closed. + // getProgressionLast(inductionVariable, boundValue - 1, step), if step != 1 and the range is open. + val lastExpression = if (closed) { + irGet(upperBoundTmpVariable) + } else { + val decrementSymbol = symbols.getUnaryOperator(OperatorNameConventions.DEC, upperBoundTmpVariable.type.toKotlinType()) + irCall(decrementSymbol.owner).apply { + dispatchReceiver = irGet(upperBoundTmpVariable) + } + } + // In case of `step` we need to calculate the last element. + val lastElement = + if (needLastCalculation) { + TODO("Implement and use irGetProgressionLast") +// irGetProgressionLast(progressionType, inductionVariable, lastExpression, stepValue) + } else { + lastExpression + } + val lastValue = scope.createTemporaryVariable( + lastElement, + nameHint = "last", + origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE + ) + + return when (progressionInfo) { + is ArrayHeaderInfo -> ArrayLoopHeader( + progressionInfo, + inductionVariable, + upperBoundTmpVariable, + lastValue, + stepValue + ) + is ProgressionHeaderInfo -> ProgressionLoopHeader( + progressionInfo, + inductionVariable, + upperBoundTmpVariable, + lastValue, + stepValue + ) + } + } + } + } + + + private fun IrExpression.castIfNecessary(progressionType: ProgressionType): IrExpression { + return if (type.toKotlinType() == progressionType.elementType(context).toKotlinType()) { + this + } else { + val function = symbols.getFunction(progressionType.numberCastFunctionName, type.toKotlinType()) + IrCallImpl(startOffset, endOffset, function.owner.returnType, function) + .apply { dispatchReceiver = this@castIfNecessary } + } + } + + private fun DeclarationIrBuilder.ensureNotNullable(expression: IrExpression) = + if (expression.type is IrSimpleType && expression.type.isNullable()) { + irImplicitCast(expression, expression.type.makeNotNull()) + } else { + expression + } + + private fun IrExpression.unaryMinus(): IrExpression { + val unaryOperator = symbols.getUnaryOperator(OperatorNameConventions.UNARY_MINUS, type.toKotlinType()) + return IrCallImpl(startOffset, endOffset, unaryOperator.owner.returnType, unaryOperator).apply { + dispatchReceiver = this@unaryMinus + } + } + + private fun HeaderInfo.defaultStep(startOffset: Int, endOffset: Int): IrExpression { + val type = progressionType.elementType(context) + val step = if (increasing) 1 else -1 + return when { + type.isInt() || type.isChar() -> + IrConstImpl.int(startOffset, endOffset, context.irBuiltIns.intType, step) + type.isLong() -> + IrConstImpl.long(startOffset, endOffset, context.irBuiltIns.longType, step.toLong()) + else -> throw IllegalArgumentException() + } + } + +// private fun irGetProgressionLast( +// progressionType: ProgressionType, +// first: IrVariable, +// lastExpression: IrExpression, +// step: IrVariable +// ): IrExpression { +// val symbol = symbols.getProgressionLast[progressionType.elementType(context).toKotlinType()] +// ?: throw IllegalArgumentException("No `getProgressionLast` for type ${step.type} ${lastExpression.type}") +// val startOffset = lastExpression.startOffset +// val endOffset = lastExpression.endOffset +// return IrCallImpl(startOffset, endOffset, symbol.owner.returnType, symbol).apply { +// putValueArgument(0, IrGetValueImpl(startOffset, endOffset, first.type, first.symbol)) +// putValueArgument(1, lastExpression) +// putValueArgument(2, IrGetValueImpl(startOffset, endOffset, step.type, step.symbol)) +// } +// } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..cc213851468 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt @@ -0,0 +1,192 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.matchers.IrCallMatcher +import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher +import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher +import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.builders.irInt +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.properties +import org.jetbrains.kotlin.ir.visitors.IrElementVisitor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.SimpleType + +// TODO: What if functions like Int.rangeTo(Char) will be added to stdlib later? +internal class RangeToHandler(val progressionElementTypes: Collection) : ProgressionHandler { + override val matcher = SimpleCalleeMatcher { + dispatchReceiver { it != null && it.type.toKotlinType() in progressionElementTypes } + fqName { it.pathSegments().last() == Name.identifier("rangeTo") } + parameterCount { it == 1 } + parameter(0) { it.type.toKotlinType() in progressionElementTypes } + } + + override fun build(call: IrCall, data: ProgressionType) = + ProgressionHeaderInfo(data, call.dispatchReceiver!!, call.getValueArgument(0)!!) +} + +internal class DownToHandler(val progressionElementTypes: Collection) : ProgressionHandler { + override val matcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.downTo"), progressionElementTypes) + parameter(0) { it.type.toKotlinType() in progressionElementTypes } + } + + override fun build(call: IrCall, data: ProgressionType): HeaderInfo? = + ProgressionHeaderInfo( + data, + call.extensionReceiver!!, + call.getValueArgument(0)!!, + increasing = false + ) +} + +internal class UntilHandler(val progressionElementTypes: Collection) : ProgressionHandler { + override val matcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.until"), progressionElementTypes) + parameter(0) { it.type.toKotlinType() in progressionElementTypes } + } + + override fun build(call: IrCall, data: ProgressionType): HeaderInfo? = + ProgressionHeaderInfo( + data, + call.extensionReceiver!!, + call.getValueArgument(0)!!, + closed = false + ) +} + +internal class IndicesHandler(val context: CommonBackendContext) : ProgressionHandler { + + override val matcher = createIrCallMatcher { + callee { + fqName { it == FqName("kotlin.collections.") } + parameterCount { it == 0 } + } + // TODO: Handle Collection<*>.indices + // TODO: Handle CharSequence.indices + extensionReceiver { it != null && KotlinBuiltIns.isArrayOrPrimitiveArray(it.type.toKotlinType()) } + } + + override fun build(call: IrCall, data: ProgressionType): HeaderInfo? = + with(context.createIrBuilder(call.symbol, call.startOffset, call.endOffset)) { + val lowerBound = irInt(0) + val arrayClass = (call.extensionReceiver!!.type.classifierOrNull) as IrClassSymbol + val arraySizeProperty = arrayClass.owner.properties.find { it.name.toString() == "size" }!! + val upperBound = irCall(arraySizeProperty.getter!!).apply { + dispatchReceiver = call.extensionReceiver + } + ProgressionHeaderInfo(data, lowerBound, upperBound, closed = false) + } + +} + +internal class StepHandler(context: CommonBackendContext, val visitor: IrElementVisitor) : ProgressionHandler { + private val symbols = context.ir.symbols + + override val matcher: IrCallMatcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.step"), symbols.progressionClassesTypes) + parameter(0) { it.type.isInt() || it.type.isLong() } + } + + private fun isDefaultStep(step: IrExpression?) = + step == null || (step is IrConst<*> && step.isOne()) + + override fun build(call: IrCall, data: ProgressionType): HeaderInfo? { + val nestedInfo = call.extensionReceiver!!.accept(visitor, null) + ?: return null + + // Due to KT-27607 nested non-default steps could lead to incorrect behaviour. + // So disable optimization of such rare cases for now. + if (!isDefaultStep(nestedInfo.step)) { + return null + } + + val newStep = call.getValueArgument(0)!! + val (newStepCheck, needBoundCalculation) = irCheckProgressionStep(symbols, data, newStep) + return ProgressionHeaderInfo( + data, + nestedInfo.lowerBound, + nestedInfo.upperBound, + newStepCheck, + nestedInfo.increasing, + needBoundCalculation, + nestedInfo.closed + ) + } + + private fun IrConst<*>.isOne() = when (kind) { + IrConstKind.Long -> value as Long == 1L + IrConstKind.Int -> value as Int == 1 + else -> false + } + + private fun IrExpression.isPositiveConst() = this is IrConst<*> && + ((kind == IrConstKind.Long && value as Long > 0) || (kind == IrConstKind.Int && value as Int > 0)) + + // Used only by the assert. + private fun stepHasRightType(step: IrExpression, progressionType: ProgressionType) = when (progressionType) { + + ProgressionType.CHAR_PROGRESSION, + ProgressionType.INT_PROGRESSION -> step.type.makeNotNull().isInt() + + ProgressionType.LONG_PROGRESSION -> step.type.makeNotNull().isLong() + } + + private fun irCheckProgressionStep(symbols: Symbols, progressionType: ProgressionType, step: IrExpression) = + if (step.isPositiveConst()) { + step to !(step as IrConst<*>).isOne() + } else + TODO("Implement call to checkProgressionStep") +// { +// // The frontend checks if the step has a right type (Long for LongProgression and Int for {Int/Char}Progression) +// // so there is no need to cast it. +// assert(stepHasRightType(step, progressionType)) +// +// val symbol = symbols.checkProgressionStep[step.type.makeNotNull().toKotlinType()] +// ?: throw IllegalArgumentException("No `checkProgressionStep` for type ${step.type}") +// IrCallImpl(step.startOffset, step.endOffset, symbol.owner.returnType, symbol).apply { +// putValueArgument(0, step) +// } to true +// } +} + +internal class ArrayIterationHandler(val context: CommonBackendContext) : HeaderInfoHandler { + + // No support for rare cases like `T : IntArray` for now. + override val matcher = createIrCallMatcher { + origin { it == IrStatementOrigin.FOR_LOOP_ITERATOR } + dispatchReceiver { it != null && KotlinBuiltIns.isArrayOrPrimitiveArray(it.type.toKotlinType()) } + } + + // Consider case like `for (elem in A) { f(elem) }` + // If we lower it to `for (i in A.indices) { f(A[i]) }` + // Then we will break program behaviour if A is an expression with side-effect. + // Instead, we lower it to + // ``` + // val a = A + // for (i in a.indices) { f(a[i]) } + // ``` + override fun build(call: IrCall, data: Nothing?): HeaderInfo? = + with(context.createIrBuilder(call.symbol, call.startOffset, call.endOffset)) { + val arrayReference = scope.createTemporaryVariable(call.dispatchReceiver!!) + val arrayClass = (arrayReference.type.classifierOrNull) as IrClassSymbol + val arraySizeProperty = arrayClass.owner.properties.find { it.name.toString() == "size" }!! + val upperBound = irCall(arraySizeProperty.getter!!).apply { + dispatchReceiver = irGet(arrayReference) + } + ArrayHeaderInfo(irInt(0), upperBound, arrayReference) + } +} diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/matchers/IrCallMatcher.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/matchers/IrCallMatcher.kt new file mode 100644 index 00000000000..15c0f898290 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/matchers/IrCallMatcher.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.matchers + +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin + + +internal interface IrCallMatcher : (IrCall) -> Boolean + +/** + * IrCallMatcher that puts restrictions only on its callee. + */ +internal class SimpleCalleeMatcher( + restrictions: IrFunctionMatcherContainer.() -> Unit +) : IrCallMatcher { + + private val calleeRestriction: IrFunctionMatcher = createIrFunctionRestrictions(restrictions) + + override fun invoke(call: IrCall) = calleeRestriction(call.symbol.owner) +} + +internal class IrCallExtensionReceiverMatcher( + val restriction: (IrExpression?) -> Boolean +) : IrCallMatcher { + override fun invoke(call: IrCall) = restriction(call.extensionReceiver) +} + +internal class IrCallDispatchReceiverMatcher( + val restriction: (IrExpression?) -> Boolean +) : IrCallMatcher { + override fun invoke(call: IrCall) = restriction(call.dispatchReceiver) +} + +internal class IrCallOriginMatcher( + val restriction: (IrStatementOrigin?) -> Boolean +) : IrCallMatcher { + override fun invoke(call: IrCall) = restriction(call.origin) +} + +internal open class IrCallMatcherContainer : IrCallMatcher { + + private val matchers = mutableListOf() + + fun add(matcher: IrCallMatcher) { + matchers += matcher + } + + fun extensionReceiver(restriction: (IrExpression?) -> Boolean) = + add(IrCallExtensionReceiverMatcher(restriction)) + + fun origin(restriction: (IrStatementOrigin?) -> Boolean) = + add(IrCallOriginMatcher(restriction)) + + fun callee(restrictions: IrFunctionMatcherContainer.() -> Unit) { + add(SimpleCalleeMatcher(restrictions)) + } + + fun dispatchReceiver(restriction: (IrExpression?) -> Boolean) = + add(IrCallDispatchReceiverMatcher(restriction)) + + override fun invoke(call: IrCall) = matchers.all { it(call) } +} + +internal fun createIrCallMatcher(restrictions: IrCallMatcherContainer.() -> Unit) = + IrCallMatcherContainer().apply(restrictions) \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/matchers/IrFunctionMatcher.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/matchers/IrFunctionMatcher.kt new file mode 100644 index 00000000000..535faf2e2af --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/matchers/IrFunctionMatcher.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.matchers + +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrValueParameter +import org.jetbrains.kotlin.ir.types.toKotlinType +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.types.SimpleType + +internal interface IrFunctionMatcher : (IrFunction) -> Boolean + +internal class ParameterMatcher( + val index: Int, + val restriction: (IrValueParameter) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + val params = function.valueParameters + return params.size > index && restriction(params[index]) + } +} + +internal class DispatchReceiverMatcher( + val restriction: (IrValueParameter?) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + return restriction(function.dispatchReceiverParameter) + } +} + +internal class ExtensionReceiverMatcher( + val restriction: (IrValueParameter?) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + return restriction(function.extensionReceiverParameter) + } +} + +internal class ParameterCountMatcher( + val restriction: (Int) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + return restriction(function.valueParameters.size) + } +} + +internal class FqNameMatcher( + val restriction: (FqName) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + return restriction(function.descriptor.fqNameSafe) + } +} + +internal open class IrFunctionMatcherContainer : IrFunctionMatcher { + private val restrictions = mutableListOf() + + fun add(restriction: IrFunctionMatcher) { + restrictions += restriction + } + + fun fqName(restriction: (FqName) -> Boolean) = + add(FqNameMatcher(restriction)) + + fun parameterCount(restriction: (Int) -> Boolean) = + add(ParameterCountMatcher(restriction)) + + fun extensionReceiver(restriction: (IrValueParameter?) -> Boolean) = + add(ExtensionReceiverMatcher(restriction)) + + fun dispatchReceiver(restriction: (IrValueParameter?) -> Boolean) = + add(DispatchReceiverMatcher(restriction)) + + fun parameter(index: Int, restriction: (IrValueParameter) -> Boolean) = + add(ParameterMatcher(index, restriction)) + + override fun invoke(function: IrFunction) = restrictions.all { it(function) } +} + +internal fun createIrFunctionRestrictions(restrictions: IrFunctionMatcherContainer.() -> Unit) = + IrFunctionMatcherContainer().apply(restrictions) + +internal fun IrFunctionMatcherContainer.singleArgumentExtension( + fqName: FqName, + types: Collection +): IrFunctionMatcherContainer { + extensionReceiver { it != null && it.type.toKotlinType() in types } + parameterCount { it == 1 } + fqName { it == fqName } + return this +}