Remove "bound" and "closed" properties from HeaderInfo and

ForLoopHeader, instead assuming that all ranges are closed and that the
"last" property is already property calculated.

This means the HeaderInfo.last for arrays, "indices" and "until"
progressions are decremented from the bound. This simplifies the loop
construction and moves the work to the handlers. This will also simplify
the handling of "reversed()" in the future since we don't need to
consider the case where the lower bound is open.
This commit is contained in:
Mark Punzalan
2019-03-19 23:49:58 -07:00
committed by max-kammerer
parent 93deff5e57
commit 301ac90770
4 changed files with 114 additions and 115 deletions
@@ -60,8 +60,7 @@ internal sealed class HeaderInfo(
val progressionType: ProgressionType,
val first: IrExpression,
val last: IrExpression,
val step: IrExpression,
val closed: Boolean
val step: IrExpression
) {
val direction: ProgressionDirection by lazy {
// If step is a constant (either Int or Long), then we can determine the direction.
@@ -81,9 +80,9 @@ internal class ProgressionHeaderInfo(
first: IrExpression,
last: IrExpression,
step: IrExpression,
closed: Boolean = true,
val additionalVariables: List<IrVariable> = listOf(),
val additionalNotEmptyCondition: IrExpression? = null
) : HeaderInfo(progressionType, first, last, step, closed)
) : HeaderInfo(progressionType, first, last, step)
internal class ArrayHeaderInfo(
first: IrExpression,
@@ -94,8 +93,7 @@ internal class ArrayHeaderInfo(
ProgressionType.INT_PROGRESSION,
first,
last,
step,
closed = false
step
)
internal interface HeaderInfoHandler<T> {
@@ -23,12 +23,10 @@ import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.*
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,
@@ -47,17 +45,16 @@ internal sealed class ForLoopHeader(
internal class ProgressionLoopHeader(
private val headerInfo: ProgressionHeaderInfo,
inductionVariable: IrVariable,
bound: IrVariable,
last: IrVariable,
step: IrVariable
) : ForLoopHeader(inductionVariable, bound, last, step, headerInfo.progressionType, needsEmptinessCheck = true) {
) : ForLoopHeader(inductionVariable, last, step, headerInfo.progressionType, needsEmptinessCheck = true) {
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
irGet(inductionVariable)
}
override val declarations: List<IrStatement>
get() = listOf(inductionVariable, step, bound, last)
get() = headerInfo.additionalVariables + listOf(inductionVariable, step, last)
override fun buildInnerLoop(builder: DeclarationIrBuilder, loop: IrLoop, newBody: IrExpression?): IrLoop = with(builder) {
assert(loopVariable != null)
@@ -131,10 +128,9 @@ internal class ProgressionLoopHeader(
internal class ArrayLoopHeader(
private val headerInfo: ArrayHeaderInfo,
inductionVariable: IrVariable,
bound: IrVariable,
last: IrVariable,
step: IrVariable
) : ForLoopHeader(inductionVariable, bound, last, step, ProgressionType.INT_PROGRESSION, needsEmptinessCheck = false) {
) : ForLoopHeader(inductionVariable, last, step, ProgressionType.INT_PROGRESSION, needsEmptinessCheck = false) {
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
val arrayClass = (headerInfo.arrayVariable.type.classifierOrNull) as IrClassSymbol
@@ -146,7 +142,7 @@ internal class ArrayLoopHeader(
}
override val declarations: List<IrStatement>
get() = listOf(headerInfo.arrayVariable, inductionVariable, step, bound, last)
get() = listOf(headerInfo.arrayVariable, inductionVariable, step, last)
override fun buildInnerLoop(builder: DeclarationIrBuilder, loop: IrLoop, newBody: IrExpression?): IrLoop = with(builder) {
val builtIns = context.irBuiltIns
@@ -182,23 +178,28 @@ internal class HeaderProcessor(
if (!variable.type.isSubtypeOfClass(symbols.iterator)) {
return null
}
// Collect loop information.
val headerInfo = headerInfoBuilder.build(variable)
?: return null // If the iterable is not supported.
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.arrayVariable.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.
with(builder) builder@{
with(headerInfo) {
// For this loop:
//
// ```
// for (i in first()..last() step step())
// ```
//
// ...the functions may have side-effects so we need to call them in the following
// order: first(), last(), step().
//
// We also need to cast them to conform to the progression type so that operations
// in the induction variable within the loop are more efficient.
//
// In the above example, if first() is a Long and last() is an Int, this creates a
// LongProgression so last(), should be cast to a Long.
val inductionVariable = scope.createTemporaryVariable(
first.castIfNecessary(progressionType),
nameHint = "inductionVariable",
@@ -206,52 +207,32 @@ internal class HeaderProcessor(
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
)
val upperBoundTmpVariable = scope.createTemporaryVariable(
ensureNotNullable(last.castIfNecessary(progressionType)),
nameHint = "upperBound",
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
)
val stepExpression = ensureNotNullable(step)
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)
}
}
// 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.
// TODO: Confirm if casting to non-nullable is still necessary
val lastValue = scope.createTemporaryVariable(
lastExpression,
ensureNotNullable(last.castIfNecessary(progressionType)),
nameHint = "last",
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
)
return when (progressionInfo) {
val stepValue = scope.createTemporaryVariable(
ensureNotNullable(step),
nameHint = "step",
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
)
return when (headerInfo) {
is ArrayHeaderInfo -> ArrayLoopHeader(
progressionInfo,
headerInfo,
inductionVariable,
upperBoundTmpVariable,
lastValue,
stepValue
)
is ProgressionHeaderInfo -> ProgressionLoopHeader(
progressionInfo,
headerInfo,
inductionVariable,
upperBoundTmpVariable,
lastValue,
stepValue
)
@@ -13,12 +13,15 @@ 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.*
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.properties
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -74,7 +77,14 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
override fun build(call: IrCall, data: ProgressionType): HeaderInfo? =
with(context.createIrBuilder(call.symbol, call.startOffset, call.endOffset)) {
//
// `last = bound - 1` for the loop `for (i in first until bound)`.
val bound = scope.createTemporaryVariable(
call.getValueArgument(0)!!, nameHint = "bound",
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
)
val decFun = data.decFun(context.irBuiltIns)
val last = irCallOp(decFun.symbol, bound.type, call.getValueArgument(0)!!)
// An additional emptiness condition is required for the corner case:
//
// ```
@@ -94,15 +104,14 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
// ```
// if (first <= last && bound > MIN_VALUE) { /* loop */ }
// ```
//
// TODO: Do not add additionalEmptinessCondition if "bound" is const and > MIN_VALUE
ProgressionHeaderInfo(
data,
first = call.extensionReceiver!!,
last = call.getValueArgument(0)!!,
last = last,
step = irInt(1),
closed = false,
additionalNotEmptyCondition = buildMinValueConditionIfNecessary(data, call.getValueArgument(0)!!)
additionalVariables = listOf(bound),
additionalNotEmptyCondition = buildMinValueConditionIfNecessary(data, irGet(bound))
)
}
@@ -136,68 +145,78 @@ internal class IndicesHandler(val context: CommonBackendContext) : ProgressionHa
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 {
// `last = array.size - 1` for the loop `for (i in array.indices)`.
val arraySizeProperty = call.extensionReceiver!!.type.getClass()!!.properties.first { it.name.asString() == "size" }
val decFun = data.decFun(context.irBuiltIns)
val last = irCallOp(decFun.symbol, data.elementType(context.irBuiltIns), irCall(arraySizeProperty.getter!!).apply {
dispatchReceiver = call.extensionReceiver
}
})
ProgressionHeaderInfo(
data,
lowerBound,
upperBound,
step = irInt(1),
closed = false
first = irInt(0),
last = last,
step = irInt(1)
)
}
private fun DeclarationIrBuilder.buildMinValueConditionIfNecessary(
progressionType: ProgressionType,
bound: IrExpression
): IrExpression? {
val irBuiltIns = context.irBuiltIns
val minConst = when (progressionType) {
ProgressionType.INT_PROGRESSION -> irInt(Int.MIN_VALUE)
ProgressionType.CHAR_PROGRESSION -> irChar(Char.MIN_VALUE)
ProgressionType.LONG_PROGRESSION -> irLong(Long.MIN_VALUE)
}
val progressionKotlinType = progressionType.elementType(irBuiltIns).toKotlinType()
return irCall(irBuiltIns.greaterFunByOperandType[progressionKotlinType]!!).apply {
putValueArgument(0, bound)
putValueArgument(1, minConst)
}
}
}
internal class ArrayIterationHandler(val context: CommonBackendContext) : HeaderInfoHandler<Nothing?> {
internal class ArrayIterationHandler(private val context: CommonBackendContext) : HeaderInfoHandler<Nothing?> {
private val intDecFun = ProgressionType.INT_PROGRESSION.decFun(context.irBuiltIns)
// No support for rare cases like `T : IntArray` for now.
override val matcher = createIrCallMatcher {
origin { it == IrStatementOrigin.FOR_LOOP_ITERATOR }
// TODO: Support rare cases like `T : IntArray`
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 {
// Consider the 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]) }
// ```
val arrayReference = scope.createTemporaryVariable(
call.dispatchReceiver!!, nameHint = "array",
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
)
// `last = array.size - 1` for the loop `for (i in array.indices)`.
val arraySizeProperty = arrayReference.type.getClass()!!.properties.first { it.name.asString() == "size" }
val last = irCallOp(intDecFun.symbol, context.irBuiltIns.intType, irCall(arraySizeProperty.getter!!).apply {
dispatchReceiver = irGet(arrayReference)
}
})
ArrayHeaderInfo(
first = irInt(0),
last = upperBound,
last = last,
step = irInt(1),
arrayVariable = arrayReference
)
}
}
private fun ProgressionType.decFun(builtIns: IrBuiltIns): IrFunction {
val symbol =
when (this) {
ProgressionType.INT_PROGRESSION -> builtIns.intClass
ProgressionType.LONG_PROGRESSION -> builtIns.longClass
ProgressionType.CHAR_PROGRESSION -> builtIns.charClass
}
return symbol.owner.functions.first { it.name.asString() == "dec" }
}
@@ -249,11 +249,12 @@ fun IrBuilderWithScope.irCallOp(
callee: IrFunctionSymbol,
type: IrType,
dispatchReceiver: IrExpression,
argument: IrExpression
argument: IrExpression? = null
): IrCall =
irCall(callee, type).apply {
this.dispatchReceiver = dispatchReceiver
putValueArgument(0, argument)
if (argument != null)
putValueArgument(0, argument)
}
fun IrBuilderWithScope.typeOperator(