From 4c61454ea56525978c7b3a04ac1f3327802f4c5f Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov <1580082+sbogolepov@users.noreply.github.com> Date: Thu, 18 Oct 2018 18:01:09 +0700 Subject: [PATCH] For loops lowering improvements (#2191) * Reduce descriptors and KotlinType usage * Add Array.indices lowering. * Added IrCallMatcher and IrFunctionMatcher * Added benchmarks --- .../kotlin/backend/konan/KonanLower.kt | 1 + .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 15 +- .../backend/konan/lower/ForLoopsLowering.kt | 557 ------------------ .../konan/lower/loops/ForLoopsLowering.kt | 395 +++++++++++++ .../konan/lower/loops/ProgressionHandlers.kt | 147 +++++ .../konan/lower/loops/ProgressionInfo.kt | 70 +++ .../konan/lower/matchers/IrCallMatcher.kt | 47 ++ .../konan/lower/matchers/IrFunctionMatcher.kt | 96 +++ .../org/jetbrains/ring/ForLoopsBenchmark.kt | 104 ++++ .../kotlin/org/jetbrains/ring/launcher.kt | 19 + 10 files changed, 891 insertions(+), 560 deletions(-) delete mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ForLoopsLowering.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionHandlers.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionInfo.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrCallMatcher.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrFunctionMatcher.kt create mode 100644 performance/src/main/kotlin/org/jetbrains/ring/ForLoopsBenchmark.kt 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 5352e6e82ba..a8b42af4bc1 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 @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.konan.lower.FinallyBlocksLowering import org.jetbrains.kotlin.backend.konan.lower.InitializersLowering import org.jetbrains.kotlin.backend.konan.lower.LateinitLowering import org.jetbrains.kotlin.backend.konan.lower.SharedVariablesLowering +import org.jetbrains.kotlin.backend.konan.lower.loops.ForLoopsLowering import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.util.checkDeclarationParents 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 e716a457bc3..58787263c9f 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 @@ -36,6 +36,7 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils import kotlin.properties.Delegates @@ -289,15 +290,14 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val { findArrayExtension(it.descriptor, "contentHashCode") } ) - fun findArrayExtension(descriptor: ClassDescriptor, name: String): IrSimpleFunctionSymbol { + private fun findArrayExtension(descriptor: ClassDescriptor, name: String): IrSimpleFunctionSymbol { val functionDescriptor = builtInsPackage("kotlin", "collections") .getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND) .singleOrNull { it.valueParameters.isEmpty() && it.extensionReceiverParameter?.type?.constructor?.declarationDescriptor == descriptor && !it.isExpect - } - ?: throw Error(descriptor.toString()) + } ?: error(descriptor.toString()) return symbolTable.referenceSimpleFunction(functionDescriptor) } @@ -319,6 +319,15 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val .getContributedFunctions(Name.identifier("set"), NoLookupLocation.FROM_BACKEND) .single().let { symbolTable.referenceSimpleFunction(it) } + val arraySize = arrays.associateBy( + { it }, + { it.descriptor.unsubstitutedMemberScope + .getContributedVariables(Name.identifier("size"), NoLookupLocation.FROM_BACKEND) + .single().let { symbolTable.referenceSimpleFunction(it.getter!!) } } + ) + + + val valuesForEnum = symbolTable.referenceSimpleFunction( context.getInternalFunctions("valuesForEnum").single()) 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 deleted file mode 100644 index c1525074acb..00000000000 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ForLoopsLowering.kt +++ /dev/null @@ -1,557 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the LICENSE file. - */ - -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.ir.util.isSimpleTypeWithQuestionMark -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.IrDeclarationOrigin -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.types.makeNotNull -import org.jetbrains.kotlin.ir.types.toKotlinType -import org.jetbrains.kotlin.ir.util.* -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.isSubtypeOf -import org.jetbrains.kotlin.types.typeUtil.makeNotNullable -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 val progressionElementClasses: List = mutableListOf(symbols.char).apply { - addAll(symbols.integerClasses) - } - - private val progressionElementClassesTypes: List = mutableListOf().apply { - progressionElementClasses.mapTo(this) { it.descriptor.defaultType } - } - - private val progressionElementClassesNullableTypes: List = mutableListOf().apply { - progressionElementClassesTypes.mapTo(this) { it.makeNullableAsSpecified(true) } - } - - //region 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)) - } - } - //endregion - - //region Util methods ============================================================================================== - private fun IrExpression.castIfNecessary(progressionType: ProgressionType): IrExpression { - val type = this.type.toKotlinType() - assert(type in progressionElementClassesTypes || type in progressionElementClassesNullableTypes) - return if (type == progressionType.elementType) { - this - } else { - val function = symbols.getFunction(progressionType.numberCastFunctionName, type) - IrCallImpl(startOffset, endOffset, function.owner.returnType, function) - .apply { dispatchReceiver = this@castIfNecessary } - } - } - - 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 = - progressionType.elementType.let { type -> - val step = if (increasing) 1 else -1 - when { - KotlinBuiltIns.isInt(type) || KotlinBuiltIns.isChar(type) -> - IrConstImpl.int(startOffset, endOffset, context.irBuiltIns.intType, step) - KotlinBuiltIns.isLong(type) -> - IrConstImpl.long(startOffset, endOffset, context.irBuiltIns.longType, step.toLong()) - else -> throw IllegalArgumentException() - } - } - - private fun IrConst<*>.isOne() = - when (kind) { - IrConstKind.Long -> value as Long == 1L - IrConstKind.Int -> value as Int == 1 - else -> false - } - - // Used only by the assert. - private fun stepHasRightType(step: IrExpression, progressionType: ProgressionType) = - ((progressionType.isCharProgression() || progressionType.isIntProgression()) && - KotlinBuiltIns.isInt(step.type.toKotlinType().makeNotNullable())) || - (progressionType.isLongProgression() && - KotlinBuiltIns.isLong(step.type.toKotlinType().makeNotNullable())) - - private fun irCheckProgressionStep(progressionType: ProgressionType, - step: IrExpression): Pair { - if (step is IrConst<*> && - ((step.kind == IrConstKind.Long && step.value as Long > 0) || - (step.kind == IrConstKind.Int && step.value as Int > 0))) { - return step to !step.isOne() - } - // The frontend checks if the step has a right type (Long for LongProgression and Int for {Int/Char}Progression) - // so there is no need to cast it. - assert(stepHasRightType(step, progressionType)) - - val symbol = symbols.checkProgressionStep[step.type.toKotlinType().makeNotNullable()] - ?: throw IllegalArgumentException("Unknown progression element type: ${step.type}") - return IrCallImpl(step.startOffset, step.endOffset, symbol.owner.returnType, symbol).apply { - putValueArgument(0, step) - } to true - } - - private fun irGetProgressionLast(progressionType: ProgressionType, - first: IrVariable, - lastExpression: IrExpression, - step: IrVariable): IrExpression { - val symbol = symbols.getProgressionLast[progressionType.elementType] - ?: throw IllegalArgumentException("Unknown progression element 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 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) { - fun isIntProgression() = KotlinBuiltIns.isInt(elementType) - fun isLongProgression() = KotlinBuiltIns.isLong(elementType) - fun isCharProgression() = KotlinBuiltIns.isChar(elementType) - } - - private 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) - - /** Contains information about variables used in the loop. */ - private data class ForLoopInfo( - val progressionInfo: ProgressionInfo, - val inductionVariable: IrVariable, - val bound: IrVariable, - val last: IrVariable, - val step: IrVariable, - var loopVariable: IrVariable? = 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 = - ProgressionInfo(progressionType, - expression.extensionReceiver!!, - expression.getValueArgument(0)!!, - closed = false) - - 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 (newStepCheck, needBoundCalculation) = irCheckProgressionStep(progressionType, newStep) - val step = when { - it.step == null -> newStepCheck - // 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(newStepCheck) - it.step - } - else -> IrCompositeImpl(expression.startOffset, expression.endOffset, newStep.type).apply { - statements.add(it.step) - statements.add(newStepCheck) - } - } - ProgressionInfo(progressionType, it.first, it.bound, step, it.increasing, needBoundCalculation, it.closed) - } - - override fun visitElement(element: IrElement, data: Nothing?): ProgressionInfo? = null - - override fun visitCall(expression: IrCall, data: Nothing?): ProgressionInfo? { - val type = expression.type.toKotlinType() - 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 - - //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 - 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 composite. - val progressionInfo = initializer.dispatchReceiver?.accept(ProgressionInfoBuilder(), null) ?: 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(bound.castIfNecessary(progressionType), - nameHint = "bound", - origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE) - .also { statements.add(it) } - - - val stepExpression = (if (increasing) step else step?.unaryMinus()) ?: 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? = null - if (!closed) { - val decrementSymbol = symbols.getUnaryOperator(OperatorNameConventions.DEC, boundValue.descriptor.type) - lastExpression = irCall(decrementSymbol.owner).apply { - dispatchReceiver = irGet(boundValue) - } - } - if (needLastCalculation) { - lastExpression = irGetProgressionLast(progressionType, - inductionVariable, - lastExpression ?: irGet(boundValue), - stepValue) - } - val lastValue = if (lastExpression != null) { - scope.createTemporaryVariable(lastExpression, - nameHint = "last", - origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE).also { - statements.add(it) - } - } else { - boundValue - } - - 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.descriptor.type, - forLoopInfo.step.descriptor.type - ) - 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 - return irCall(context.irBuiltIns.greaterFunByOperandType[context.irBuiltIns.int]?.symbol!!).apply { - val minConst = when { - progressionType.isIntProgression() -> IrConstImpl - .int(startOffset, endOffset, context.irBuiltIns.intType, Int.MIN_VALUE) - progressionType.isCharProgression() -> IrConstImpl - .char(startOffset, endOffset, context.irBuiltIns.charType, 0.toChar()) - progressionType.isLongProgression() -> IrConstImpl - .long(startOffset, endOffset, context.irBuiltIns.longType, Long.MIN_VALUE) - else -> throw IllegalArgumentException("Unknown progression type") - } - val compareToCall = irCall(symbols.getBinaryOperator(OperatorNameConventions.COMPARE_TO, - forLoopInfo.bound.descriptor.type, - minConst.type.toKotlinType())).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.buildEmptyCheck(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.descriptor.type, - forLoopInfo.last.descriptor.type) - - 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 } - return 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 - } - - /** - * 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) - * } - */ - // 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 { - if (it is IrContainerExpression && !it.isTransparentScope) { - with(it) { IrCompositeImpl(startOffset, endOffset, type, origin, 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 - } - 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) - } - //endregion -} - 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 new file mode 100644 index 00000000000..7c00b42099a --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ForLoopsLowering.kt @@ -0,0 +1,395 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.kotlin.backend.konan.lower.loops + +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.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.* +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol +import org.jetbrains.kotlin.ir.types.* +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. + * + * 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 + * simple while loop over primitive induction variable. + */ +internal class ForLoopsLowering(val context: Context) : FileLoweringPass { + + private val progressionInfoBuilder = ProgressionInfoBuilder(context) + + override fun lower(irFile: IrFile) { + val transformer = ForLoopsTransformer(context, progressionInfoBuilder) + // 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 + } + }) + } +} + +/** 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 val symbols = context.ir.symbols + private val iteratorToLoopInfo = mutableMapOf() + internal val oldLoopToNewLoop = mutableMapOf() + + private val progressionElementClasses = + (symbols.integerClasses + symbols.char).toSet() + + 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 } + } + } + + 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) + ?: 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 + } + + /** + * 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) + * } + */ + // 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 { + 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 + } + oldLoopToNewLoop[loop] = newLoop + // Build a check for an empty progression before the loop. + return buildEmptinessCheck(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) + } + //endregion +} 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 new file mode 100644 index 00000000000..c348dbeba5b --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionHandlers.kt @@ -0,0 +1,147 @@ +package org.jetbrains.kotlin.backend.konan.lower.loops + +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.lower.matchers.IrCallMatcher +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.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 + +internal class RangeToHandler(val progressionElementClasses: Collection) : ProgressionHandler { + override val matcher = SimpleCalleeMatcher { + dispatchReceiver { it != null && it.type.classifierOrNull in progressionElementClasses } + fqName { it.pathSegments().last() == Name.identifier("rangeTo") } + parameterCount { it == 1 } + parameter(0) { it.type.classifierOrNull in progressionElementClasses } + } + + override fun build(call: IrCall, progressionType: ProgressionType) = + ProgressionInfo(progressionType, call.dispatchReceiver!!, call.getValueArgument(0)!!) +} + +internal class DownToHandler(val progressionElementClasses: Collection) : ProgressionHandler { + override val matcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.downTo"), progressionElementClasses) + parameter(0) { it.type.classifierOrNull in progressionElementClasses } + } + + override fun build(call: IrCall, progressionType: ProgressionType): ProgressionInfo? = + ProgressionInfo(progressionType, + call.extensionReceiver!!, + call.getValueArgument(0)!!, + increasing = false) +} + +internal class UntilHandler(val progressionElementClasses: Collection) : ProgressionHandler { + override val matcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.until"), progressionElementClasses) + parameter(0) { it.type.classifierOrNull in progressionElementClasses } + } + + override fun build(call: IrCall, progressionType: ProgressionType): ProgressionInfo? = + ProgressionInfo(progressionType, + call.extensionReceiver!!, + call.getValueArgument(0)!!, + closed = false) +} + +internal class IndicesHandler(val context: Context) : ProgressionHandler { + + private val symbols = context.ir.symbols + + private val supportedArrays = symbols.primitiveArrays.values + symbols.array + + override val matcher = createIrCallMatcher { + callee { + fqName { it == FqName("kotlin.collections.") } + parameterCount { it == 0 } + } + extensionReceiver { it != null && it.type.classifierOrNull in supportedArrays } + } + + override fun build(call: IrCall, progressionType: ProgressionType): ProgressionInfo? { + val int0 = IrConstImpl.int(call.startOffset, call.endOffset, context.irBuiltIns.intType, 0) + + val bound = with(context.createIrBuilder(call.symbol, call.startOffset, call.endOffset)) { + val clazz = call.extensionReceiver!!.type.classifierOrFail + val symbol = symbols.arraySize[clazz]!! + irCall(symbol).apply { + dispatchReceiver = call.extensionReceiver + } + } + return ProgressionInfo(progressionType, int0, bound, closed = false) + } +} + +internal class StepHandler(context: Context, val visitor: IrElementVisitor) : ProgressionHandler { + private val symbols = context.ir.symbols + + override val matcher: IrCallMatcher = SimpleCalleeMatcher { + singleArgumentExtension(FqName("kotlin.ranges.step"), symbols.progressionClasses) + parameter(0) { it.type.isInt() || it.type.isLong() } + } + + override fun build(call: IrCall, progressionType: ProgressionType): ProgressionInfo? { + 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) { + return null + } + + val newStep = call.getValueArgument(0)!! + val (newStepCheck, needBoundCalculation) = irCheckProgressionStep(symbols, progressionType, newStep) + return ProgressionInfo( + progressionType, + nestedInfo.first, + nestedInfo.bound, + newStepCheck, nestedInfo.increasing, + needBoundCalculation, + nestedInfo.closed) + } + + private fun IrConst<*>.stepIsOne() = when (kind) { + IrConstKind.Long -> value as Long == 1L + IrConstKind.Int -> value as Int == 1 + else -> false + } + + private fun IrExpression.isPositiveConst() = this is IrConst<*> && + ((kind == IrConstKind.Long && value as Long > 0) || (kind == IrConstKind.Int && value as Int > 0)) + + // Used only by the assert. + private fun stepHasRightType(step: IrExpression, progressionType: ProgressionType) = when (progressionType) { + + ProgressionType.CHAR_PROGRESSION, + ProgressionType.INT_PROGRESSION -> step.type.makeNotNull().isInt() + + ProgressionType.LONG_PROGRESSION -> step.type.makeNotNull().isLong() + } + + private fun irCheckProgressionStep(symbols: KonanSymbols, progressionType: ProgressionType, step: IrExpression) = + if (step.isPositiveConst()) { + step to !(step as IrConst<*>).stepIsOne() + } 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. + assert(stepHasRightType(step, progressionType)) + + val symbol = symbols.checkProgressionStep[step.type.makeNotNull().toKotlinType()] + ?: throw IllegalArgumentException("No `checkProgressionStep` for type ${step.type}") + IrCallImpl(step.startOffset, step.endOffset, symbol.owner.returnType, symbol).apply { + putValueArgument(0, step) + } to true + } +} 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 new file mode 100644 index 00000000000..e467152bbf6 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/loops/ProgressionInfo.kt @@ -0,0 +1,70 @@ +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 new file mode 100644 index 00000000000..3c55835eef5 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrCallMatcher.kt @@ -0,0 +1,47 @@ +package org.jetbrains.kotlin.backend.konan.lower.matchers + +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression + + +internal interface IrCallMatcher : (IrCall) -> Boolean + +/** + * IrCallMatcher that puts restrictions only on its callee. + */ +internal class SimpleCalleeMatcher( + restrictions: IrFunctionMatcherContainer.() -> Unit +) : IrCallMatcher { + + private val calleeRestriction: IrFunctionMatcher + = createIrFunctionRestrictions(restrictions) + + override fun invoke(call: IrCall) = calleeRestriction(call.symbol.owner) +} + +internal class IrCallExtensionReceiverMatcher( + val restriction: (IrExpression?) -> Boolean +) : IrCallMatcher { + override fun invoke(call: IrCall) = restriction(call.extensionReceiver) +} + +internal open class IrCallMatcherContainer : IrCallMatcher { + + private val matchers = mutableListOf() + + fun add(matcher: IrCallMatcher) { + matchers += matcher + } + + fun extensionReceiver(restriction: (IrExpression?) -> Boolean) = + add(IrCallExtensionReceiverMatcher(restriction)) + + fun callee(restrictions: IrFunctionMatcherContainer.() -> Unit) { + add(SimpleCalleeMatcher(restrictions)) + } + + override fun invoke(call: IrCall) = matchers.all { it(call) } +} + +internal fun createIrCallMatcher(restrictions: IrCallMatcherContainer.() -> Unit) = + IrCallMatcherContainer().apply(restrictions) \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrFunctionMatcher.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrFunctionMatcher.kt new file mode 100644 index 00000000000..9c1bd16ae64 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/matchers/IrFunctionMatcher.kt @@ -0,0 +1,96 @@ +package org.jetbrains.kotlin.backend.konan.lower.matchers + +import org.jetbrains.kotlin.backend.konan.irasdescriptors.fqNameSafe +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrValueParameter +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.types.classifierOrNull +import org.jetbrains.kotlin.name.FqName + +internal interface IrFunctionMatcher : (IrFunction) -> Boolean + +internal class ParameterMatcher( + val index: Int, + val restriction: (IrValueParameter) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + val params = function.valueParameters + return params.size > index && restriction(params[index]) + } +} + +internal class DispatchReceiverMatcher( + val restriction: (IrValueParameter?) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + return restriction(function.dispatchReceiverParameter) + } +} + +internal class ExtensionReceiverMatcher( + val restriction: (IrValueParameter?) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + return restriction(function.extensionReceiverParameter) + } +} + +internal class ParameterCountMatcher( + val restriction: (Int) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + return restriction(function.valueParameters.size) + } +} + +internal class FqNameMatcher( + val restriction: (FqName) -> Boolean +) : IrFunctionMatcher { + + override fun invoke(function: IrFunction): Boolean { + return restriction(function.fqNameSafe) + } +} + +internal open class IrFunctionMatcherContainer : IrFunctionMatcher { + private val restrictions = mutableListOf() + + fun add(restriction: IrFunctionMatcher) { + restrictions += restriction + } + + fun fqName(restriction: (FqName) -> Boolean) = + add(FqNameMatcher(restriction)) + + fun parameterCount(restriction: (Int) -> Boolean) = + add(ParameterCountMatcher(restriction)) + + fun extensionReceiver(restriction: (IrValueParameter?) -> Boolean) = + add(ExtensionReceiverMatcher(restriction)) + + fun dispatchReceiver(restriction: (IrValueParameter?) -> Boolean) = + add(DispatchReceiverMatcher(restriction)) + + fun parameter(index: Int, restriction: (IrValueParameter) -> Boolean) = + add(ParameterMatcher(index, restriction)) + + override fun invoke(function: IrFunction) = restrictions.all { it(function) } +} + +internal fun createIrFunctionRestrictions(restrictions: IrFunctionMatcherContainer.() -> Unit) = + IrFunctionMatcherContainer().apply(restrictions) + +internal fun IrFunctionMatcherContainer.singleArgumentExtension( + fqName: FqName, + classes: Collection +): IrFunctionMatcherContainer { + extensionReceiver { it != null && it.type.classifierOrNull in classes } + parameterCount { it == 1 } + fqName { it == fqName } + return this +} + diff --git a/performance/src/main/kotlin/org/jetbrains/ring/ForLoopsBenchmark.kt b/performance/src/main/kotlin/org/jetbrains/ring/ForLoopsBenchmark.kt new file mode 100644 index 00000000000..5fa73879f33 --- /dev/null +++ b/performance/src/main/kotlin/org/jetbrains/ring/ForLoopsBenchmark.kt @@ -0,0 +1,104 @@ +package org.jetbrains.ring + +class ForLoopsBenchmark { + + private val array: Array = Array(BENCHMARK_SIZE) { + it + } + + private val intArray: IntArray = IntArray(BENCHMARK_SIZE) { + it + } + + private val charArray: CharArray = CharArray(BENCHMARK_SIZE) { + it.toChar() + } + + private val string: String = charArray.joinToString() + + private val floatArray: FloatArray = FloatArray(BENCHMARK_SIZE) { + it.toFloat() + } + + fun arrayLoop(): Long { + var sum = 0L + for (e in array) { + sum += e + } + return sum + } + + fun intArrayLoop(): Long { + var sum = 0L + for (e in intArray) { + sum += e + } + return sum + } + + fun charArrayLoop(): Long { + var sum = 0L + for (e in charArray) { + sum += e.toLong() + } + return sum + } + + fun stringLoop(): Long { + var sum = 0L + for (e in string) { + sum += e.hashCode() + } + return sum + } + + fun floatArrayLoop(): Double { + var sum = 0.0 + for (e in array) { + sum += e + } + return sum + } + + // Iterations over .indices + + fun arrayIndicesLoop(): Long { + var sum = 0L + for (i in array.indices) { + sum += array[i] + } + return sum + } + + fun intArrayIndicesLoop(): Long { + var sum = 0L + for (i in intArray.indices) { + sum += intArray[i] + } + return sum + } + + fun charArrayIndicesLoop(): Long { + var sum = 0L + for (i in charArray.indices) { + sum += charArray[i].toLong() + } + return sum + } + + fun stringIndicesLoop(): Long { + var sum = 0L + for (i in string.indices) { + sum += string[i].hashCode() + } + return sum + } + + fun floatArrayIndicesLoop(): Double { + var sum = 0.0 + for (i in floatArray.indices) { + sum += floatArray[i] + } + return sum + } +} \ No newline at end of file diff --git a/performance/src/main/kotlin/org/jetbrains/ring/launcher.kt b/performance/src/main/kotlin/org/jetbrains/ring/launcher.kt index 1f52c759ce4..b7fb4586821 100644 --- a/performance/src/main/kotlin/org/jetbrains/ring/launcher.kt +++ b/performance/src/main/kotlin/org/jetbrains/ring/launcher.kt @@ -79,6 +79,7 @@ class Launcher(val numWarmIterations: Int) { runElvisBenchmark() runEulerBenchmark() runFibonacciBenchmark() + runForLoopBenchmark() runInlineBenchmark() runIntArrayBenchmark() runIntBaselineBenchmark() @@ -267,6 +268,24 @@ class Launcher(val numWarmIterations: Int) { //-------------------------------------------------------------------------// + fun runForLoopBenchmark() { + val benchmark = ForLoopsBenchmark() + + results["ForLoops.arrayLoop"] = launch(benchmark::arrayLoop) + results["ForLoops.intArrayLoop"] = launch(benchmark::intArrayLoop) + results["ForLoops.floatArrayLoop"] = launch(benchmark::floatArrayLoop) + results["ForLoops.charArrayLoop"] = launch(benchmark::charArrayLoop) + results["ForLoops.stringLoop"] = launch(benchmark::stringLoop) + + results["ForLoops.arrayIndicesLoop"] = launch(benchmark::arrayIndicesLoop) + results["ForLoops.intArrayIndicesLoop"] = launch(benchmark::intArrayIndicesLoop) + results["ForLoops.floatArrayIndicesLoop"] = launch(benchmark::floatArrayIndicesLoop) + results["ForLoops.charArrayIndicesLoop"] = launch(benchmark::charArrayIndicesLoop) + results["ForLoops.stringIndicesLoop"] = launch(benchmark::stringIndicesLoop) + } + + //-------------------------------------------------------------------------// + fun runInlineBenchmark() { val benchmark = InlineBenchmark() results["Inline.calculate"] = launch(benchmark::calculate)