diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/RedundantCoercionsCleaner.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/RedundantCoercionsCleaner.kt index 7c1049b7212..76e9c21953a 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/RedundantCoercionsCleaner.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/RedundantCoercionsCleaner.kt @@ -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(unbox(value)) --> value + * unbox(const(value)) --> const(value) + * + * - In case of a when clause or a returnable block, the algorithm tries to propagate the coercion to subvalues if possible: + * box(when { + * predicate1 -> unbox(value1) + * predicate2 -> value2 + * }) --> when { + * predicate1 -> value1 + * predicate2 -> box(value2) + * } + * box(block { + * .. + * return@block unbox(value1) + * .. + * return@block value2 + * }) --> block { + * .. + * return@block value1 + * .. + * return@block box(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 or function without any receivers" + private class TransformerState(val coercion: IrCall) { + var folded = false + val casts = mutableListOf() + + 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>() - - 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 kotlin.collections.MutableList.transform... - */ - private inline fun MutableList.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 { + 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() + val foldedReturnableBlocks = mutableSetOf() + val foldedReturns = mutableSetOf() + + 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 } } }