JVM_IR: Use direct field access instead of calling certain accessors.

Final default properties accessors that access a backing field
on the same class can be replaced by direct field use.

Perform the optimization late in the pipeline to allow lowerings
to expose more opportunities for optimizations.
This commit is contained in:
Mads Ager
2020-01-27 13:13:32 +01:00
committed by max-kammerer
parent 09057c485b
commit d68a1898d0
8 changed files with 103 additions and 51 deletions
@@ -314,10 +314,9 @@ private val jvmFilePhases =
staticDefaultFunctionPhase then
syntheticAccessorPhase then
jvmArgumentNullabilityAssertions then
toArrayPhase then
jvmBuiltinOptimizationLoweringPhase then
jvmOptimizationLoweringPhase then
additionalClassAnnotationPhase then
typeOperatorLowering then
replaceKFunctionInvokeWithFunctionInvokePhase then
@@ -6,15 +6,17 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlock
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.codegen.intrinsics.Not
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.builders.irGetField
import org.jetbrains.kotlin.ir.builders.irSetField
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
@@ -24,16 +26,15 @@ import org.jetbrains.kotlin.ir.types.isNullableAny
import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
internal val jvmBuiltinOptimizationLoweringPhase = makeIrFilePhase(
::JvmBuiltinOptimizationLowering,
internal val jvmOptimizationLoweringPhase = makeIrFilePhase(
::JvmOptimizationLowering,
name = "JvmBuiltinOptimizationLowering",
description = "Optimize builtin calls for JVM code generation"
description = "Optimize code for JVM code generation"
)
class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass {
class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass {
companion object {
fun isNegation(expression: IrExpression, context: JvmBackendContext): Boolean =
@@ -71,9 +72,38 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
}
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this)
val transformer = object : IrElementTransformer<IrClass?> {
// Thread the current class through the transformations in order to replace
// final default accessor calls with direct backing field access when
// possible.
override fun visitClass(declaration: IrClass, data: IrClass?): IrStatement {
declaration.transformChildren(this, declaration)
return declaration
}
// For some functions, we clear the current class field since the code could end up
// in another class then the one it is nested under in the IR.
// TODO: Loosen this up for local functions for lambdas passed as an inline lambda
// argument to an inline function. In that case the code does end up in the current class.
override fun visitFunction(declaration: IrFunction, currentClass: IrClass?): IrStatement {
val codeMightBeGeneratedInDifferentClass = declaration.isSuspend ||
declaration.isInline ||
declaration.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
declaration.transformChildren(this, currentClass.takeUnless { codeMightBeGeneratedInDifferentClass })
return declaration
}
override fun visitCall(expression: IrCall, currentClass: IrClass?): IrExpression {
expression.transformChildren(this, currentClass)
if (expression.symbol.owner.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) {
if (currentClass == null) return expression
val simpleFunction = (expression.symbol.owner as? IrSimpleFunction) ?: return expression
val property = simpleFunction.correspondingPropertySymbol?.owner ?: return expression
if (property.isLateinit) return expression
return optimizePropertyAccess(expression, simpleFunction, property, currentClass)
}
if (isNegation(expression, context) && isNegation(expression.dispatchReceiver!!, context)) {
return (expression.dispatchReceiver as IrCall).dispatchReceiver!!
@@ -115,9 +145,41 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
return expression
}
override fun visitWhen(expression: IrWhen): IrExpression {
private fun optimizePropertyAccess(
expression: IrCall,
accessor: IrSimpleFunction,
property: IrProperty,
currentClass: IrClass
): IrExpression {
if (accessor.parentAsClass == currentClass &&
property.backingField?.parentAsClass == currentClass &&
accessor.modality == Modality.FINAL &&
!accessor.isExternal
) {
val backingField = property.backingField!!
val receiver = expression.dispatchReceiver
return context.createIrBuilder(expression.symbol, expression.startOffset, expression.endOffset).irBlock(expression) {
if (backingField.isStatic && receiver != null) {
// If the field is static, evaluate the receiver for potential side effects.
+receiver.coerceToUnit(context.irBuiltIns)
}
if (accessor.valueParameters.size > 0) {
+irSetField(
receiver.takeUnless { backingField.isStatic },
backingField,
expression.getValueArgument(expression.valueArgumentsCount - 1)!!
)
} else {
+irGetField(receiver.takeUnless { backingField.isStatic }, backingField)
}
}
}
return expression
}
override fun visitWhen(expression: IrWhen, currentClass: IrClass?): IrExpression {
val isCompilerGenerated = expression.origin == null
expression.transformChildrenVoid(this)
expression.transformChildren(this, currentClass)
// Remove all branches with constant false condition.
expression.branches.removeIf {
it.condition.isFalseConst() && isCompilerGenerated
@@ -259,19 +321,19 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
}
}
override fun visitBlockBody(body: IrBlockBody): IrBody {
body.transformChildrenVoid(this)
override fun visitBlockBody(body: IrBlockBody, currentClass: IrClass?): IrBody {
body.transformChildren(this, currentClass)
removeUnnecessaryTemporaryVariables(body.statements)
return body
}
override fun visitContainerExpression(expression: IrContainerExpression): IrExpression {
expression.transformChildrenVoid(this)
override fun visitContainerExpression(expression: IrContainerExpression, currentClass: IrClass?): IrExpression {
expression.transformChildren(this, currentClass)
removeUnnecessaryTemporaryVariables(expression.statements)
return expression
}
override fun visitGetValue(expression: IrGetValue): IrExpression {
override fun visitGetValue(expression: IrGetValue, currentClass: IrClass?): IrExpression {
// Replace IrGetValue of an immutable temporary variable with a constant
// initializer with the constant initializer.
val variable = expression.symbol.owner
@@ -280,6 +342,7 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
else
expression
}
})
}
irFile.transformChildren(transformer, null)
}
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.ir.expressions.IrFieldAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.types.makeNotNull
import org.jetbrains.kotlin.ir.util.coerceToUnit
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -129,16 +130,7 @@ class PropertiesToFieldsLowering(val context: CommonBackendContext) : IrElementT
if (receiver != null && needBlock) {
// Evaluate `dispatchReceiver` for the sake of its side effects, then return `setOrGetExpr`.
return context.createIrBuilder(setOrGetExpr.symbol, setOrGetExpr.startOffset, setOrGetExpr.endOffset).irBlock(setOrGetExpr) {
// `coerceToUnit()` is private in InsertImplicitCasts, have to reproduce it here
val receiverVoid = IrTypeOperatorCallImpl(
receiver.startOffset, receiver.endOffset,
context.irBuiltIns.unitType,
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT,
context.irBuiltIns.unitType,
receiver
)
+receiverVoid
+receiver.coerceToUnit(context.irBuiltIns)
setOrGetExpr.receiver = null
+setOrGetExpr
}
@@ -37,7 +37,7 @@ import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.impl.originalKotlinType
import org.jetbrains.kotlin.ir.util.TypeTranslator
import org.jetbrains.kotlin.ir.util.coerceToUnitIfNeeded
import org.jetbrains.kotlin.ir.util.coerceToUnit
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.psi2ir.containsNull
@@ -147,7 +147,7 @@ internal class InsertImplicitCasts(
body.transformPostfix {
statements.forEachIndexed { i, irStatement ->
if (irStatement is IrExpression) {
body.statements[i] = irStatement.coerceToUnit()
body.statements[i] = irStatement.coerceToUnit(irBuiltIns)
}
}
}
@@ -163,7 +163,7 @@ internal class InsertImplicitCasts(
if (i == lastIndex)
irStatement.cast(type)
else
irStatement.coerceToUnit()
irStatement.coerceToUnit(irBuiltIns)
}
}
}
@@ -171,7 +171,7 @@ internal class InsertImplicitCasts(
override fun visitReturn(expression: IrReturn): IrExpression =
expression.transformPostfix {
value = if (expression.returnTargetSymbol is IrConstructorSymbol) {
value.coerceToUnit()
value.coerceToUnit(irBuiltIns)
} else {
val returnTargetDescriptor = expression.returnTarget
val isLambdaReturnValue = returnTargetDescriptor is AnonymousFunctionDescriptor
@@ -233,7 +233,7 @@ internal class InsertImplicitCasts(
override fun visitLoop(loop: IrLoop): IrExpression =
loop.transformPostfix {
condition = condition.cast(builtIns.booleanType)
body = body?.coerceToUnit()
body = body?.coerceToUnit(irBuiltIns)
}
override fun visitThrow(expression: IrThrow): IrExpression =
@@ -249,7 +249,7 @@ internal class InsertImplicitCasts(
aCatch.result = aCatch.result.cast(type)
}
finallyExpression = finallyExpression?.coerceToUnit()
finallyExpression = finallyExpression?.coerceToUnit(irBuiltIns)
}
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression =
@@ -318,7 +318,7 @@ internal class InsertImplicitCasts(
return when {
expectedType.isUnit() ->
coerceToUnit()
coerceToUnit(irBuiltIns)
valueType.isDynamic() && !expectedType.isDynamic() ->
if (expectedType.isNullableAny())
@@ -383,14 +383,6 @@ internal class InsertImplicitCasts(
)
}
private fun IrExpression.coerceToUnit(): IrExpression {
val valueType = getKotlinType(this)
return coerceToUnitIfNeeded(valueType, irBuiltIns)
}
private fun getKotlinType(irExpression: IrExpression) =
irExpression.type.originalKotlinType!!
private fun KotlinType.isBuiltInIntegerType(): Boolean =
KotlinBuiltIns.isByte(this) ||
KotlinBuiltIns.isShort(this) ||
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.originalKotlinType
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.psi.psiUtil.endOffset
@@ -154,6 +155,14 @@ fun IrExpression.isTrueConst() = this is IrConst<*> && this.kind == IrConstKind.
fun IrExpression.isFalseConst() = this is IrConst<*> && this.kind == IrConstKind.Boolean && this.value == false
fun IrExpression.coerceToUnit(builtins: IrBuiltIns): IrExpression {
val valueType = getKotlinType(this)
return coerceToUnitIfNeeded(valueType, builtins)
}
private fun getKotlinType(irExpression: IrExpression) =
irExpression.type.toKotlinType()
fun IrExpression.coerceToUnitIfNeeded(valueType: KotlinType, irBuiltIns: IrBuiltIns): IrExpression {
return if (KotlinTypeChecker.DEFAULT.isSubtypeOf(valueType, irBuiltIns.unitType.toKotlinType()))
this
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
package b
abstract class B {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
val x = 1
class A {
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
class Example
{
var a1 = 0