For loops lowering improvements (#2191)

* Reduce descriptors and KotlinType usage
* Add Array.indices lowering.
* Added IrCallMatcher and IrFunctionMatcher
* Added benchmarks
This commit is contained in:
Sergey Bogolepov
2018-10-18 18:01:09 +07:00
committed by GitHub
parent 70e6b71134
commit 4c61454ea5
10 changed files with 891 additions and 560 deletions
@@ -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
@@ -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())
@@ -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<IrVariableSymbol, ForLoopInfo>()
internal val oldLoopToNewLoop = mutableMapOf<IrLoop, IrLoop>()
private val iteratorType = symbols.iterator.descriptor.defaultType.replaceArgumentsWithStarProjections()
private val scopeOwnerSymbol
get() = currentScope!!.scope.scopeOwnerSymbol
private val progressionElementClasses: List<IrClassSymbol> = mutableListOf(symbols.char).apply {
addAll(symbols.integerClasses)
}
private val progressionElementClassesTypes: List<SimpleType> = mutableListOf<SimpleType>().apply {
progressionElementClasses.mapTo(this) { it.descriptor.defaultType }
}
private val progressionElementClassesNullableTypes: List<SimpleType> = mutableListOf<SimpleType>().apply {
progressionElementClassesTypes.mapTo(this) { it.makeNullableAsSpecified(true) }
}
//region Symbols for progression building functions ================================================================
private fun getProgressionBuildingMethods(name: String): Set<IrFunctionSymbol> =
getMethodsForProgressionElements(name) {
it.valueParameters.size == 1 && it.valueParameters[0].type in progressionElementClassesTypes
}
private fun getProgressionBuildingExtensions(name: String, pkg: FqName): Set<IrFunctionSymbol> =
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<IrFunctionSymbol> =
mutableSetOf<IrFunctionSymbol>().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<IrFunctionSymbol> =
mutableSetOf<IrFunctionSymbol>().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<IrExpression, Boolean> {
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<ProgressionInfo?, Nothing?> {
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<IrStatement>()
/**
* 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<IrExpression, ForLoopInfo>? {
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
}
@@ -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<IrVariableSymbol, ForLoopInfo>()
internal val oldLoopToNewLoop = mutableMapOf<IrLoop, IrLoop>()
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<IrStatement>()
/**
* 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<IrExpression, ForLoopInfo>? {
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
}
@@ -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<IrClassSymbol>) : 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<IrClassSymbol>) : 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<IrClassSymbol>) : 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.<get-indices>") }
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<ProgressionInfo?, Nothing?>) : 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
}
}
@@ -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<ProgressionInfo?, Nothing?> {
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) }
}
}
@@ -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<IrCallMatcher>()
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)
@@ -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<IrFunctionMatcher>()
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<IrClassSymbol>
): IrFunctionMatcherContainer {
extensionReceiver { it != null && it.type.classifierOrNull in classes }
parameterCount { it == 1 }
fqName { it == fqName }
return this
}
@@ -0,0 +1,104 @@
package org.jetbrains.ring
class ForLoopsBenchmark {
private val array: Array<Int> = 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
}
}
@@ -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)