JVM_IR reuse loop variable as index variable should happen after LDL

We can't apply "reuse loop variable as index variable" transformation
before local declarations lowering, otherwise it will affect captured
loop variable behavior, resulting in KT-48626.

Since it's JVM-specific, move it to JvmOptimizationLowering.
This commit is contained in:
Dmitry Petrov
2021-09-06 16:42:02 +03:00
committed by TeamCityServer
parent f62ffeaa0a
commit d9e4dec810
19 changed files with 445 additions and 133 deletions
@@ -56,9 +56,6 @@ interface CommonBackendContext : BackendContext, LoggingContext {
val preferJavaLikeCounterLoop: Boolean
get() = false
val reuseLoopVariableAsInductionVariable: Boolean
get() = false
val doWhileCounterLoopOrigin: IrStatementOrigin?
get() = null
@@ -8,18 +8,16 @@ package org.jetbrains.kotlin.backend.common.lower.loops
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.lower.AbstractVariableRemapper
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSymbolOwner
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.symbols.impl.IrVariableSymbolImpl
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.*
@@ -190,102 +188,9 @@ private class RangeLoopTransformer(
statements[0] = loweredHeader
statements[1] = loopReplacementExpression
if (context.reuseLoopVariableAsInductionVariable && loopHeader.canReuseLoopVariableAsInductionVariable) {
reuseLoopVariableAsInductionVariable(expression)
}
return super.visitBlock(expression)
}
private fun reuseLoopVariableAsInductionVariable(irBlock: IrBlock) {
// Given a loop in the form:
// {
// var inductionVariable = <start>
// }
// do {
// if (!(<whileLoopCondition>)) break
// val loopVariable = inductionVariable
// <originalLoopBody>
// } while ( { inductionVariable += <step>; true } )
// replace it with:
// {
// var loopVariable' = <start>
// }
// do {
// if (!(<whileLoopCondition'>)) break
// <originalLoopBody'>
// } while ( { loopVariable' += <step>; true } )
// where whenLoopCondition' and originalLoopBody' are corresponding statements
// with inductionVariable and loopVariable remapped to loopVariable'.
//
// NB we can do so only with a do-while counter loop as described above,
// otherwise it changes semantics of 'continue' inside the loop.
val header = irBlock.statements[0] as? IrStatementContainer ?: return
val inductionVariableIndex = header.statements.indexOfFirst { it.isInductionVariable(context) }
if (inductionVariableIndex < 0) return
val inductionVariable = header.statements[inductionVariableIndex] as IrVariable
val innerLoop = findInnerDoWhileLoop(irBlock.statements[1]) ?: return
if (innerLoop.origin != context.doWhileCounterLoopOrigin) return
val loopVariableContainerAndIndex = findLoopVariable(innerLoop) ?: return
val (loopVariableContainer, loopVariableIndex) = loopVariableContainerAndIndex
val loopVariable = loopVariableContainer.statements[loopVariableIndex] as IrVariable
val inductionVariableType = inductionVariable.type
val loopVariableType = loopVariable.type
if (loopVariableType.isNullable()) return
if (loopVariableType.classifierOrNull != inductionVariableType.classifierOrNull) return
val newLoopVariable = IrVariableImpl(
loopVariable.startOffset, loopVariable.endOffset, loopVariable.origin,
IrVariableSymbolImpl(),
loopVariable.name, loopVariableType,
isVar = true, // NB original loop variable is 'val'
isConst = false, isLateinit = false
)
newLoopVariable.initializer = inductionVariable.initializer
header.statements[inductionVariableIndex] = newLoopVariable
loopVariableContainer.statements.removeAt(loopVariableIndex)
val remapper = object : AbstractVariableRemapper() {
override fun remapVariable(value: IrValueDeclaration): IrValueDeclaration? =
if (value == inductionVariable || value == loopVariable) newLoopVariable else null
}
irBlock.statements[1].transformChildren(remapper, null)
}
private fun findInnerDoWhileLoop(statement: IrStatement): IrDoWhileLoop? {
if (statement is IrDoWhileLoop) {
return statement
}
if (statement is IrWhen) {
val branch0Result = statement.branches[0].result
if (branch0Result is IrDoWhileLoop)
return branch0Result
}
return null
}
private fun findLoopVariable(doWhileLoop: IrDoWhileLoop): Pair<IrContainerExpression, Int>? {
val loopBody = doWhileLoop.body as? IrContainerExpression ?: return null
for ((index, statement) in loopBody.statements.withIndex()) {
if (statement.isLoopVariable())
return Pair(loopBody, index)
else if (statement is IrContainerExpression && statement.origin == IrStatementOrigin.FOR_LOOP_NEXT) {
val loopVarIndex = statement.statements.indexOfFirst { it.isLoopVariable() }
if (loopVarIndex < 0) return null
return Pair(statement, loopVarIndex)
}
}
return null
}
private fun IrStatement.isLoopVariable() =
this is IrVariable && origin == IrDeclarationOrigin.FOR_LOOP_VARIABLE
/**
* Lowers the "header" statement that stores the iterator into the loop variable
* (e.g., `val it = someIterable.iterator()`) and gather information for building the for-loop
@@ -44,10 +44,6 @@ interface ForLoopHeader {
*/
val consumesLoopVariableComponents: Boolean
/** `true` if it's possible to use loop variable as induction variable in this kind of loop */
val canReuseLoopVariableAsInductionVariable: Boolean
get() = false
/** Statements used to initialize an iteration of the loop (e.g., assign loop variable). */
fun initializeIteration(
loopVariable: IrVariable?,
@@ -62,7 +58,7 @@ interface ForLoopHeader {
internal const val inductionVariableName = "inductionVariable"
internal fun IrStatement.isInductionVariable(context: CommonBackendContext) =
fun IrStatement.isInductionVariable(context: CommonBackendContext) =
this is IrVariable &&
origin == context.inductionVariableOrigin &&
name.asString() == inductionVariableName
@@ -75,8 +71,6 @@ abstract class NumericForLoopHeader<T : NumericHeaderInfo>(
override val consumesLoopVariableComponents = false
override val canReuseLoopVariableAsInductionVariable get() = true
val inductionVariable: IrVariable
protected val stepVariable: IrVariable?
@@ -445,51 +439,56 @@ class ProgressionLoopHeader(
): LoopReplacement {
// Transform loop:
// while (<newLoopCondition>) {
// {
// <loopVarAssignments>
// inductionVariable += step
// { // FOR_LOOP_NEXT
// <initializeLoopIteration>
// <inductionVariableUpdate>
// }
// <originalLoopBody>
// }
// to:
// do {
// if (!(<newLoopCondition>)) break
// val forLoopVariable = inductionVariable
// { // FOR_LOOP_NEXT
// if (!(<newLoopCondition>)) break
// <initializeLoopIteration>
// }
// <originalLoopBody>
// } while ( { inductionVariable += step; true } )
// } while (
// {
// <inductionVariableUpdate>
// true
// }
// )
val bodyBlock = newBody as? IrContainerExpression
?: throw AssertionError("newBody: ${newBody?.dump()}")
val forLoopNextBlock = bodyBlock.statements[0] as? IrContainerExpression
?: throw AssertionError("bodyBlock[0]: ${bodyBlock.statements[0].dump()}")
if (forLoopNextBlock.origin != IrStatementOrigin.FOR_LOOP_NEXT)
throw AssertionError("FOR_LOOP_NEXT expected: ${forLoopNextBlock.dump()}")
val loopStep = forLoopNextBlock.statements.last() as? IrSetValue
val inductionVariableUpdate = forLoopNextBlock.statements.last() as? IrSetValue
?: throw AssertionError("forLoopNextBlock.last: ${forLoopNextBlock.statements.last().dump()}")
val doWhileLoop = IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, context.doWhileCounterLoopOrigin)
doWhileLoop.label = oldLoop.label
val negatedConditionCheck = createNegatedConditionCheck(newLoopCondition, doWhileLoop)
bodyBlock.statements[0] = IrCompositeImpl(
forLoopNextBlock.startOffset, forLoopNextBlock.endOffset,
forLoopNextBlock.type,
forLoopNextBlock.origin,
).apply {
statements.add(createNegatedConditionCheck(newLoopCondition, doWhileLoop))
if (forLoopNextBlock.statements.size >= 2)
statements.addAll(forLoopNextBlock.statements.subList(0, forLoopNextBlock.statements.lastIndex))
}
bodyBlock.statements[0] = negatedConditionCheck
val loopVarAssignments =
if (forLoopNextBlock.statements.size == 2)
forLoopNextBlock.statements[0]
else
IrCompositeImpl(
forLoopNextBlock.startOffset, forLoopNextBlock.endOffset, forLoopNextBlock.type, null,
forLoopNextBlock.statements.subList(0, forLoopNextBlock.statements.lastIndex)
)
bodyBlock.statements.add(1, loopVarAssignments)
doWhileLoop.body = bodyBlock
val stepStartOffset = loopStep.startOffset
val stepEndOffset = loopStep.endOffset
val stepStartOffset = inductionVariableUpdate.startOffset
val stepEndOffset = inductionVariableUpdate.endOffset
val doWhileCondition =
IrCompositeImpl(
stepStartOffset, stepEndOffset, context.irBuiltIns.booleanType, null,
listOf(
loopStep,
inductionVariableUpdate,
IrConstImpl.boolean(stepStartOffset, stepEndOffset, context.irBuiltIns.booleanType, true)
)
)