Minimal porting of ForLoopsLowering from kotlin-native.

Diffs resulting from the port:
https://github.com/punzki/kotlin-native/compare/master...punzki:for-loop-diff

When ForLoopsLowering is added to the JVM lowering phases, this causes some of the forLoop bytecode tests to pass, and many still fail due to differences in behavior compared to non-IR backend:
- Generated conditions for lowered loop
- Generated temporary variables
- Supported iterables (e.g., withIndex(), CharSequences)
- Means of incrementing induction variable

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