[K/N] Refactored RedundantCoercionsCleaner

#KT-58654 Fixed
This commit is contained in:
Igor Chevdar
2023-05-24 08:47:23 +03:00
committed by Space Team
parent 1ae0350ca5
commit d9b0f2a75c
@@ -6,276 +6,217 @@
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.getInlinedClassNative
import org.jetbrains.kotlin.backend.konan.ir.isBoxOrUnboxCall
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationBase
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.impl.IrReturnableBlockSymbolImpl
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.getArgumentsWithIr
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.*
internal class RedundantCoercionsCleaner(val context: Context) : FileLoweringPass, IrElementTransformerVoid() {
/*
* The algorithm tries to find and remove matching pairs of box/unbox calls.
*
* - The leaf points are either a call to a matching box/unbox function or a constant primitive with appropriate type:
* box<T>(unbox<T>(value)) --> value
* unbox<T>(const<Any>(value)) --> const<T>(value)
*
* - In case of a when clause or a returnable block, the algorithm tries to propagate the coercion to subvalues if possible:
* box<T>(when {
* predicate1 -> unbox<T>(value1)
* predicate2 -> value2
* }) --> when {
* predicate1 -> value1
* predicate2 -> box<T>(value2)
* }
* box<T>(block {
* ..
* return@block unbox<T>(value1)
* ..
* return@block value2
* }) --> block {
* ..
* return@block value1
* ..
* return@block box<T>(value2)
* }
*
* - There might be also some casts in between matching box/unbox pair, the algorithm assumes that in the correct IR
* such casts are redundant and eliminates them.
*/
internal class RedundantCoercionsCleaner(val context: Context) : FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.transformChildren(transformer, null)
}
private class PossiblyFoldedExpression(val expression: IrExpression, val folded: Boolean) {
fun getFullExpression(coercion: IrCall, cast: IrTypeOperatorCall?): IrExpression {
if (folded) return expression
require (coercion.dispatchReceiver == null && coercion.extensionReceiver == null) {
"Expected either <box> or <unbox> function without any receivers"
private class TransformerState(val coercion: IrCall) {
var folded = false
val casts = mutableListOf<IrTypeOperatorCall>()
fun copy() = TransformerState(coercion).also {
it.folded = folded
it.casts.addAll(casts)
}
fun applyCoercion(expression: IrExpression): IrExpression {
var result = expression
for (i in casts.size - 1 downTo 0) {
val cast = casts[i]
result = with(cast) {
IrTypeOperatorCallImpl(startOffset, endOffset, type, operator, typeOperand, result)
}
}
val castedExpression =
if (cast == null)
expression
else with (cast) {
IrTypeOperatorCallImpl(startOffset, endOffset, type, operator,
typeOperand, expression)
}
with (coercion) {
return IrCallImpl(startOffset, endOffset, type, symbol, typeArgumentsCount, symbol.owner.valueParameters.size, origin).apply {
putValueArgument(0, castedExpression)
return with(coercion) {
IrCallImpl(
startOffset, endOffset, type, symbol, typeArgumentsCount, valueArgumentsCount, origin
).apply {
putValueArgument(0, result)
}
}
}
}
private val returnableBlockValues = mutableMapOf<IrReturnableBlock, MutableList<IrExpression>>()
private fun computeReturnableBlockValues(irFile: IrFile) {
irFile.acceptChildrenVoid(object: IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitContainerExpression(expression: IrContainerExpression) {
if (expression is IrReturnableBlock)
returnableBlockValues[expression] = mutableListOf()
super.visitContainerExpression(expression)
}
override fun visitReturn(expression: IrReturn) {
val returnableBlock = expression.returnTargetSymbol.owner as? IrReturnableBlock
if (returnableBlock != null)
returnableBlockValues[returnableBlock]!!.add(expression.value)
super.visitReturn(expression)
}
})
}
override fun lower(irFile: IrFile) {
computeReturnableBlockValues(irFile)
irFile.transformChildrenVoid(this)
}
override fun visitCall(expression: IrCall): IrExpression {
if (!expression.isBoxOrUnboxCall())
return super.visitCall(expression)
val argument = expression.getArgumentsWithIr().single().second
val foldedArgument = fold(
expression = argument,
coercion = expression,
cast = null,
transformRecursively = true)
return foldedArgument.getFullExpression(expression, null)
}
private fun IrFunction.getCoercedClass(): IrClass {
if (name.asString().endsWith("-box>"))
return valueParameters[0].type.classifierOrFail.owner as IrClass
if (name.asString().endsWith("-unbox>"))
return returnType.classifierOrFail.owner as IrClass
error("Unexpected coercion: ${this.dump()}")
error("Unexpected coercion: ${this.render()}")
}
private fun IrExpression.unwrapImplicitCasts(): IrExpression {
var expression = this
while (expression is IrTypeOperatorCall && expression.operator == IrTypeOperator.IMPLICIT_CAST)
expression = expression.argument
return expression
}
private fun IrTypeOperator.isCast() =
this == IrTypeOperator.CAST || this == IrTypeOperator.IMPLICIT_CAST || this == IrTypeOperator.SAFE_CAST
/**
* TODO: JVM inliner crashed on attempt inline this function from transform.kt with:
* j.l.IllegalStateException: Couldn't obtain compiled function body for
* public inline fun <reified T : org.jetbrains.kotlin.ir.IrElement> kotlin.collections.MutableList<T>.transform...
*/
private inline fun <reified T : IrElement> MutableList<T>.transform(transformation: (T) -> IrElement) {
forEachIndexed { i, item ->
set(i, transformation(item) as T)
}
}
private data class PossiblyFoldedExpression(val expression: IrExpression, val folded: Boolean)
private fun fold(expression: IrExpression, coercion: IrCall,
cast: IrTypeOperatorCall?, transformRecursively: Boolean): PossiblyFoldedExpression {
private val transformer = object : IrElementTransformer<TransformerState?> {
override fun visitElement(element: IrElement, data: TransformerState?) = super.visitElement(element, null)
override fun visitDeclaration(declaration: IrDeclarationBase, data: TransformerState?) = super.visitDeclaration(declaration, null)
override fun visitExpression(expression: IrExpression, data: TransformerState?) = super.visitExpression(expression, null)
override fun visitConstantValue(expression: IrConstantValue, data: TransformerState?) = super.visitConstantValue(expression, null)
override fun visitBranch(branch: IrBranch, data: TransformerState?) = super.visitBranch(branch, null)
override fun visitCatch(aCatch: IrCatch, data: TransformerState?) = super.visitCatch(aCatch, null)
val transformer = this
override fun visitCall(expression: IrCall, data: TransformerState?): IrElement {
if (!expression.isBoxOrUnboxCall())
return super.visitCall(expression, null)
fun IrExpression.transformIfAsked() =
if (transformRecursively) this.transform(transformer, data = null) else this
fun IrElement.transformIfAsked() =
if (transformRecursively) this.transform(transformer, data = null) else this
val coercionDeclaringClass = coercion.symbol.owner.getCoercedClass()
expression.unwrapImplicitCasts().let {
if (it.isBoxOrUnboxCall()) {
val result =
if (coercionDeclaringClass == (it as IrCall).symbol.owner.getCoercedClass())
it.getArgumentsWithIr().single().second
else expression
return PossiblyFoldedExpression(result.transformIfAsked(), result != expression)
val argument = expression.getValueArgument(0)!!
return if (expression.symbol.owner.getCoercedClass() == data?.coercion?.symbol?.owner?.getCoercedClass()) {
data.folded = true
argument.transform(this, null)
} else {
val state = TransformerState(expression)
val result = argument.transform(this, state)
if (state.folded)
result
else
expression.also { it.putValueArgument(0, result) }
}
}
return when (expression) {
is IrReturnableBlock -> {
val foldedReturnableBlockValues = returnableBlockValues[expression]!!.associate {
it to fold(it, coercion, cast, false)
}
val someoneFolded = foldedReturnableBlockValues.any { it.value.folded }
val transformedReturnableBlock =
if (!someoneFolded)
expression
else {
val oldSymbol = expression.symbol
val newSymbol = IrReturnableBlockSymbolImpl()
val transformedReturnableBlock = with(expression) {
IrReturnableBlockImpl(
startOffset = startOffset,
endOffset = endOffset,
type = coercion.type,
symbol = newSymbol,
origin = origin,
statements = statements,
)
}
/*
* Visitor below requires being very careful when changing it.
* It heavily relies on implementation of fold and getFullExpression functions,
* in particular it uses fact, that parts of foldedReturnableBlockValues will be replaced
* simultaneously with IR.
*
* For example, this requires replacing ReturnableBlock symbols in separate pass,
* because it needs new return nodes to be created, which breaks changing it's content in
* foldedReturnableBlockValues.
*/
transformedReturnableBlock.transformChildrenVoid(object: IrElementTransformerVoid() {
override fun visitExpression(expression: IrExpression): IrExpression {
expression.transformChildrenVoid()
foldedReturnableBlockValues[expression]?.let {
return it.getFullExpression(coercion, cast)
}
return expression
}
})
transformedReturnableBlock.transformChildrenVoid(object: IrElementTransformerVoid() {
override fun visitReturn(expression: IrReturn): IrExpression {
expression.transformChildrenVoid(this)
return if (expression.returnTargetSymbol != oldSymbol)
expression
else with(expression) {
IrReturnImpl(
startOffset = startOffset,
endOffset = endOffset,
type = context.irBuiltIns.nothingType,
returnTargetSymbol = newSymbol,
value = value)
}
}
})
transformedReturnableBlock
}
if (transformRecursively)
transformedReturnableBlock.transformChildrenVoid(this)
PossiblyFoldedExpression(transformedReturnableBlock, someoneFolded)
}
is IrBlock -> {
val statements = expression.statements
if (statements.isEmpty())
PossiblyFoldedExpression(expression, false)
else {
val lastStatement = statements.last() as IrExpression
val foldedLastStatement = fold(lastStatement, coercion, cast, transformRecursively)
statements.transform {
if (it == lastStatement)
foldedLastStatement.expression
else
it.transformIfAsked()
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: TransformerState?): IrExpression {
if (!expression.operator.isCast())
return super.visitTypeOperator(expression, null)
data?.casts?.push(expression)
val argument = expression.argument.transform(this, data)
data?.casts?.pop()
return if (data?.folded == true)
argument
else expression.also { it.argument = argument }
}
override fun visitConstantPrimitive(expression: IrConstantPrimitive, data: TransformerState?): IrConstantValue {
if (expression.value.type == data?.coercion?.type) {
data.folded = true
expression.type = expression.value.type
}
return expression
}
override fun visitWhen(expression: IrWhen, data: TransformerState?): IrExpression {
if (data == null)
return super.visitWhen(expression, null)
val branchResults = expression.branches.map { branch ->
branch.condition = branch.condition.transform(this, null)
val result = branch.result.transform(this, data)
val folded = data.folded
data.folded = false
PossiblyFoldedExpression(result, folded)
}
if (branchResults.all { !it.folded }) {
branchResults.forEachIndexed { index, branchResult ->
expression.branches[index].result = branchResult.expression
}
} else {
branchResults.forEachIndexed { index, branchResult ->
expression.branches[index].result = if (branchResult.folded)
branchResult.expression
else
data.applyCoercion(branchResult.expression)
}
expression.type = data.coercion.type
data.folded = true
}
return expression
}
val returnableBlockStates = mutableMapOf<IrReturnableBlock, TransformerState>()
val foldedReturnableBlocks = mutableSetOf<IrReturnableBlock>()
val foldedReturns = mutableSetOf<IrReturn>()
override fun visitReturn(expression: IrReturn, data: TransformerState?): IrExpression {
val returnableBlock = expression.returnTargetSymbol.owner as? IrReturnableBlock
return if (returnableBlock == null)
super.visitReturn(expression, data)
else {
val state = returnableBlockStates[returnableBlock]?.copy()
expression.value = expression.value.transform(this, state)
if (state?.folded == true) {
foldedReturnableBlocks.add(returnableBlock)
foldedReturns.add(expression)
}
expression
}
}
override fun visitBlock(expression: IrBlock, data: TransformerState?): IrExpression {
if (data == null)
return super.visitBlock(expression, null)
val returnableBlock = expression as? IrReturnableBlock
if (returnableBlock != null)
returnableBlockStates[returnableBlock] = data
val statements = expression.statements
for (i in statements.indices) {
val state = data.takeIf { i == statements.lastIndex && returnableBlock == null }
statements[i] = statements[i].transform(this, state) as IrStatement
}
if (returnableBlock in foldedReturnableBlocks) {
expression.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitReturn(expression: IrReturn): IrExpression {
val value = expression.value.transform(this, null)
expression.value = if (expression.returnTargetSymbol.owner == returnableBlock && expression !in foldedReturns)
data.applyCoercion(value)
else value
return expression
}
val transformedBlock =
if (!foldedLastStatement.folded)
expression
else with(expression) {
IrBlockImpl(
startOffset = startOffset,
endOffset = endOffset,
type = coercion.type,
origin = origin,
statements = statements)
}
PossiblyFoldedExpression(transformedBlock, foldedLastStatement.folded)
}
})
data.folded = true
}
is IrWhen -> {
val foldedBranches = expression.branches.map { fold(it.result, coercion, cast, transformRecursively) }
val someoneFolded = foldedBranches.any { it.folded }
val transformedWhen = with(expression) {
IrWhenImpl(startOffset, endOffset, if (someoneFolded) coercion.type else type, origin,
branches.asSequence().withIndex().map { (index, branch) ->
IrBranchImpl(
startOffset = branch.startOffset,
endOffset = branch.endOffset,
condition = branch.condition.transformIfAsked(),
result = if (someoneFolded)
foldedBranches[index].getFullExpression(coercion, cast)
else foldedBranches[index].expression)
}.toList())
}
return PossiblyFoldedExpression(transformedWhen, someoneFolded)
}
is IrTypeOperatorCall ->
if (expression.operator != IrTypeOperator.CAST
&& expression.operator != IrTypeOperator.IMPLICIT_CAST
&& expression.operator != IrTypeOperator.SAFE_CAST)
PossiblyFoldedExpression(expression.transformIfAsked(), false)
else {
if (expression.typeOperand.getInlinedClassNative() != coercionDeclaringClass)
PossiblyFoldedExpression(expression.transformIfAsked(), false)
else {
val foldedArgument = fold(expression.argument, coercion, expression, transformRecursively)
if (foldedArgument.folded)
foldedArgument
else
PossiblyFoldedExpression(expression.apply { argument = foldedArgument.expression }, false)
}
}
is IrConstantPrimitive ->
if (expression.value.type == coercion.type) {
// In case the initial and final types are equal, then
// the whole sequence of transformations is no-op, and we can remove them all
PossiblyFoldedExpression(expression.value, true)
} else {
// Types are not equal for ex. in kt53100_casts.kt
PossiblyFoldedExpression(expression.transformIfAsked(), false)
}
else -> PossiblyFoldedExpression(expression.transformIfAsked(), false)
if (data.folded)
expression.type = data.coercion.type
return expression
}
}
}