JVM_IR: Rewrite '?.' and '?:' chains
This shortens code and avoids unnecessary boxing in some cases.
This commit is contained in:
+326
@@ -0,0 +1,326 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.CommonBackendContext
|
||||||
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
|
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||||
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irBlock
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irGet
|
||||||
|
import org.jetbrains.kotlin.ir.builders.irIfNull
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrVariable
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
|
||||||
|
import org.jetbrains.kotlin.ir.descriptors.WrappedVariableDescriptor
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
|
||||||
|
import org.jetbrains.kotlin.ir.types.isNullable
|
||||||
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.*
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
|
||||||
|
val ifNullExpressionsFusionPhase =
|
||||||
|
makeIrFilePhase(
|
||||||
|
::IfNullExpressionsFusionLowering,
|
||||||
|
name = "IfNullExpressionsFusionLowering",
|
||||||
|
description = "Simplify '?.' and '?:' operator chains"
|
||||||
|
)
|
||||||
|
|
||||||
|
class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileLoweringPass {
|
||||||
|
|
||||||
|
override fun lower(irFile: IrFile) {
|
||||||
|
irFile.transformChildrenVoid(Transformer(irFile))
|
||||||
|
irFile.patchDeclarationParents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Transformer(private val currentFile: IrFile) : IrElementTransformerVoid() {
|
||||||
|
override fun visitBlock(expression: IrBlock): IrExpression =
|
||||||
|
expression.transformPostfix {
|
||||||
|
fuseIfNullExpressions(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are looking for the "if-null" expressions:
|
||||||
|
//
|
||||||
|
// IfNull( E, v, A0, A1 ) =
|
||||||
|
// {
|
||||||
|
// val v = E
|
||||||
|
// if (v == null) A0 else A1
|
||||||
|
// }
|
||||||
|
// where
|
||||||
|
// E is an expression,
|
||||||
|
// v is a temporary variable,
|
||||||
|
// A0, A1 are expressions possibly containing 'v'.
|
||||||
|
//
|
||||||
|
// Such expressions are subject to the following rewrite rule:
|
||||||
|
//
|
||||||
|
// FUSE_IF_NULL:
|
||||||
|
// IfNull( IfNull( E, u, A0, A1 ), v, B0, B1 ) =>
|
||||||
|
// IfNull(
|
||||||
|
// E, u,
|
||||||
|
// SIMPLIFY_IF_NULL( A0, tmp1, B0[v <- 'tmp1'], B1[v <- 'tmp1']; { u == null }) =: C0,
|
||||||
|
// SIMPLIFY_IF_NULL( A1, tmp2, B0[v <- 'tmp2'], B1[v <- 'tmp2']; { u != null }) =: C1
|
||||||
|
// )
|
||||||
|
// where 'SIMPLIFY_IF_NULL( S, v, X0, X1; Context )' attempts to simplify 'IfNull( S, v, X0, X1 )' in Context,
|
||||||
|
// and 'X[v <- Y]' is an expression X with all occurrences of variable 'v' replaced with expression Y.
|
||||||
|
//
|
||||||
|
// Note that this transformation in general is not beneficial.
|
||||||
|
// However, the structure of argument expressions in case of '?.' and '?:' operators often allows reductions in SIMPLIFY_IF_NULL.
|
||||||
|
// This transformation is applied only if
|
||||||
|
// size(A0) + size(A1) + size(B0) + size(B1) + 1 >= size(C0) + size(C1)
|
||||||
|
// where
|
||||||
|
// size(X) is number of nodes in IR tree for X,
|
||||||
|
// thus assuring that we don't grow trees exponentially.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// a?.x ?: b?.y ?: z
|
||||||
|
//
|
||||||
|
// is translated to
|
||||||
|
// IfNull(
|
||||||
|
// IfNull(
|
||||||
|
// IfNull( 'a', t1, 'null', 't1.x' ),
|
||||||
|
// t2,
|
||||||
|
// IfNull( 'b', t3, 'null', 't3.y' ),
|
||||||
|
// 't2'
|
||||||
|
// ),
|
||||||
|
// t0, 'z', 't0'
|
||||||
|
// )
|
||||||
|
// which, assuming 'a.x' and 'b.y' are stable not-null expressions, is optimized to
|
||||||
|
// IfNull(
|
||||||
|
// 'a', t1,
|
||||||
|
// IfNull( 'b', t2, 'z', 't2.y' ),
|
||||||
|
// 't1.x'
|
||||||
|
// )
|
||||||
|
// by applying FUSE_IF_NULL twice.
|
||||||
|
//
|
||||||
|
private fun fuseIfNullExpressions(expression: IrBlock) {
|
||||||
|
val ifNull1 = expression.matchIfNullExpr() ?: return
|
||||||
|
val ifNull2 = ifNull1.subjectExpr.matchIfNullExpr() ?: return
|
||||||
|
|
||||||
|
val u = ifNull1.subjectVar
|
||||||
|
// We are going to erase 1st variable. Do so only if it is temporary (true for variables introduced for '?.' and '?:').
|
||||||
|
if (u.origin != IrDeclarationOrigin.IR_TEMPORARY_VARIABLE) return
|
||||||
|
|
||||||
|
val a0 = ifNull2.ifNullExpr
|
||||||
|
val a1 = ifNull2.ifNotNullExpr
|
||||||
|
val tmp1 = createTemporaryVar(a0)
|
||||||
|
val tmp2 = createTemporaryVar(a1)
|
||||||
|
|
||||||
|
val b0 = ifNull1.ifNullExpr
|
||||||
|
val b0tmp1 = b0.substituteVariable(u.symbol, tmp1.symbol)
|
||||||
|
val b0tmp2 = b0.substituteVariable(u.symbol, tmp2.symbol)
|
||||||
|
|
||||||
|
val b1 = ifNull1.ifNotNullExpr
|
||||||
|
val b1tmp1 = b1.substituteVariable(u.symbol, tmp1.symbol)
|
||||||
|
val b1tmp2 = b1.substituteVariable(u.symbol, tmp2.symbol)
|
||||||
|
|
||||||
|
val v = ifNull2.subjectVar
|
||||||
|
val c0 = simplifyIfNull(tmp1, b0tmp1, b1tmp1, v.symbol, true)
|
||||||
|
val c1 = simplifyIfNull(tmp2, b0tmp2, b1tmp2, v.symbol, false)
|
||||||
|
|
||||||
|
val sizeBeforeEstimate = a0.size() + a1.size() + b0.size() + b1.size() + 1
|
||||||
|
val sizeAfterEstimate = c0.size() + c1.size()
|
||||||
|
if (sizeBeforeEstimate < sizeAfterEstimate) return
|
||||||
|
|
||||||
|
val newBlock = constructIfNullExpr(v, c0, c1)
|
||||||
|
|
||||||
|
expression.statements.clear()
|
||||||
|
expression.statements.addAll(newBlock.statements)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var tmpVarsCount = 0
|
||||||
|
|
||||||
|
private fun createTemporaryVar(initializer: IrExpression): IrVariable {
|
||||||
|
val descriptor = WrappedVariableDescriptor()
|
||||||
|
val symbol = IrVariableSymbolImpl(descriptor)
|
||||||
|
val irVar = IrVariableImpl(
|
||||||
|
initializer.startOffset, initializer.endOffset,
|
||||||
|
IrDeclarationOrigin.IR_TEMPORARY_VARIABLE,
|
||||||
|
symbol,
|
||||||
|
Name.identifier("tmp_f_${tmpVarsCount++}"),
|
||||||
|
initializer.type,
|
||||||
|
isVar = false, isConst = false, isLateinit = false
|
||||||
|
)
|
||||||
|
irVar.initializer = initializer
|
||||||
|
descriptor.bind(irVar)
|
||||||
|
return irVar
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun simplifyIfNull(
|
||||||
|
subjectVariable: IrVariable,
|
||||||
|
ifNullExpr: IrExpression,
|
||||||
|
ifNotNullExpr: IrExpression,
|
||||||
|
knownVariableSymbol: IrVariableSymbol,
|
||||||
|
knownVariableIsNull: Boolean
|
||||||
|
): IrExpression {
|
||||||
|
val subjectExpr = subjectVariable.initializer
|
||||||
|
?: throw AssertionError("Subject variable should have an initializer: ${subjectVariable.render()}")
|
||||||
|
|
||||||
|
val ifNullResultExpr = ifNullExpr.safeReplaceSubjectVariableWithSubjectExpression(subjectVariable)
|
||||||
|
?: return constructIfNullExpr(subjectVariable, ifNullExpr, ifNotNullExpr)
|
||||||
|
|
||||||
|
val ifNotNullResultExpr = ifNotNullExpr.safeReplaceSubjectVariableWithSubjectExpression(subjectVariable)
|
||||||
|
?: return constructIfNullExpr(subjectVariable, ifNullExpr, ifNotNullExpr)
|
||||||
|
|
||||||
|
return when {
|
||||||
|
subjectExpr is IrConst<*> ->
|
||||||
|
if (subjectExpr.value == null)
|
||||||
|
ifNullResultExpr
|
||||||
|
else
|
||||||
|
ifNotNullResultExpr
|
||||||
|
|
||||||
|
subjectExpr is IrGetValue && subjectExpr.symbol == knownVariableSymbol ->
|
||||||
|
if (knownVariableIsNull)
|
||||||
|
ifNullResultExpr
|
||||||
|
else
|
||||||
|
ifNotNullResultExpr
|
||||||
|
|
||||||
|
subjectExpr is IrGetValue && !subjectExpr.type.isNullable() ->
|
||||||
|
ifNotNullResultExpr
|
||||||
|
|
||||||
|
subjectExpr is IrConstructorCall ||
|
||||||
|
subjectExpr is IrGetSingletonValue ||
|
||||||
|
subjectExpr is IrFunctionExpression ||
|
||||||
|
subjectExpr is IrCallableReference ||
|
||||||
|
subjectExpr is IrClassReference ||
|
||||||
|
subjectExpr is IrGetClass ->
|
||||||
|
ifNotNullResultExpr
|
||||||
|
|
||||||
|
subjectExpr is IrCall && !subjectExpr.type.isNullable() && subjectExpr.isStableCall() ->
|
||||||
|
ifNotNullResultExpr
|
||||||
|
|
||||||
|
else ->
|
||||||
|
constructIfNullExpr(subjectVariable, ifNullExpr, ifNotNullExpr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.safeReplaceSubjectVariableWithSubjectExpression(v: IrVariable): IrExpression? =
|
||||||
|
when {
|
||||||
|
this is IrGetValue && symbol == v.symbol ->
|
||||||
|
v.initializer!!
|
||||||
|
|
||||||
|
this is IrTypeOperatorCall ->
|
||||||
|
argument.safeReplaceSubjectVariableWithSubjectExpression(v)?.let {
|
||||||
|
IrTypeOperatorCallImpl(startOffset, endOffset, type, operator, typeOperand, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.containsGetValue(v) ->
|
||||||
|
null
|
||||||
|
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.containsGetValue(v: IrVariable): Boolean {
|
||||||
|
class Searcher : IrElementVisitorVoid {
|
||||||
|
var found = false
|
||||||
|
|
||||||
|
override fun visitElement(element: IrElement) {
|
||||||
|
element.acceptChildrenVoid(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitGetValue(expression: IrGetValue) {
|
||||||
|
if (expression.symbol == v.symbol)
|
||||||
|
found = true
|
||||||
|
else
|
||||||
|
expression.acceptChildrenVoid(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Searcher().also { acceptChildrenVoid(it) }.found
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make calls to the declarations within the same module "stable"
|
||||||
|
private fun IrCall.isStableCall() =
|
||||||
|
symbol.owner.fileOrNull == currentFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private class IfNullExpr(
|
||||||
|
val subjectVar: IrVariable,
|
||||||
|
val ifNullExpr: IrExpression,
|
||||||
|
val ifNotNullExpr: IrExpression
|
||||||
|
) {
|
||||||
|
val subjectExpr: IrExpression = subjectVar.initializer!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.matchIfNullExpr(): IfNullExpr? {
|
||||||
|
if (this !is IrBlock) return null
|
||||||
|
if (statements.size != 2) return null
|
||||||
|
|
||||||
|
val subjectVar = statements[0] as? IrVariable ?: return null
|
||||||
|
if (subjectVar.isVar) return null
|
||||||
|
if (subjectVar.initializer == null) return null
|
||||||
|
|
||||||
|
val whenExpr = statements[1] as? IrWhen ?: return null
|
||||||
|
if (whenExpr.branches.size != 2) return null
|
||||||
|
|
||||||
|
val branch0 = whenExpr.branches[0]
|
||||||
|
val condition0 = branch0.condition as? IrCall ?: return null
|
||||||
|
if (condition0.symbol != context.irBuiltIns.eqeqSymbol) return null
|
||||||
|
val arg0 = condition0.getValueArgument(0) as? IrGetValue ?: return null
|
||||||
|
if (arg0.symbol != subjectVar.symbol) return null
|
||||||
|
val arg1 = condition0.getValueArgument(1) as? IrConst<*> ?: return null
|
||||||
|
if (arg1.value != null) return null
|
||||||
|
|
||||||
|
val branch1 = whenExpr.branches[1] as? IrElseBranch ?: return null
|
||||||
|
|
||||||
|
return IfNullExpr(
|
||||||
|
subjectVar,
|
||||||
|
branch0.result,
|
||||||
|
branch1.result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.substituteVariable(fromVar: IrVariableSymbol, toVar: IrVariableSymbol): IrExpression {
|
||||||
|
val symbolRemapper = DeepCopySymbolRemapper().also { acceptVoid(it) }
|
||||||
|
val typeRemapper = DeepCopyTypeRemapper(symbolRemapper)
|
||||||
|
|
||||||
|
class Substitutor : DeepCopyIrTreeWithSymbols(symbolRemapper, typeRemapper) {
|
||||||
|
|
||||||
|
override fun getNonTransformedLoop(irLoop: IrLoop): IrLoop = irLoop
|
||||||
|
|
||||||
|
override fun visitGetValue(expression: IrGetValue): IrGetValue =
|
||||||
|
if (expression.symbol == fromVar)
|
||||||
|
IrGetValueImpl(expression.startOffset, expression.endOffset, toVar)
|
||||||
|
else
|
||||||
|
super.visitGetValue(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform(Substitutor(), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun constructIfNullExpr(
|
||||||
|
subjectVariable: IrVariable,
|
||||||
|
ifNullExpr: IrExpression,
|
||||||
|
ifNotNullExpr: IrExpression
|
||||||
|
): IrContainerExpression =
|
||||||
|
context.createIrBuilder(subjectVariable.symbol, subjectVariable.startOffset, subjectVariable.endOffset)
|
||||||
|
.irBlock {
|
||||||
|
+subjectVariable
|
||||||
|
+irIfNull(
|
||||||
|
ifNullExpr.type,
|
||||||
|
irGet(subjectVariable),
|
||||||
|
ifNullExpr,
|
||||||
|
ifNotNullExpr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.size(): Int {
|
||||||
|
class ChildrenCounter : IrElementVisitorVoid {
|
||||||
|
var count = 0
|
||||||
|
override fun visitElement(element: IrElement) {
|
||||||
|
++count
|
||||||
|
element.acceptChildrenVoid(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChildrenCounter().also { acceptVoid(it) }.count
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -353,6 +353,7 @@ private val jvmFilePhases =
|
|||||||
jvmArgumentNullabilityAssertions then
|
jvmArgumentNullabilityAssertions then
|
||||||
toArrayPhase then
|
toArrayPhase then
|
||||||
jvmOptimizationLoweringPhase then
|
jvmOptimizationLoweringPhase then
|
||||||
|
ifNullExpressionsFusionPhase then
|
||||||
additionalClassAnnotationPhase then
|
additionalClassAnnotationPhase then
|
||||||
typeOperatorLowering then
|
typeOperatorLowering then
|
||||||
replaceKFunctionInvokeWithFunctionInvokePhase then
|
replaceKFunctionInvokeWithFunctionInvokePhase then
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ inline fun IrBuilderWithScope.irBlock(
|
|||||||
origin: IrStatementOrigin? = null,
|
origin: IrStatementOrigin? = null,
|
||||||
resultType: IrType? = null,
|
resultType: IrType? = null,
|
||||||
body: IrBlockBuilder.() -> Unit
|
body: IrBlockBuilder.() -> Unit
|
||||||
): IrExpression =
|
): IrContainerExpression =
|
||||||
IrBlockBuilder(
|
IrBlockBuilder(
|
||||||
context, scope,
|
context, scope,
|
||||||
startOffset,
|
startOffset,
|
||||||
|
|||||||
+5
-3
@@ -1,6 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TODO KT-36651 Avoid boxing in safe call / elvis chains in JVM_IR
|
|
||||||
|
|
||||||
class A(val x : Int, val y : A?)
|
class A(val x : Int, val y : A?)
|
||||||
|
|
||||||
fun check(a : A?) : Int {
|
fun check(a : A?) : Int {
|
||||||
@@ -9,4 +6,9 @@ fun check(a : A?) : Int {
|
|||||||
|
|
||||||
// 0 valueOf
|
// 0 valueOf
|
||||||
// 0 Value\s\(\)
|
// 0 Value\s\(\)
|
||||||
|
|
||||||
|
// JVM_TEMPLATES:
|
||||||
// 0 ACONST_NULL
|
// 0 ACONST_NULL
|
||||||
|
|
||||||
|
// JVM_IR_TEMPLATES:
|
||||||
|
// 1 ACONST_NULL
|
||||||
|
|||||||
Reference in New Issue
Block a user