JVM_IR: avoid copying when optimizing ?:/?.

This commit is contained in:
pyos
2020-04-16 16:18:50 +02:00
committed by Dmitry Petrov
parent 2307692064
commit 887165a23b
@@ -8,25 +8,15 @@ package org.jetbrains.kotlin.backend.common.lower
import org.jetbrains.kotlin.backend.common.CommonBackendContext import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.irBlock import org.jetbrains.kotlin.ir.declarations.*
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.*
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.* import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.name.Name
val ifNullExpressionsFusionPhase = val ifNullExpressionsFusionPhase =
makeIrFilePhase( makeIrFilePhase(
@@ -36,17 +26,13 @@ val ifNullExpressionsFusionPhase =
) )
class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileLoweringPass { class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileLoweringPass {
override fun lower(irFile: IrFile) { override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(Transformer(irFile)) irFile.transformChildrenVoid(Transformer(irFile))
irFile.patchDeclarationParents()
} }
private inner class Transformer(private val currentFile: IrFile) : IrElementTransformerVoid() { private inner class Transformer(private val currentFile: IrFile) : IrElementTransformerVoid() {
override fun visitBlock(expression: IrBlock): IrExpression = override fun visitBlock(expression: IrBlock): IrExpression =
expression.transformPostfix { visitExpression(expression.fuseIfNull())
fuseIfNullExpressions(expression)
}
// We are looking for the "if-null" expressions: // We are looking for the "if-null" expressions:
// //
@@ -72,13 +58,11 @@ class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileL
// where 'SIMPLIFY_IF_NULL( S, v, X0, X1; Context )' attempts to simplify 'IfNull( S, v, X0, X1 )' in Context, // 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. // 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. // Note that this transformation in general is not beneficial, and will in fact lead to duplication. However,
// However, the structure of argument expressions in case of '?.' and '?:' operators often allows reductions in SIMPLIFY_IF_NULL. // the structure of argument expressions in case of '?.' and '?:' operators often allows reductions in SIMPLIFY_IF_NULL.
// This transformation is applied only if // This transformation is applied only if it is not necessary to copy any non-trivial subexpression:
// size(A0) + size(A1) + size(B0) + size(B1) + 1 >= size(C0) + size(C1) // if B1 is trivial or either A0 or A1 is always null, and B0 is trivial or either A0 or A1 is never null.
// where // ("Trivial" is not precisely defined, but a trivial expression should have no side effects and be reasonably small.)
// size(X) is number of nodes in IR tree for X,
// thus assuring that we don't grow trees exponentially.
// //
// Example: // Example:
// //
@@ -94,150 +78,67 @@ class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileL
// ), // ),
// t0, 'z', 't0' // t0, 'z', 't0'
// ) // )
// which, assuming 'a.x' and 'b.y' are stable not-null expressions, is optimized to // which, assuming 'a!!.x' and 'b!!.y' are stable not-null expressions, is optimized to
// IfNull( // IfNull(
// 'a', t1, // 'a', t1,
// IfNull( 'b', t2, 'z', 't2.y' ), // IfNull( 'b', t2, 'z', 't2.y' ),
// 't1.x' // 't1.x'
// ) // )
// by applying FUSE_IF_NULL twice. // by applying FUSE_IF_NULL twice: in `a?.x ?: [...]` A0 is non-null and A1 is null, while in `[...] ?: z`
// // A1 is non-null and B1 is trivial (read of a temporary variable that stores the subject of the branch).
private fun fuseIfNullExpressions(expression: IrBlock) { private fun IrBlock.fuseIfNull(): IrExpression {
val ifNull1 = expression.matchIfNullExpr() ?: return val outer = matchIfNullExpr() ?: return this
val ifNull2 = ifNull1.subjectExpr.matchIfNullExpr() ?: return
val type = expression.type
val u = ifNull1.subjectVar
// We are going to erase 1st variable. Do so only if it is temporary (true for variables introduced for '?.' and '?:'). // 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 if (outer.subjectVar.origin != IrDeclarationOrigin.IR_TEMPORARY_VARIABLE) return this
val inner = outer.subjectVar.initializer?.matchIfNullExpr() ?: return this
val a0 = ifNull2.ifNullExpr val innerKeepsNull = inner.ifNullExpr.isNull(inner.subjectVar.symbol, true)
val a1 = ifNull2.ifNotNullExpr val innerDiscardsNonNull = inner.ifNotNullExpr.isNull(inner.subjectVar.symbol, false)
val tmp1 = createTemporaryVar(a0) if ((!outer.ifNotNullExpr.isTrivial() && innerKeepsNull != true && innerDiscardsNonNull != true) ||
val tmp2 = createTemporaryVar(a1) (!outer.ifNullExpr.isTrivial() && innerKeepsNull != false && innerDiscardsNonNull != false)
) return this
val b0 = ifNull1.ifNullExpr return inner.createIrBuilder().irBlock {
val b0tmp1 = b0.substituteVariable(u.symbol, tmp1.symbol) val ifNull = outer.substitute(inner.ifNullExpr, innerKeepsNull)
val b0tmp2 = b0.substituteVariable(u.symbol, tmp2.symbol) val ifNotNull = outer.substitute(inner.ifNotNullExpr, innerDiscardsNonNull)
+inner.subjectVar
val b1 = ifNull1.ifNotNullExpr +irIfNull(outer.type, irGet(inner.subjectVar), ifNull, ifNotNull)
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, type, true)
val c1 = simplifyIfNull(tmp2, b0tmp2, b1tmp2, v.symbol, type, 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, type)
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,
type: IrType,
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, type)
val ifNotNullResultExpr = ifNotNullExpr.safeReplaceSubjectVariableWithSubjectExpression(subjectVariable)
?: return constructIfNullExpr(subjectVariable, ifNullExpr, ifNotNullExpr, type)
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, type)
} }
} }
private fun IrExpression.safeReplaceSubjectVariableWithSubjectExpression(v: IrVariable): IrExpression? = private fun IfNullExpr.substitute(subject: IrExpression, knownNullability: Boolean?): IrExpression =
when { when (knownNullability) {
this is IrGetValue && symbol == v.symbol -> null -> createIrBuilder().irBlock {
v.initializer!! val tmp = createTmpVariable(subject)
val ifNull = ifNullExpr.remap(subjectVar, lazy { tmp })
this is IrTypeOperatorCall -> val ifNotNull = ifNotNullExpr.remap(subjectVar, lazy { tmp })
argument.safeReplaceSubjectVariableWithSubjectExpression(v)?.let { +irIfNull(type, irGet(tmp), ifNull, ifNotNull)
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)
} }
true -> ifNullExpr.remap(subjectVar, subject)
else -> ifNotNullExpr.remap(subjectVar, subject)
} }
return Searcher().also { acceptChildrenVoid(it) }.found private fun IfNullExpr.createIrBuilder() =
context.createIrBuilder((subjectVar.parent as IrSymbolOwner).symbol, subjectVar.startOffset, subjectVar.endOffset)
private fun IrExpression.isNull(knownVariableSymbol: IrVariableSymbol, knownVariableIsNull: Boolean): Boolean? {
when (this) {
is IrConst<*> ->
return value == null
is IrGetValue -> {
if (symbol == knownVariableSymbol) return knownVariableIsNull
if (!type.isNullable()) return false
}
is IrConstructorCall,
is IrGetSingletonValue,
is IrFunctionExpression,
is IrCallableReference,
is IrClassReference,
is IrGetClass ->
return false
is IrCall ->
if (!type.isNullable() && isStableCall()) return false
}
return null
} }
// TODO make calls to the declarations within the same module "stable" // TODO make calls to the declarations within the same module "stable"
@@ -246,12 +147,11 @@ class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileL
} }
private class IfNullExpr( private class IfNullExpr(
val type: IrType,
val subjectVar: IrVariable, val subjectVar: IrVariable,
val ifNullExpr: IrExpression, val ifNullExpr: IrExpression,
val ifNotNullExpr: IrExpression val ifNotNullExpr: IrExpression
) { )
val subjectExpr: IrExpression = subjectVar.initializer!!
}
private fun IrExpression.matchIfNullExpr(): IfNullExpr? { private fun IrExpression.matchIfNullExpr(): IfNullExpr? {
if (this !is IrBlock) return null if (this !is IrBlock) return null
@@ -274,57 +174,30 @@ class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileL
val branch1 = whenExpr.branches[1] as? IrElseBranch ?: return null val branch1 = whenExpr.branches[1] as? IrElseBranch ?: return null
return IfNullExpr( return IfNullExpr(whenExpr.type, subjectVar, branch0.result, branch1.result)
subjectVar,
branch0.result,
branch1.result
)
} }
private fun IrExpression.substituteVariable(fromVar: IrVariableSymbol, toVar: IrVariableSymbol): IrExpression { private fun IrExpression.isTrivial() =
val symbolRemapper = DeepCopySymbolRemapper().also { acceptVoid(it) } this is IrExpressionWithCopy
val typeRemapper = DeepCopyTypeRemapper(symbolRemapper)
class Substitutor : DeepCopyIrTreeWithSymbols(symbolRemapper, typeRemapper) { private fun IrExpression.copyIfTrivial() =
if (this is IrExpressionWithCopy) copy() else this
override fun getNonTransformedLoop(irLoop: IrLoop): IrLoop = irLoop private fun IrExpression.remap(from: IrVariable, to: Lazy<IrVariable>): IrExpression =
copyIfTrivial().transform(object : AbstractVariableRemapper() {
override fun remapVariable(value: IrValueDeclaration): IrValueDeclaration? =
if (value.symbol == from.symbol) to.value else null
}, null)
override fun visitGetValue(expression: IrGetValue): IrGetValue = private fun IrExpression.remap(from: IrVariable, to: IrExpression): IrExpression =
if (expression.symbol == fromVar) when {
IrGetValueImpl(expression.startOffset, expression.endOffset, toVar) this is IrGetValue && symbol == from.symbol -> to
else this is IrTypeOperatorCall ->
super.visitGetValue(expression) IrTypeOperatorCallImpl(startOffset, endOffset, type, operator, typeOperand, argument.remap(from, to))
// TODO other expressions may not require a temporary even if they read the variable.
else ->
context.createIrBuilder((from.parent as IrSymbolOwner).symbol, startOffset, endOffset).irBlock {
+remap(from, lazy { createTmpVariable(to) })
}.let { it.statements.singleOrNull() as IrExpression? ?: it }
} }
}
return transform(Substitutor(), null)
}
private fun constructIfNullExpr(
subjectVariable: IrVariable,
ifNullExpr: IrExpression,
ifNotNullExpr: IrExpression,
type: IrType
): IrContainerExpression =
context.createIrBuilder(subjectVariable.symbol, subjectVariable.startOffset, subjectVariable.endOffset)
.irBlock {
+subjectVariable
+irIfNull(
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
}
}