diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt index 7015bdd194b..03d0d011827 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/ir/Ir.kt @@ -1,7 +1,6 @@ package org.jetbrains.kotlin.backend.common.ir import org.jetbrains.kotlin.backend.common.CommonBackendContext -import org.jetbrains.kotlin.backend.common.descriptors.replace import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -9,11 +8,13 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.SimpleType import org.jetbrains.kotlin.util.OperatorNameConventions // This is what Context collects about IR. @@ -61,17 +62,42 @@ open class Symbols(val context: T, private val symb ) as ClassDescriptor ) + val iterator = symbolTable.referenceClass( + builtInsPackage("kotlin", "collections").getContributedClassifier( + Name.identifier("Iterator"), NoLookupLocation.FROM_BACKEND + ) as ClassDescriptor) + + private fun progression(name: String) = symbolTable.referenceClass( + builtInsPackage("kotlin", "ranges").getContributedClassifier( + Name.identifier(name), NoLookupLocation.FROM_BACKEND + ) as ClassDescriptor + ) + + val charProgression = progression("CharProgression") + val intProgression = progression("IntProgression") + val longProgression = progression("LongProgression") + val progressionClasses = listOf(charProgression, intProgression, longProgression) + val progressionClassesTypes = progressionClasses.map { it.descriptor.defaultType }.toSet() + + val checkProgressionStep = context.getInternalFunctions("checkProgressionStep") + .map { Pair(it.returnType, symbolTable.referenceSimpleFunction(it)) }.toMap() + val getProgressionBound = context.getInternalFunctions("getProgressionBound") + .map { Pair(it.returnType, symbolTable.referenceSimpleFunction(it)) }.toMap() + val defaultConstructorMarker = symbolTable.referenceClass(context.getInternalClass("DefaultConstructorMarker")) val any = symbolTable.referenceClass(builtIns.any) val unit = symbolTable.referenceClass(builtIns.unit) + val char = symbolTable.referenceClass(builtIns.char) + val byte = symbolTable.referenceClass(builtIns.byte) val short = symbolTable.referenceClass(builtIns.short) val int = symbolTable.referenceClass(builtIns.int) val long = symbolTable.referenceClass(builtIns.long) val integerClasses = listOf(byte, short, int, long) + val integerClassesTypes = integerClasses.map { it.descriptor.defaultType } val arrayOf = symbolTable.referenceSimpleFunction( builtInsPackage("kotlin").getContributedFunctions( @@ -143,6 +169,8 @@ open class Symbols(val context: T, private val symb } ) + + val valuesForEnum = symbolTable.referenceSimpleFunction( context.getInternalFunctions("valuesForEnum").single()) @@ -171,4 +199,21 @@ open class Symbols(val context: T, private val symb val kLocalDelegatedPropertyImpl = symbolTable.referenceClass(context.reflectionTypes.kLocalDelegatedPropertyImpl) val kLocalDelegatedMutablePropertyImpl = symbolTable.referenceClass(context.reflectionTypes.kLocalDelegatedMutablePropertyImpl) + fun getFunction(name: Name, receiverType: KotlinType, vararg argTypes: KotlinType) = + symbolTable.referenceFunction(receiverType.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND) + .single { + var i = 0 + it.valueParameters.size == argTypes.size && it.valueParameters.all { type -> type == argTypes[i++] } + } + ) + + fun getBinaryOperator(name: Name, lhsType: KotlinType, rhsType: KotlinType) = + symbolTable.referenceFunction(lhsType.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND) + .single { it.valueParameters.size == 1 && it.valueParameters[0].type == rhsType } + ) + + fun getUnaryOperator(name: Name, receiverType: KotlinType) = + symbolTable.referenceFunction(receiverType.memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND) + .single { it.valueParameters.isEmpty() } + ) } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 4255b6daa90..38263f69d48 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -66,6 +66,9 @@ internal class KonanLower(val context: Context) { phaser.phase(KonanPhase.LOWER_DATA_CLASSES) { DataClassOperatorsLowering(context).runOnFilePostfix(irFile) } + phaser.phase(KonanPhase.LOWER_FOR_LOOPS) { + ForLoopsLowering(context).lower(irFile) + } phaser.phase(KonanPhase.LOWER_ENUMS) { EnumClassLowering(context).run(irFile) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index 324f62dec20..04b3a539c17 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -32,6 +32,7 @@ enum class KonanPhase(val description: String, /* ... ... */ LOWER_INLINE("Functions inlining", LOWER_INLINE_CONSTRUCTORS), /* ... ... ... */ DESERIALIZER("Deserialize inline bodies"), /* ... ... */ LOWER_INTEROP_PART1("Interop lowering, part 1", LOWER_INLINE), + /* ... ... */ LOWER_FOR_LOOPS("For loops lowering"), /* ... ... */ LOWER_ENUMS("Enum classes lowering"), /* ... ... */ LOWER_DELEGATION("Delegation lowering"), /* ... ... */ LOWER_INITIALIZERS("Initializers lowering", LOWER_ENUMS), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt new file mode 100644 index 00000000000..fce992f380f --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt @@ -0,0 +1,414 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.backend.konan.lower + +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.konan.Context +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.ir.IrElement +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.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol +import org.jetbrains.kotlin.ir.visitors.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.SimpleType +import org.jetbrains.kotlin.types.typeUtil.isBoolean +import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf +import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections +import org.jetbrains.kotlin.util.OperatorNameConventions + +/** This lowering pass optimizes range-based for loops. */ +internal class ForLoopsLowering(val context: Context) : FileLoweringPass { + override fun lower(irFile: IrFile) { + val transformer = ForLoopsTransformer(context) + // Lower loops + irFile.transformChildrenVoid(transformer) + + // Update references in break/continue. + irFile.transformChildrenVoid(object: IrElementTransformerVoid() { + override fun visitBreakContinue(jump: IrBreakContinue): IrExpression { + transformer.oldLoopToNewLoop[jump.loop]?.let { jump.loop = it } + return jump + } + }) + } +} + +private class ForLoopsTransformer(val context: Context) : IrElementTransformerVoidWithContext() { + + private val symbols = context.ir.symbols + private val iteratorToLoopInfo = mutableMapOf() + internal val oldLoopToNewLoop = mutableMapOf() + + private val iteratorType = symbols.iterator.descriptor.defaultType.replaceArgumentsWithStarProjections() + + private val scopeOwnerSymbol + get() = currentScope!!.scope.scopeOwnerSymbol + + private fun irConstOne(startOffset: Int, endOffset: Int) = + IrConstImpl.int(startOffset, endOffset, context.builtIns.intType, 1) + + private fun irConstMinusOne(startOffset: Int, endOffset: Int) = + IrConstImpl.int(startOffset, endOffset, context.builtIns.intType, -1) + + private val progressionElementClasses: Set = mutableSetOf(symbols.char).apply { + addAll(symbols.integerClasses) + } + + private val progressionElementClassesTypes: Set = mutableSetOf().apply { + progressionElementClasses.mapTo(this) { it.descriptor.defaultType } + } + + // Symbols for progression building functions ====================================================================== + private fun getProgressionBuildingMethods(name: String): Set = + getMethodsForProgressionElements(name) { + it.valueParameters.size == 1 && + it.valueParameters[0].type in progressionElementClassesTypes + } + + private fun getProgressionBuildingExtensions(name: String, pkg: FqName): Set = + getExtensionsForProgressionElements(name, pkg) { + it.extensionReceiverParameter?.type in progressionElementClassesTypes && + it.valueParameters.size == 1 && + it.valueParameters[0].type in progressionElementClassesTypes + } + + private fun getMethodsForProgressionElements(name: String, + filter: (SimpleFunctionDescriptor) -> Boolean): Set = + mutableSetOf().apply { + progressionElementClasses.flatMapTo(this) { receiver -> + receiver.descriptor.unsubstitutedMemberScope + .getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND) + .filter(filter).map { symbols.symbolTable.referenceFunction(it) } + } + } + + private fun getExtensionsForProgressionElements(name: String, + pkg: FqName, + filter: (SimpleFunctionDescriptor) -> Boolean): Set = + mutableSetOf().apply { + progressionElementClasses.flatMapTo(this) { receiver -> + context.builtIns.builtInsModule.getPackage(pkg).memberScope + .getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND) + .filter(filter).map { symbols.symbolTable.referenceFunction(it) } + } + } + + private val rangeToSymbols by lazy { getProgressionBuildingMethods("rangeTo") } + private val untilSymbols by lazy { getProgressionBuildingExtensions("until", FqName("kotlin.ranges")) } + private val downToSymbols by lazy { getProgressionBuildingExtensions("downTo", FqName("kotlin.ranges")) } + private val stepSymbols by lazy { + getExtensionsForProgressionElements("step", FqName("kotlin.ranges")) { + it.extensionReceiverParameter?.type in symbols.progressionClassesTypes && + it.valueParameters.size == 1 && + (KotlinBuiltIns.isLong(it.valueParameters[0].type) || KotlinBuiltIns.isInt(it.valueParameters[0].type)) + } + } + + //region Util methods ============================================================================================== + private fun IrExpression.castIfNecessary(progressionType: ProgressionType, castToChar: Boolean = true): IrExpression { + assert(type in progressionElementClassesTypes) + if (type == progressionType.elementType || (!castToChar && KotlinBuiltIns.isChar(progressionType.elementType))) { + return this + } + return IrCallImpl(startOffset, endOffset, symbols.getFunction(progressionType.numberCastFunctionName, type)) + .apply { dispatchReceiver = this@castIfNecessary } + } + + private fun IrExpression.unaryMinus(): IrExpression = + IrCallImpl(startOffset, endOffset, symbols.getUnaryOperator(OperatorNameConventions.UNARY_MINUS, type)).apply { + dispatchReceiver = this@unaryMinus + } + + private fun irCheckProgressionStep(progressionType: ProgressionType, + step: IrExpression): IrExpression { + if (step is IrConst<*> && + ((step.kind == IrConstKind.Long && step.value as Long > 0) || + (step.kind == IrConstKind.Int && step.value as Int > 0))) { + return step + } + val castedStep = step.castIfNecessary(progressionType, false) + val symbol = symbols.checkProgressionStep[castedStep.type] + ?: throw IllegalArgumentException("Unknown progression element type: ${step.type}") + return IrCallImpl(step.startOffset, step.endOffset, symbol).apply { + putValueArgument(0, castedStep) + } + } + + private fun irGetProgressionBound(progressionType: ProgressionType, + first: IrVariableSymbol, + lastExpression: IrExpression, + step: IrVariableSymbol): IrExpression { + val symbol = symbols.getProgressionBound[progressionType.elementType] + ?: throw IllegalArgumentException("Unknown progression element type: ${lastExpression.type}") + val startOffset = lastExpression.startOffset + val endOffset = lastExpression.endOffset + return IrCallImpl(startOffset, lastExpression.endOffset, symbol).apply { + putValueArgument(0, IrGetValueImpl(startOffset, endOffset, first)) + putValueArgument(1, lastExpression.castIfNecessary(progressionType)) + putValueArgument(2, IrGetValueImpl(startOffset, endOffset, step)) + } + } + //endregion + + //region Util classes ==================================================================================================== + // TODO: Replace with a cast when such support is added in the boxing lowering. + private data class ProgressionType(val elementType: KotlinType, + val numberCastFunctionName: Name) + + private data class ProgressionInfo( + val progressionType: ProgressionType, + val first: IrExpression, + val last: IrExpression, + val step: IrExpression? = null, + val increasing: Boolean = true) + + /** Contains information about variables used in the loop. */ + private data class ForLoopInfo( + val progressionInfo: ProgressionInfo, + val inductionVariable: IrVariableSymbol, + val bound: IrVariableSymbol, + val step: IrVariableSymbol, + var loopVariable: IrVariableSymbol? = null) + + private inner class ProgressionInfoBuilder : IrElementVisitor { + + val INT_PROGRESSION = ProgressionType(context.builtIns.intType, Name.identifier("toInt")) + val LONG_PROGRESSION = ProgressionType(context.builtIns.longType, Name.identifier("toLong")) + val CHAR_PROGRESSION = ProgressionType(context.builtIns.charType, Name.identifier("toChar")) + + private fun buildRangeTo(expression: IrCall, progressionType: ProgressionType) = + ProgressionInfo(progressionType, expression.dispatchReceiver!!, expression.getValueArgument(0)!!) + + private fun buildUntil(expression: IrCall, progressionType: ProgressionType): ProgressionInfo { + val firstExpression = expression.extensionReceiver!! + val lastExpression = expression.getValueArgument(0)!! + val decrementSymbol = symbols.getUnaryOperator(OperatorNameConventions.DEC, lastExpression.type) + val decrementCall = IrCallImpl(expression.startOffset, expression.endOffset, decrementSymbol).apply { + dispatchReceiver = lastExpression + } + return ProgressionInfo(progressionType, firstExpression, decrementCall) + } + + private fun buildDownTo(expression: IrCall, progressionType: ProgressionType) = + ProgressionInfo(progressionType, expression.extensionReceiver!!, expression.getValueArgument(0)!!, increasing = false) + + private fun buildStep(expression: IrCall, progressionType: ProgressionType) = + expression.extensionReceiver!!.accept(this, null)?.let { + val newStep = expression.getValueArgument(0)!! + val step = when { + it.step == null -> irCheckProgressionStep(progressionType, newStep) + // There were step calls before. Just add our check in the container or create a new one. + it.step is IrStatementContainer -> { + it.step.statements.add(irCheckProgressionStep(progressionType, newStep)); it.step + } + else -> IrCompositeImpl(expression.startOffset, expression.endOffset, newStep.type).apply { + statements.add(it.step) + statements.add(irCheckProgressionStep(progressionType, newStep)) + } + } + ProgressionInfo(progressionType, it.first, it.last, step, it.increasing) + } + + override fun visitElement(element: IrElement, data: Nothing?): ProgressionInfo? = null + + override fun visitCall(expression: IrCall, data: Nothing?): ProgressionInfo? { + val type = expression.type + val progressionType = when { + type.isSubtypeOf(symbols.charProgression.descriptor.defaultType) -> CHAR_PROGRESSION + type.isSubtypeOf(symbols.intProgression.descriptor.defaultType) -> INT_PROGRESSION + type.isSubtypeOf(symbols.longProgression.descriptor.defaultType) -> LONG_PROGRESSION + else -> return null + } + + // TODO: Process constructors and other factory functions. + return when (expression.symbol) { + in rangeToSymbols -> buildRangeTo(expression, progressionType) + in untilSymbols -> buildUntil(expression, progressionType) + in downToSymbols -> buildDownTo(expression, progressionType) + in stepSymbols -> buildStep(expression, progressionType) + else -> null + } + } + } + //endregion + + // Lowering ======================================================================================================== + // Lower a loop header. + private fun processHeader(variable: IrVariable, initializer: IrCall): IrStatement? { + val symbol = variable.symbol + if (!variable.descriptor.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 block. + val progressionInfo = initializer.dispatchReceiver?.accept(ProgressionInfoBuilder(), null) ?: return null + return builder.irBlock { + with(progressionInfo) { + val inductionVariable = irTemporaryVar(first.castIfNecessary(progressionType), "inductionVariable") + val stepExpression = if (increasing) { // TODO: Remove the casts. + step ?: irConstOne(startOffset, endOffset).castIfNecessary(progressionType, false) + } else { + step?.unaryMinus() ?: irConstMinusOne(startOffset, endOffset).castIfNecessary(progressionType, false) + } + val stepValue = irTemporary(stepExpression, "step") + // TODO: Don't call the check it step is 1 or -1 + val boundExpression = irGetProgressionBound(progressionType, inductionVariable.symbol, last, stepValue.symbol) + val boundValue = irTemporary(boundExpression, "bound") + iteratorToLoopInfo[symbol] = ForLoopInfo(progressionInfo, + inductionVariable.symbol, + boundValue.symbol, + stepValue.symbol) + } + } + } + + + // Lower getting a next induction variable value. + private fun processNext(variable: IrVariable, initializer: IrCall): IrExpression? { + 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) + + // TODO: Cache it. + val plusOperator = symbols.getBinaryOperator( + OperatorNameConventions.PLUS, + forLoopInfo.inductionVariable.descriptor.type, + forLoopInfo.step.descriptor.type + ) + forLoopInfo.loopVariable = variable.symbol + + with(builder) { + variable.initializer = irGet(forLoopInfo.inductionVariable) + val increment = irSetVar(forLoopInfo.inductionVariable, + irCallOp(plusOperator, irGet(forLoopInfo.inductionVariable), irGet(forLoopInfo.step))) + return IrCompositeImpl(variable.startOffset, + variable.endOffset, + context.irBuiltIns.unit, + IrStatementOrigin.FOR_LOOP_NEXT, + listOf(variable, increment)) + } + } + + private fun DeclarationIrBuilder.buildEmptyCheck(loop: IrLoop, forLoopInfo: ForLoopInfo): IrExpression { + val increasing = forLoopInfo.progressionInfo.increasing + val comparingBuiltIn = if (increasing) context.irBuiltIns.lteq0Symbol else context.irBuiltIns.gteq0Symbol + + val compareTo = symbols.getBinaryOperator(OperatorNameConventions.COMPARE_TO, + forLoopInfo.inductionVariable.descriptor.type, + forLoopInfo.bound.descriptor.type) + + val check = irCall(comparingBuiltIn).apply { + putValueArgument(0, irCallOp(compareTo, irGet(forLoopInfo.inductionVariable), irGet(forLoopInfo.bound))) + } + return irIfThen(check, 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 + val loopVariable = forLoopInfo.loopVariable!! // TODO: Check! + + return irCall(context.irBuiltIns.booleanNotSymbol).apply { + val eqeqCall = irCall(context.irBuiltIns.eqeqSymbol).apply { + putValueArgument(0, irGet(loopVariable)) + putValueArgument(1, irGet(forLoopInfo.bound)) + } + putValueArgument(0, eqeqCall) + } to forLoopInfo + } + + /** + * 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) + * } + */ + override fun visitWhileLoop(loop: IrWhileLoop): IrExpression { + if (loop.origin != IrStatementOrigin.FOR_LOOP_INNER_WHILE) { + return super.visitWhileLoop(loop) + } + + with(context.createIrBuilder(scopeOwnerSymbol)) { + // Transform accesses to the old iterator (see visitVariable method). Store loopVariable in loopInfo. + val newBody = loop.body?.transform(this@ForLoopsTransformer, null) + val (newCondition, forLoopInfo) = buildNewCondition(loop.condition) ?: return super.visitWhileLoop(loop) + assert(forLoopInfo.loopVariable != null) + + val newLoop = IrDoWhileLoopImpl(loop.startOffset, loop.endOffset, loop.type, loop.origin).apply { + label = loop.label + condition = newCondition + body = newBody + } + oldLoopToNewLoop[loop] = newLoop + // Build a check for an empty progression before the loop. + return buildEmptyCheck(newLoop, forLoopInfo) + } + } + + override fun visitVariable(declaration: IrVariable): IrStatement { + val initializer = declaration.initializer + if (initializer == null || initializer !is IrCall) { + return super.visitVariable(declaration) + } + val result = when (initializer.origin) { + IrStatementOrigin.FOR_LOOP_ITERATOR -> processHeader(declaration, initializer) + IrStatementOrigin.FOR_LOOP_NEXT -> processNext(declaration, initializer) + else -> null + } + return result ?: super.visitVariable(declaration) + } +} + diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 2aa86c2a8a1..fd1e1c8c22d 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -307,6 +307,29 @@ task sum_3const(type: RunKonanTest) { source = "codegen/function/sum_3const.kt" } +task codegen_basics_for_loops(type: RunKonanTest) { + source = "codegen/basics/for_loops.kt" + goldValue = "01234\n0123\n43210\n\n024\n02\n420\n\n036\n03\n630\n\n" +} + +task codegen_basics_for_loops_types(type: RunKonanTest) { + source = "codegen/basics/for_loops_types.kt" + goldValue = "01234\n01234\n01234\n01234\n01234\n01234\n01234\n01234\n01234\n01234\n01234\n01234\n01234\n01234\n" + + "01234\n01234\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n0123\n" + + "0123\n43210\n43210\n43210\n43210\n43210\n43210\n43210\n43210\n43210\n43210\n43210\n43210\n43210\n43210\n" + + "43210\n43210\nabcd\nabc\ndcba\n" +} + +task codegen_basics_for_loops_overflow(type: RunKonanTest) { + source = "codegen/basics/for_loops_overflow.kt" + goldValue = "2147483646 2147483647 \n2147483646 \n-2147483647 -2147483648 \n1073741827 \n" +} + +task codegen_basics_for_loops_errors(type: RunKonanTest) { + source = "codegen/basics/for_loops_errors.kt" + goldValue = "OK\n" +} + task local_variable(type: RunKonanTest) { source = "codegen/basics/local_variable.kt" } diff --git a/backend.native/tests/codegen/basics/for_loops.kt b/backend.native/tests/codegen/basics/for_loops.kt new file mode 100644 index 00000000000..cc8d1c2dee3 --- /dev/null +++ b/backend.native/tests/codegen/basics/for_loops.kt @@ -0,0 +1,54 @@ +fun main(args: Array) { + + // Simple loops + for (i in 0..4) { + print(i) + } + println() + + for (i in 0 until 4) { + print(i) + } + println() + + for (i in 4 downTo 0) { + print(i) + } + println() + println() + + // Steps + for (i in 0..4 step 2) { + print(i) + } + println() + + for (i in 0 until 4 step 2) { + print(i) + } + println() + + for (i in 4 downTo 0 step 2) { + print(i) + } + println() + println() + + + // Two steps + for (i in 0..6 step 2 step 3) { + print(i) + } + println() + + for (i in 0 until 6 step 2 step 3) { + print(i) + } + println() + + for (i in 6 downTo 0 step 2 step 3) { + print(i) + } + println() + println() +} \ No newline at end of file diff --git a/backend.native/tests/codegen/basics/for_loops_errors.kt b/backend.native/tests/codegen/basics/for_loops_errors.kt new file mode 100644 index 00000000000..5023fdda249 --- /dev/null +++ b/backend.native/tests/codegen/basics/for_loops_errors.kt @@ -0,0 +1,51 @@ +fun main(args: Array) { + // Negative step. + try { + for (i in 0 .. 4 step -2) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + try { + for (i in 0 until 4 step -2) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + try { + for (i in 4 downTo 0 step -2) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + // Zero step. + try { + for (i in 0 .. 4 step 0) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + try { + for (i in 0 until 4 step 0) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + try { + for (i in 4 downTo 0 step 0) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + // Two steps, one is negative. + try { + for (i in 0 .. 4 step -2 step 3) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + try { + for (i in 0 until 4 step -2 step 3) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + try { + for (i in 4 downTo 0 step -2 step 3) print(i); println() + throw AssertionError() + } catch (e: IllegalArgumentException) {} + + println("OK") +} diff --git a/backend.native/tests/codegen/basics/for_loops_overflow.kt b/backend.native/tests/codegen/basics/for_loops_overflow.kt new file mode 100644 index 00000000000..69bfde26aa6 --- /dev/null +++ b/backend.native/tests/codegen/basics/for_loops_overflow.kt @@ -0,0 +1,8 @@ +fun main(args: Array) { + for (i in Int.MAX_VALUE - 1 .. Int.MAX_VALUE) { print(i); print(' ') }; println() + for (i in Int.MAX_VALUE - 1 until Int.MAX_VALUE) { print(i); print(' ') }; println() + for (i in Int.MIN_VALUE + 1 downTo Int.MIN_VALUE) { print(i); print(' ') }; println() + + val M = Int.MAX_VALUE / 2 + for (i in M + 4..M + 10 step M) { print(i); print(' ') }; println() +} \ No newline at end of file diff --git a/backend.native/tests/codegen/basics/for_loops_types.kt b/backend.native/tests/codegen/basics/for_loops_types.kt new file mode 100644 index 00000000000..6ffb760b0a5 --- /dev/null +++ b/backend.native/tests/codegen/basics/for_loops_types.kt @@ -0,0 +1,55 @@ +fun main(args: Array) { + for (i in 0.toByte() .. 4.toByte()) print(i); println() + for (i in 0.toByte() .. 4.toShort()) print(i); println() + for (i in 0.toByte() .. 4.toInt()) print(i); println() + for (i in 0.toByte() .. 4.toLong()) print(i); println() + for (i in 0.toShort() .. 4.toByte()) print(i); println() + for (i in 0.toShort() .. 4.toShort()) print(i); println() + for (i in 0.toShort() .. 4.toInt()) print(i); println() + for (i in 0.toShort() .. 4.toLong()) print(i); println() + for (i in 0.toInt() .. 4.toByte()) print(i); println() + for (i in 0.toInt() .. 4.toShort()) print(i); println() + for (i in 0.toInt() .. 4.toInt()) print(i); println() + for (i in 0.toInt() .. 4.toLong()) print(i); println() + for (i in 0.toLong() .. 4.toByte()) print(i); println() + for (i in 0.toLong() .. 4.toShort()) print(i); println() + for (i in 0.toLong() .. 4.toInt()) print(i); println() + for (i in 0.toLong() .. 4.toLong()) print(i); println() + for (i in 0.toByte() until 4.toByte()) print(i); println() + for (i in 0.toByte() until 4.toShort()) print(i); println() + for (i in 0.toByte() until 4.toInt()) print(i); println() + for (i in 0.toByte() until 4.toLong()) print(i); println() + for (i in 0.toShort() until 4.toByte()) print(i); println() + for (i in 0.toShort() until 4.toShort()) print(i); println() + for (i in 0.toShort() until 4.toInt()) print(i); println() + for (i in 0.toShort() until 4.toLong()) print(i); println() + for (i in 0.toInt() until 4.toByte()) print(i); println() + for (i in 0.toInt() until 4.toShort()) print(i); println() + for (i in 0.toInt() until 4.toInt()) print(i); println() + for (i in 0.toInt() until 4.toLong()) print(i); println() + for (i in 0.toLong() until 4.toByte()) print(i); println() + for (i in 0.toLong() until 4.toShort()) print(i); println() + for (i in 0.toLong() until 4.toInt()) print(i); println() + for (i in 0.toLong() until 4.toLong()) print(i); println() + for (i in 4.toByte() downTo 0.toByte()) print(i); println() + for (i in 4.toByte() downTo 0.toShort()) print(i); println() + for (i in 4.toByte() downTo 0.toInt()) print(i); println() + for (i in 4.toByte() downTo 0.toLong()) print(i); println() + for (i in 4.toShort() downTo 0.toByte()) print(i); println() + for (i in 4.toShort() downTo 0.toShort()) print(i); println() + for (i in 4.toShort() downTo 0.toInt()) print(i); println() + for (i in 4.toShort() downTo 0.toLong()) print(i); println() + for (i in 4.toInt() downTo 0.toByte()) print(i); println() + for (i in 4.toInt() downTo 0.toShort()) print(i); println() + for (i in 4.toInt() downTo 0.toInt()) print(i); println() + for (i in 4.toInt() downTo 0.toLong()) print(i); println() + for (i in 4.toLong() downTo 0.toByte()) print(i); println() + for (i in 4.toLong() downTo 0.toShort()) print(i); println() + for (i in 4.toLong() downTo 0.toInt()) print(i); println() + for (i in 4.toLong() downTo 0.toLong()) print(i); println() + for (i in 'a' .. 'd') print(i); println() + for (i in 'a' until 'd') print(i); println() + for (i in 'd' downTo 'a') print(i); println() + + // TODO: Add step tests. +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt index dd72ccf93c1..54ccefd9871 100644 --- a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt +++ b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt @@ -16,6 +16,8 @@ package konan.internal +import kotlin.internal.getProgressionLastElement + @ExportForCppRuntime fun ThrowNullPointerException(): Nothing { throw NullPointerException() @@ -84,4 +86,13 @@ fun > valuesForEnum(values: Array): Array result[value.ordinal] = value @Suppress("UNCHECKED_CAST") return result as Array -} \ No newline at end of file +} + +fun checkProgressionStep(step: Int) = if (step > 0) step else throw IllegalArgumentException("Step must be positive, was: $step.") +fun checkProgressionStep(step: Long) = if (step > 0) step else throw IllegalArgumentException("Step must be positive, was: $step.") + +fun getProgressionBound(start: Char, end: Char, step: Int): Char = + getProgressionBound(start.toInt(), end.toInt(), step).toChar() + +fun getProgressionBound(start: Int, end: Int, step: Int): Int = getProgressionLastElement(start, end, step) +fun getProgressionBound(start: Long, end: Long, step: Long): Long = getProgressionLastElement(start, end, step) \ No newline at end of file