From c322d432a0857abf31f54ba3550bf3395fc253a2 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov <1580082+sbogolepov@users.noreply.github.com> Date: Fri, 23 Nov 2018 16:01:59 +0700 Subject: [PATCH] Add support for arrays in for loops lowering. (#2351) --- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 18 +- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 9 +- .../konan/lower/loops/ForLoopsLowering.kt | 441 ++++++------------ .../backend/konan/lower/loops/HeaderInfo.kt | 110 +++++ .../konan/lower/loops/HeaderProcessor.kt | 270 +++++++++++ .../konan/lower/loops/ProgressionHandlers.kt | 96 ++-- .../konan/lower/loops/ProgressionInfo.kt | 70 --- .../konan/lower/matchers/IrCallMatcher.kt | 19 + .../backend/konan/optimizations/DFGBuilder.kt | 20 +- backend.native/tests/build.gradle | 35 ++ .../codegen/controlflow/for_loops_array.kt | 40 ++ .../for_loops_array_break_continue.kt | 21 + .../controlflow/for_loops_array_indices.kt | 18 + .../controlflow/for_loops_array_mutation.kt | 13 + .../controlflow/for_loops_array_nested.kt | 25 + .../controlflow/for_loops_array_nullable.kt | 16 + .../for_loops_array_side_effects.kt | 23 + backend.native/tests/runtime/basic/hello1.kt | 4 +- .../org/jetbrains/ring/ForLoopsBenchmark.kt | 2 +- 19 files changed, 825 insertions(+), 425 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/HeaderInfo.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/HeaderProcessor.kt delete mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionInfo.kt create mode 100644 backend.native/tests/codegen/controlflow/for_loops_array.kt create mode 100644 backend.native/tests/codegen/controlflow/for_loops_array_break_continue.kt create mode 100644 backend.native/tests/codegen/controlflow/for_loops_array_indices.kt create mode 100644 backend.native/tests/codegen/controlflow/for_loops_array_mutation.kt create mode 100644 backend.native/tests/codegen/controlflow/for_loops_array_nested.kt create mode 100644 backend.native/tests/codegen/controlflow/for_loops_array_nullable.kt create mode 100644 backend.native/tests/codegen/controlflow/for_loops_array_side_effects.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index f8472abac71..a1fbde5d92c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -306,7 +306,6 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val } ?: error(descriptor.toString()) return symbolTable.referenceSimpleFunction(functionDescriptor) } - override val copyRangeTo = arrays.map { symbol -> val packageViewDescriptor = builtIns.builtInsModule.getPackage(KotlinBuiltIns.COLLECTIONS_PACKAGE_FQ_NAME) val functionDescriptor = packageViewDescriptor.memberScope @@ -317,21 +316,18 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val symbol.descriptor to symbolTable.referenceSimpleFunction(functionDescriptor) }.toMap() - val arrayGet = array.descriptor.unsubstitutedMemberScope + val arrayGet = arrays.associateWith { it.descriptor.unsubstitutedMemberScope .getContributedFunctions(Name.identifier("get"), NoLookupLocation.FROM_BACKEND) - .single().let { symbolTable.referenceSimpleFunction(it) } + .single().let { symbolTable.referenceSimpleFunction(it) } } - val arraySet = array.descriptor.unsubstitutedMemberScope - .getContributedFunctions(Name.identifier("set"), NoLookupLocation.FROM_BACKEND) - .single().let { symbolTable.referenceSimpleFunction(it) } + val arraySet = arrays.associateWith { it.descriptor.unsubstitutedMemberScope + .getContributedFunctions(Name.identifier("set"), NoLookupLocation.FROM_BACKEND) + .single().let { symbolTable.referenceSimpleFunction(it) } } - val arraySize = arrays.associateBy( - { it }, - { it.descriptor.unsubstitutedMemberScope + + val arraySize = arrays.associateWith { it.descriptor.unsubstitutedMemberScope .getContributedVariables(Name.identifier("size"), NoLookupLocation.FROM_BACKEND) .single().let { symbolTable.referenceSimpleFunction(it.getter!!) } } - ) - val valuesForEnum = symbolTable.referenceSimpleFunction( diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 5fee4856486..cb551ca7cbd 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -1247,7 +1247,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map if (element.origin != IrDeclarationOrigin.IR_TEMPORARY_VARIABLE) debugInfoLocalVariableLocation( + is IrVariable -> if (shouldGenerateDebugInfo(element)) debugInfoLocalVariableLocation( builder = context.debugInfo.builder, functionScope = locationInfo.scope, diType = element.descriptor.type.diType(context, codegen.llvmTargetData), @@ -1269,6 +1269,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map false + else -> true + } + private fun generateVariable(variable: IrVariable) { context.log{"generateVariable : ${ir2string(variable)}"} val value = variable.initializer?.let { evaluateExpression(it) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ForLoopsLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ForLoopsLowering.kt index 7c00b42099a..580969b90c2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ForLoopsLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ForLoopsLowering.kt @@ -11,12 +11,8 @@ 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.konan.Context -import org.jetbrains.kotlin.backend.konan.irasdescriptors.isSubtypeOf -import org.jetbrains.kotlin.backend.konan.irasdescriptors.typeWithStarProjections -import org.jetbrains.kotlin.ir.util.isSimpleTypeWithQuestionMark import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.* -import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrVariable import org.jetbrains.kotlin.ir.expressions.* @@ -27,304 +23,66 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.* import org.jetbrains.kotlin.util.OperatorNameConventions -/** This lowering pass optimizes range-based for loops. +/** This lowering pass optimizes for-loops. * - * NB! not implemented yet. - * Process `for (elem in array) { f(elem) }` construct first. - * Replace with `for (i in array.indices) { f(array.get(i)) }`. - * - * Next replace iteration over ranges (X.indices, a..b, etc.) with + * Replace iteration over ranges (X.indices, a..b, etc.) and arrays with * simple while loop over primitive induction variable. */ internal class ForLoopsLowering(val context: Context) : FileLoweringPass { - private val progressionInfoBuilder = ProgressionInfoBuilder(context) + private val headerInfoBuilder = HeaderInfoBuilder(context) override fun lower(irFile: IrFile) { - val transformer = ForLoopsTransformer(context, progressionInfoBuilder) - // Lower loops + val oldLoopToNewLoop = mutableMapOf() + val transformer = RangeLoopTransformer(context, oldLoopToNewLoop, headerInfoBuilder) irFile.transformChildrenVoid(transformer) - // Update references in break/continue. - irFile.transformChildrenVoid(object: IrElementTransformerVoid() { + irFile.transformChildrenVoid(object : IrElementTransformerVoid() { override fun visitBreakContinue(jump: IrBreakContinue): IrExpression { - transformer.oldLoopToNewLoop[jump.loop]?.let { jump.loop = it } + oldLoopToNewLoop[jump.loop]?.let { jump.loop = it } return jump } }) } } -/** Contains information about variables used in the loop. */ -internal data class ForLoopInfo( - val progressionInfo: ProgressionInfo, - val inductionVariable: IrVariable, - val bound: IrVariable, - val last: IrVariable, - val step: IrVariable, - var loopVariable: IrVariable? = null) - -private fun ProgressionType.elementType(context: Context): IrType = when (this) { - ProgressionType.INT_PROGRESSION -> context.irBuiltIns.intType - ProgressionType.LONG_PROGRESSION -> context.irBuiltIns.longType - ProgressionType.CHAR_PROGRESSION -> context.irBuiltIns.charType -} - -private class ForLoopsTransformer(val context: Context, val progressionInfoBuilder: ProgressionInfoBuilder) : IrElementTransformerVoidWithContext() { +private class RangeLoopTransformer( + val context: Context, + val oldLoopToNewLoop: MutableMap, + headerInfoBuilder: HeaderInfoBuilder) + : IrElementTransformerVoidWithContext() { private val symbols = context.ir.symbols - private val iteratorToLoopInfo = mutableMapOf() - internal val oldLoopToNewLoop = mutableMapOf() + private val iteratorToLoopHeader = mutableMapOf() + private val headerProcessor = HeaderProcessor(context, headerInfoBuilder, this::getScopeOwnerSymbol) - private val progressionElementClasses = - (symbols.integerClasses + symbols.char).toSet() + fun getScopeOwnerSymbol() = currentScope!!.scope.scopeOwnerSymbol - private val scopeOwnerSymbol - get() = currentScope!!.scope.scopeOwnerSymbol - - //region Util methods ============================================================================================== - private fun IrExpression.castIfNecessary(progressionType: ProgressionType): IrExpression { - assert(type.classifierOrNull in progressionElementClasses) - return if (type.classifierOrNull == progressionType.elementType(context).classifierOrNull) { - this - } else { - val function = symbols.getFunction(progressionType.numberCastFunctionName, type.toKotlinType()) - IrCallImpl(startOffset, endOffset, function.owner.returnType, function) - .apply { dispatchReceiver = this@castIfNecessary } + 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 DeclarationIrBuilder.ensureNotNullable(expression: IrExpression): IrExpression { - return if (expression.type.isSimpleTypeWithQuestionMark) { - 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 ProgressionInfo.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, lastExpression.endOffset, symbol.owner.returnType, symbol).apply { - putValueArgument(0, IrGetValueImpl(startOffset, endOffset, first.type, first.symbol)) - putValueArgument(1, lastExpression.castIfNecessary(progressionType)) - putValueArgument(2, IrGetValueImpl(startOffset, endOffset, step.type, step.symbol)) - } - } - //endregion - - //region Lowering ================================================================================================== - // Lower a loop header. - private fun processHeader(variable: IrVariable, initializer: IrCall): IrStatement? { - assert(variable.origin == IrDeclarationOrigin.FOR_LOOP_ITERATOR) - val symbol = variable.symbol - val iteratorType = symbols.iterator.typeWithStarProjections - - if (!variable.type.isSubtypeOf(iteratorType)) { - return null - } - assert(symbol !in iteratorToLoopInfo) - - val builder = context.createIrBuilder(scopeOwnerSymbol, variable.startOffset, variable.endOffset) - // Collect loop info and form the loop header composite. - val progressionInfo = initializer.dispatchReceiver?.accept(progressionInfoBuilder, null) + private fun processHeader(variable: IrVariable): IrStatement? { + assert(variable.symbol !in iteratorToLoopHeader) + val forLoopInfo = headerProcessor.processHeader(variable) ?: return null - - with(builder) { - with(progressionInfo) { - // 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 statements = mutableListOf() - - /** - * 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). - */ - val inductionVariable = scope.createTemporaryVariable(first.castIfNecessary(progressionType), - nameHint = "inductionVariable", - isMutable = true, - origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE).also { - statements.add(it) - } - - val boundValue = scope.createTemporaryVariable(ensureNotNullable(bound.castIfNecessary(progressionType)), - nameHint = "bound", - origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE) - .also { statements.add(it) } - - val stepExpression = if (step != null) { - if (increasing) step else step.unaryMinus() - } else { - defaultStep(startOffset, endOffset) - } - - val stepValue = scope.createTemporaryVariable(ensureNotNullable(stepExpression), - nameHint = "step", - origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE).also { - statements.add(it) - } - - // 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. - var lastExpression: IrExpression = if (!closed) { - val decrementSymbol = symbols.getUnaryOperator(OperatorNameConventions.DEC, boundValue.type.toKotlinType()) - irCall(decrementSymbol.owner).apply { - dispatchReceiver = irGet(boundValue) - } - } else { - irGet(boundValue) - } - if (needLastCalculation) { - lastExpression = irGetProgressionLast(progressionType, - inductionVariable, - lastExpression, - stepValue) - } - - val lastValue = scope.createTemporaryVariable(lastExpression, - nameHint = "last", - origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE).also { - statements.add(it) - } - - iteratorToLoopInfo[symbol] = ForLoopInfo(progressionInfo, - inductionVariable, - boundValue, - lastValue, - stepValue) - - return IrCompositeImpl(startOffset, endOffset, context.irBuiltIns.unitType, null, statements) - } - } - } - - // Lower getting a next induction variable value. - private fun processNext(variable: IrVariable, initializer: IrCall): IrExpression? { - assert(variable.origin == IrDeclarationOrigin.FOR_LOOP_VARIABLE - || variable.origin == IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE) - val irIteratorAccess = initializer.dispatchReceiver as? IrGetValue ?: throw AssertionError() - val forLoopInfo = iteratorToLoopInfo[irIteratorAccess.symbol] ?: return null // If we didn't lower a corresponding header. - val builder = context.createIrBuilder(scopeOwnerSymbol, initializer.startOffset, initializer.endOffset) - - val plusOperator = symbols.getBinaryOperator( - OperatorNameConventions.PLUS, - forLoopInfo.inductionVariable.type.toKotlinType(), - forLoopInfo.step.type.toKotlinType() - ) - forLoopInfo.loopVariable = variable - - with(builder) { - variable.initializer = irGet(forLoopInfo.inductionVariable) - val increment = irSetVar(forLoopInfo.inductionVariable, - irCallOp(plusOperator.owner, irGet(forLoopInfo.inductionVariable), irGet(forLoopInfo.step))) - return IrCompositeImpl(variable.startOffset, - variable.endOffset, - context.irBuiltIns.unitType, - IrStatementOrigin.FOR_LOOP_NEXT, - listOf(variable, increment)) - } - } - - private fun DeclarationIrBuilder.buildMinValueCondition(forLoopInfo: ForLoopInfo): IrExpression { - // Condition for a corner case: for (i in a until Int.MIN_VALUE) {}. - // Check if forLoopInfo.bound > MIN_VALUE. - val progressionType = forLoopInfo.progressionInfo.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, - forLoopInfo.bound.type.toKotlinType(), - minConst.type.toKotlinType()) - return irCall(irBuiltIns.greaterFunByOperandType[irBuiltIns.int]?.symbol!!).apply { - val compareToCall = irCall(compareTo).apply { - dispatchReceiver = irGet(forLoopInfo.bound) - putValueArgument(0, minConst) - } - putValueArgument(0, compareToCall) - putValueArgument(1, irInt(0)) - } - } - - // TODO: Eliminate the loop if we can prove that it will not be executed. - private fun DeclarationIrBuilder.buildEmptinessCheck(loop: IrLoop, forLoopInfo: ForLoopInfo): IrExpression { - val builtIns = context.irBuiltIns - val increasing = forLoopInfo.progressionInfo.increasing - val comparingBuiltIn = if (increasing) builtIns.lessOrEqualFunByOperandType[builtIns.int]?.symbol - else builtIns.greaterOrEqualFunByOperandType[builtIns.int]?.symbol - - // Check if inductionVariable <= last. - val compareTo = symbols.getBinaryOperator(OperatorNameConventions.COMPARE_TO, - forLoopInfo.inductionVariable.type.toKotlinType(), - forLoopInfo.last.type.toKotlinType()) - - val check: IrExpression = irCall(comparingBuiltIn!!).apply { - putValueArgument(0, irCallOp(compareTo.owner, irGet(forLoopInfo.inductionVariable), irGet(forLoopInfo.last))) - putValueArgument(1, irInt(0)) - } - - // Process closed and open ranges in different manners. - return if (forLoopInfo.progressionInfo.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(forLoopInfo), loop)) - } - } - - private fun DeclarationIrBuilder.buildNewCondition(oldCondition: IrExpression): Pair? { - 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 a corresponding header. - val forLoopInfo = iteratorToLoopInfo[irIteratorAccess.symbol] ?: return null - assert(forLoopInfo.loopVariable != null) - - return irCall(context.irBuiltIns.booleanNotSymbol).apply { - val eqeqCall = irCall(context.irBuiltIns.eqeqSymbol).apply { - putValueArgument(0, irGet(forLoopInfo.loopVariable!!)) - putValueArgument(1, irGet(forLoopInfo.last)) - } - putValueArgument(0, eqeqCall) - } to forLoopInfo + iteratorToLoopHeader[variable.symbol] = forLoopInfo + return IrCompositeImpl( + variable.startOffset, + variable.endOffset, + context.irBuiltIns.unitType, + null, + forLoopInfo.declarations) } /** @@ -349,47 +107,126 @@ private class ForLoopsTransformer(val context: Context, val progressionInfoBuild * ... * } 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) } - - with(context.createIrBuilder(scopeOwnerSymbol, loop.startOffset, loop.endOffset)) { - // Transform accesses to the old iterator (see visitVariable method). Store loopVariable in loopInfo. - // Replace not transparent containers with transparent ones (IrComposite) - val newBody = loop.body?.transform(this@ForLoopsTransformer, null)?.let { + 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 (newCondition, forLoopInfo) = buildNewCondition(loop.condition) ?: return super.visitWhileLoop(loop) - - val newLoop = IrDoWhileLoopImpl(loop.startOffset, loop.endOffset, loop.type, loop.origin).apply { - label = loop.label - condition = newCondition - body = newBody - } + 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. - return buildEmptinessCheck(newLoop, forLoopInfo) + if (loopHeader is ProgressionLoopHeader) { + buildEmptinessCheck(newLoop, loopHeader) + } else { + newLoop + } } } - override fun visitVariable(declaration: IrVariable): IrStatement { - val initializer = declaration.initializer - if (initializer == null || initializer !is IrCall) { - return super.visitVariable(declaration) + 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 result = when (initializer.origin) { - IrStatementOrigin.FOR_LOOP_ITERATOR -> processHeader(declaration, initializer) - IrStatementOrigin.FOR_LOOP_NEXT -> processNext(declaration, initializer) - else -> null + 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)) } - return result ?: super.visitVariable(declaration) } - //endregion -} + + 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). + val compareTo = symbols.getBinaryOperator(OperatorNameConventions.COMPARE_TO, + loopHeader.inductionVariable.type.toKotlinType(), + loopHeader.last.type.toKotlinType()) + + val check = irCall(comparingBuiltIn).apply { + putValueArgument(0, irCallOp(compareTo.owner, 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. + 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, irCallOp(plusOperator.owner, + 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/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/HeaderInfo.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/HeaderInfo.kt new file mode 100644 index 00000000000..3b8524a0ccc --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/HeaderInfo.kt @@ -0,0 +1,110 @@ +package org.jetbrains.kotlin.backend.konan.lower.loops + +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.irasdescriptors.isSubtypeOf +import org.jetbrains.kotlin.backend.konan.lower.matchers.IrCallMatcher +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 + +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 it's subtree +// to get information about underlying progression. +private class ProgressionHeaderInfoBuilder(val context: Context) : IrElementVisitor { + + private val symbols = context.ir.symbols + + private val progressionElementClasses = symbols.integerClasses + symbols.char + + private val progressionHandlers = listOf( + IndicesHandler(context), + UntilHandler(progressionElementClasses), + DownToHandler(progressionElementClasses), + StepHandler(context, this), + RangeToHandler(progressionElementClasses) + ) + + 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: Context) { + 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/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/HeaderProcessor.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/HeaderProcessor.kt new file mode 100644 index 00000000000..b3d4fc371db --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/HeaderProcessor.kt @@ -0,0 +1,270 @@ +package org.jetbrains.kotlin.backend.konan.lower.loops + +import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols +import org.jetbrains.kotlin.backend.konan.irasdescriptors.isSubtypeOf +import org.jetbrains.kotlin.backend.konan.irasdescriptors.typeWithStarProjections +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.* +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.isSimpleTypeWithQuestionMark +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: KonanSymbols, 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: KonanSymbols, 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: KonanSymbols, builder: DeclarationIrBuilder) = with(builder) { + val callee = symbols.arrayGet[arrayDeclaration.type.classifierOrFail]!! + irCall(callee).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: Context): 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: Context, + private val headerInfoBuilder: HeaderInfoBuilder, + private val scopeOwnerSymbol: () -> IrSymbol) { + + private val symbols = context.ir.symbols + + private val progressionElementClasses = (symbols.integerClasses + symbols.char).toSet() + + fun processHeader(variable: IrVariable): ForLoopHeader? { + + assert(variable.origin == IrDeclarationOrigin.FOR_LOOP_ITERATOR) + val iteratorType = symbols.iterator.typeWithStarProjections + + 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) { + 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 { + assert(type.classifierOrNull in progressionElementClasses) + return if (type.classifierOrNull == progressionType.elementType(context).classifierOrNull) { + 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.isSimpleTypeWithQuestionMark) { + 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/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionHandlers.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionHandlers.kt index c348dbeba5b..9c9cc84eaa1 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionHandlers.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionHandlers.kt @@ -8,15 +8,17 @@ import org.jetbrains.kotlin.backend.konan.lower.matchers.SimpleCalleeMatcher import org.jetbrains.kotlin.backend.konan.lower.matchers.createIrCallMatcher import org.jetbrains.kotlin.backend.konan.lower.matchers.singleArgumentExtension 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.expressions.impl.IrCallImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.visitors.IrElementVisitor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +// TODO: What if functions like Int.rangeTo(Char) will be added to stdlib later? internal class RangeToHandler(val progressionElementClasses: Collection) : ProgressionHandler { override val matcher = SimpleCalleeMatcher { dispatchReceiver { it != null && it.type.classifierOrNull in progressionElementClasses } @@ -25,8 +27,8 @@ internal class RangeToHandler(val progressionElementClasses: Collection) : ProgressionHandler { @@ -35,8 +37,8 @@ internal class DownToHandler(val progressionElementClasses: Collection) : ProgressionHandler { +internal class StepHandler(context: Context, val visitor: IrElementVisitor) : ProgressionHandler { private val symbols = context.ir.symbols override val matcher: IrCallMatcher = SimpleCalleeMatcher { @@ -91,28 +92,32 @@ internal class StepHandler(context: Context, val visitor: IrElementVisitor && 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 (nestedInfo.step != null) { + if (!isDefaultStep(nestedInfo.step)) { return null } val newStep = call.getValueArgument(0)!! - val (newStepCheck, needBoundCalculation) = irCheckProgressionStep(symbols, progressionType, newStep) - return ProgressionInfo( - progressionType, - nestedInfo.first, - nestedInfo.bound, - newStepCheck, nestedInfo.increasing, + val (newStepCheck, needBoundCalculation) = irCheckProgressionStep(symbols, data, newStep) + return ProgressionHeaderInfo( + data, + nestedInfo.lowerBound, + nestedInfo.upperBound, + newStepCheck, + nestedInfo.increasing, needBoundCalculation, nestedInfo.closed) } - private fun IrConst<*>.stepIsOne() = when (kind) { + private fun IrConst<*>.isOne() = when (kind) { IrConstKind.Long -> value as Long == 1L IrConstKind.Int -> value as Int == 1 else -> false @@ -132,7 +137,7 @@ internal class StepHandler(context: Context, val visitor: IrElementVisitor).stepIsOne() + step to !(step as IrConst<*>).isOne() } else { // 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. @@ -145,3 +150,36 @@ internal class StepHandler(context: Context, val visitor: IrElementVisitor { + + private val symbols = context.ir.symbols + private val supportedArrays = symbols.arrays + + // No support for rare cases like `T : IntArray` for now. + override val matcher = createIrCallMatcher { + origin { it == IrStatementOrigin.FOR_LOOP_ITERATOR } + dispatchReceiver { it != null && (it.type.classifierOrNull in supportedArrays) } + } + + // 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 clazz = arrayReference.type.classifierOrFail + val arraySizeSymbol = symbols.arraySize[clazz]!! + val upperBound = irCall(arraySizeSymbol).apply { + dispatchReceiver = irGet(arrayReference) + } + ArrayHeaderInfo(irInt(0), upperBound, arrayReference) + } +} + + diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionInfo.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionInfo.kt deleted file mode 100644 index e467152bbf6..00000000000 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionInfo.kt +++ /dev/null @@ -1,70 +0,0 @@ -package org.jetbrains.kotlin.backend.konan.lower.loops - -import org.jetbrains.kotlin.backend.konan.Context -import org.jetbrains.kotlin.backend.konan.irasdescriptors.isSubtypeOf -import org.jetbrains.kotlin.backend.konan.lower.matchers.* -import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.expressions.* -import org.jetbrains.kotlin.ir.types.* -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 - -enum class ProgressionType(val numberCastFunctionName: Name) { - INT_PROGRESSION(Name.identifier("toInt")), - LONG_PROGRESSION(Name.identifier("toLong")), - CHAR_PROGRESSION(Name.identifier("toChar")); -} - -internal data class ProgressionInfo( - val progressionType: ProgressionType, - val first: IrExpression, - val bound: IrExpression, - val step: IrExpression? = null, - val increasing: Boolean = true, - var needLastCalculation: Boolean = false, - val closed: Boolean = true) - -internal interface ProgressionHandler { - val matcher: IrCallMatcher - - fun build(call: IrCall, progressionType: ProgressionType): ProgressionInfo? - - fun handle(irCall: IrCall, progressionType: ProgressionType) = if (matcher(irCall)) { - build(irCall, progressionType) - } else { - null - } -} - -internal class ProgressionInfoBuilder(val context: Context) : IrElementVisitor { - - private val symbols = context.ir.symbols - - private val progressionElementClasses = symbols.integerClasses + symbols.char - - private val handlers = listOf( - IndicesHandler(context), - UntilHandler(progressionElementClasses), - DownToHandler(progressionElementClasses), - StepHandler(context, this), - RangeToHandler(progressionElementClasses) - ) - - 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?): ProgressionInfo? = null - - override fun visitCall(expression: IrCall, data: Nothing?): ProgressionInfo? { - val progressionType = expression.type.getProgressionType() - ?: return null - - return handlers.firstNotNullResult { it.handle(expression, progressionType) } - } -} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrCallMatcher.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrCallMatcher.kt index 3c55835eef5..fd72223ee9c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrCallMatcher.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrCallMatcher.kt @@ -2,6 +2,7 @@ package org.jetbrains.kotlin.backend.konan.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 @@ -25,6 +26,18 @@ internal class IrCallExtensionReceiverMatcher( 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() @@ -36,10 +49,16 @@ internal open class IrCallMatcherContainer : IrCallMatcher { 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) } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt index 7c1ba9dffbf..55f9f67ee94 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt @@ -391,19 +391,21 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag } } + private val symbols = context.ir.symbols + private val invokeSuspendFunctionSymbol = - context.ir.symbols.baseContinuationImpl.owner.declarations + symbols.baseContinuationImpl.owner.declarations .filterIsInstance().single { it.name.asString() == "invokeSuspend" }.symbol - private val getContinuationSymbol = context.ir.symbols.getContinuation + private val getContinuationSymbol = symbols.getContinuation private val continuationType = getContinuationSymbol.owner.returnType - private val arrayGetSymbol = context.ir.symbols.arrayGet - private val arraySetSymbol = context.ir.symbols.arraySet - private val createUninitializedInstanceSymbol = context.ir.symbols.createUninitializedInstance - private val initInstanceSymbol = context.ir.symbols.initInstance - private val executeImplSymbol = context.ir.symbols.executeImpl - private val executeImplProducerClassSymbol = context.ir.symbols.functions[0] + private val arrayGetSymbol = symbols.arrayGet[symbols.array] + private val arraySetSymbol = symbols.arraySet[symbols.array] + private val createUninitializedInstanceSymbol = symbols.createUninitializedInstance + private val initInstanceSymbol = symbols.initInstance + private val executeImplSymbol = symbols.executeImpl + private val executeImplProducerClassSymbol = symbols.functions[0] private val executeImplProducerInvoke = executeImplProducerClassSymbol.owner.simpleFunctions() .single { it.name == OperatorNameConventions.INVOKE } @@ -460,7 +462,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag ) val throwsNode = DataFlowIR.Node.Variable( values = thrownValues.map { expressionToEdge(it) }, - type = symbolTable.mapClassReferenceType(context.ir.symbols.throwable.owner), + type = symbolTable.mapClassReferenceType(symbols.throwable.owner), kind = DataFlowIR.VariableKind.Temporary ) variables.forEach { descriptor, node -> diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 8bf14f00800..fb403a3e9d5 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -455,6 +455,41 @@ task codegen_controlflow_for_loops_call_order(type: RunKonanTest) { goldValue = "1234\n1234\n2134\n" } +task codegen_controlflow_for_loops_array_indices(type: RunKonanTest) { + source = "codegen/controlflow/for_loops_array_indices.kt" + goldValue = "0123\n\n" +} + +task codegen_controlflow_for_loops_array(type: RunKonanTest) { + source = "codegen/controlflow/for_loops_array.kt" + goldValue = "4035\n\n0\n0\n" +} + +task codegen_controlflow_for_loops_array_nested(type: RunKonanTest) { + source = "codegen/controlflow/for_loops_array_nested.kt" + goldValue = "123\nHello\n\n12345678910\n" +} + +task codegen_controlflow_for_loops_array_side_effects(type: RunKonanTest) { + source = "codegen/controlflow/for_loops_array_side_effects.kt" + goldValue = "side-effect\n4035\nside-effect\n\n" +} + +task codegen_controlflow_for_loops_array_break_continue(type: RunKonanTest) { + source = "codegen/controlflow/for_loops_array_break_continue.kt" + goldValue = "403\n\n" +} + +task codegen_controlflow_for_loops_array_mutation(type: RunKonanTest) { + source = "codegen/controlflow/for_loops_array_mutation.kt" + goldValue = "4000" +} + +task codegen_controlflow_for_loops_array_nullable(type: RunKonanTest) { + source = "codegen/controlflow/for_loops_array_nullable.kt" + goldValue = "123" +} + task local_variable(type: RunKonanTest) { source = "codegen/basics/local_variable.kt" } diff --git a/backend.native/tests/codegen/controlflow/for_loops_array.kt b/backend.native/tests/codegen/controlflow/for_loops_array.kt new file mode 100644 index 00000000000..68cec2fed38 --- /dev/null +++ b/backend.native/tests/codegen/controlflow/for_loops_array.kt @@ -0,0 +1,40 @@ +package codegen.controlflow.for_loops_array + +import kotlin.test.* + +fun genericArray(data : T): Int { + var sum = 0 + for (element in data) { + sum += element + } + return sum +} + +fun IntArray.sum(): Int { + var sum = 0 + for (element in this) { + sum += element + } + return sum +} + +@Test fun runTest() { + val intArray = intArrayOf(4, 0, 3, 5) + + val emptyArray = arrayOf() + + for (element in intArray) { + print(element) + } + println() + for (element in emptyArray) { + print(element) + } + println() + + val byteArray = byteArrayOf(1, -1) + println(genericArray(byteArray)) + + val fives = intArrayOf(5, 5, 5, -5, -5, -5) + println(fives.sum()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/controlflow/for_loops_array_break_continue.kt b/backend.native/tests/codegen/controlflow/for_loops_array_break_continue.kt new file mode 100644 index 00000000000..d107a550726 --- /dev/null +++ b/backend.native/tests/codegen/controlflow/for_loops_array_break_continue.kt @@ -0,0 +1,21 @@ +package codegen.controlflow.for_loops_array_break_continue + +import kotlin.test.* + +@Test fun runTest() { + val intArray = intArrayOf(4, 0, 3, 5) + + val emptyArray = arrayOf() + + for (element in intArray) { + print(element) + if (element == 3) { + break + } + } + println() + for (element in emptyArray) { + print(element) + } + println() +} \ No newline at end of file diff --git a/backend.native/tests/codegen/controlflow/for_loops_array_indices.kt b/backend.native/tests/codegen/controlflow/for_loops_array_indices.kt new file mode 100644 index 00000000000..89d21c2892e --- /dev/null +++ b/backend.native/tests/codegen/controlflow/for_loops_array_indices.kt @@ -0,0 +1,18 @@ +package codegen.controlflow.for_loops_array_indices + +import kotlin.test.* + +@Test fun runTest() { + val intArray = intArrayOf(4, 0, 3, 5) + + val emptyArray = arrayOf() + + for (index in intArray.indices) { + print(index) + } + println() + for (index in emptyArray.indices) { + print(index) + } + println() +} \ No newline at end of file diff --git a/backend.native/tests/codegen/controlflow/for_loops_array_mutation.kt b/backend.native/tests/codegen/controlflow/for_loops_array_mutation.kt new file mode 100644 index 00000000000..0d1c5b707e8 --- /dev/null +++ b/backend.native/tests/codegen/controlflow/for_loops_array_mutation.kt @@ -0,0 +1,13 @@ +package codegen.controlflow.for_loops_array_mutation + +import kotlin.test.* + +@Test fun runTest() { + val intArray = arrayOf(4, 0, 3, 5) + + for (element in intArray) { + intArray[2] = 0 + intArray[3] = 0 + print(element) + } +} \ No newline at end of file diff --git a/backend.native/tests/codegen/controlflow/for_loops_array_nested.kt b/backend.native/tests/codegen/controlflow/for_loops_array_nested.kt new file mode 100644 index 00000000000..9ecf83bca48 --- /dev/null +++ b/backend.native/tests/codegen/controlflow/for_loops_array_nested.kt @@ -0,0 +1,25 @@ +package codegen.controlflow.for_loops_array_nested + +import kotlin.test.* + +@Test fun arrayOfArrays() { + val metaArray = arrayOf( + arrayOf(1, 2, 3), + arrayOf("Hello"), + arrayOf(), + arrayOf(1..10) + ) + for (array in metaArray) { + inner@for (elem in array) { + if (elem is IntProgression) { + for (i in elem) { + print(i) + } + continue@inner + } else { + print(elem) + } + } + println() + } +} \ No newline at end of file diff --git a/backend.native/tests/codegen/controlflow/for_loops_array_nullable.kt b/backend.native/tests/codegen/controlflow/for_loops_array_nullable.kt new file mode 100644 index 00000000000..032f54bf312 --- /dev/null +++ b/backend.native/tests/codegen/controlflow/for_loops_array_nullable.kt @@ -0,0 +1,16 @@ +package codegen.controlflow.for_loops_array_nullable + +import kotlin.test.* + +private fun nullableArray(a: Array): Array? { + return a +} + +@Test fun nullable() { + val array = arrayOf(1, 2, 3) + nullableArray(array)?.let { + for (elem in it) { + print(elem) + } + } +} \ No newline at end of file diff --git a/backend.native/tests/codegen/controlflow/for_loops_array_side_effects.kt b/backend.native/tests/codegen/controlflow/for_loops_array_side_effects.kt new file mode 100644 index 00000000000..28222f046e5 --- /dev/null +++ b/backend.native/tests/codegen/controlflow/for_loops_array_side_effects.kt @@ -0,0 +1,23 @@ +package codegen.controlflow.for_loops_array_side_effects + +import kotlin.test.* + +private fun sideEffect(array: T): T { + println("side-effect") + return array +} + +@Test fun runTest() { + val intArray = intArrayOf(4, 0, 3, 5) + + val emptyArray = arrayOf() + + for (element in sideEffect(intArray)) { + print(element) + } + println() + for (element in sideEffect(emptyArray)) { + print(element) + } + println() +} \ No newline at end of file diff --git a/backend.native/tests/runtime/basic/hello1.kt b/backend.native/tests/runtime/basic/hello1.kt index 9553b10ed2d..b08c1f3e65e 100644 --- a/backend.native/tests/runtime/basic/hello1.kt +++ b/backend.native/tests/runtime/basic/hello1.kt @@ -5,6 +5,6 @@ // TODO: TestRuner should be able to pass input to stdin // TODO: remove kotlin_native.io once overrides are in place. -fun main(args : Array) { - print(readLine().toString()) +fun main(args: Array) { + print(readLine().toString()) } diff --git a/performance/src/main/kotlin/org/jetbrains/ring/ForLoopsBenchmark.kt b/performance/src/main/kotlin/org/jetbrains/ring/ForLoopsBenchmark.kt index 5fa73879f33..67dc5dc6533 100644 --- a/performance/src/main/kotlin/org/jetbrains/ring/ForLoopsBenchmark.kt +++ b/performance/src/main/kotlin/org/jetbrains/ring/ForLoopsBenchmark.kt @@ -54,7 +54,7 @@ class ForLoopsBenchmark { fun floatArrayLoop(): Double { var sum = 0.0 - for (e in array) { + for (e in floatArray) { sum += e } return sum