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 e351d560d6
commit 62f9e7a810
8 changed files with 103 additions and 51 deletions
@@ -314,10 +314,9 @@ private val jvmFilePhases =
staticDefaultFunctionPhase then staticDefaultFunctionPhase then
syntheticAccessorPhase then syntheticAccessorPhase then
jvmArgumentNullabilityAssertions then jvmArgumentNullabilityAssertions then
toArrayPhase then toArrayPhase then
jvmBuiltinOptimizationLoweringPhase then jvmOptimizationLoweringPhase then
additionalClassAnnotationPhase then additionalClassAnnotationPhase then
typeOperatorLowering then typeOperatorLowering then
replaceKFunctionInvokeWithFunctionInvokePhase then replaceKFunctionInvokeWithFunctionInvokePhase then
@@ -6,15 +6,17 @@
package org.jetbrains.kotlin.backend.jvm.lower package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass 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.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.codegen.intrinsics.Not import org.jetbrains.kotlin.codegen.intrinsics.Not
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.builders.irGetField
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.builders.irSetField
import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl 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.isPrimitiveType
import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
internal val jvmBuiltinOptimizationLoweringPhase = makeIrFilePhase( internal val jvmOptimizationLoweringPhase = makeIrFilePhase(
::JvmBuiltinOptimizationLowering, ::JvmOptimizationLowering,
name = "JvmBuiltinOptimizationLowering", 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 { companion object {
fun isNegation(expression: IrExpression, context: JvmBackendContext): Boolean = fun isNegation(expression: IrExpression, context: JvmBackendContext): Boolean =
@@ -71,9 +72,38 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
} }
override fun lower(irFile: IrFile) { override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() { val transformer = object : IrElementTransformer<IrClass?> {
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this) // 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)) { if (isNegation(expression, context) && isNegation(expression.dispatchReceiver!!, context)) {
return (expression.dispatchReceiver as IrCall).dispatchReceiver!! return (expression.dispatchReceiver as IrCall).dispatchReceiver!!
@@ -115,9 +145,41 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
return expression 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 val isCompilerGenerated = expression.origin == null
expression.transformChildrenVoid(this) expression.transformChildren(this, currentClass)
// Remove all branches with constant false condition. // Remove all branches with constant false condition.
expression.branches.removeIf { expression.branches.removeIf {
it.condition.isFalseConst() && isCompilerGenerated it.condition.isFalseConst() && isCompilerGenerated
@@ -259,19 +321,19 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
} }
} }
override fun visitBlockBody(body: IrBlockBody): IrBody { override fun visitBlockBody(body: IrBlockBody, currentClass: IrClass?): IrBody {
body.transformChildrenVoid(this) body.transformChildren(this, currentClass)
removeUnnecessaryTemporaryVariables(body.statements) removeUnnecessaryTemporaryVariables(body.statements)
return body return body
} }
override fun visitContainerExpression(expression: IrContainerExpression): IrExpression { override fun visitContainerExpression(expression: IrContainerExpression, currentClass: IrClass?): IrExpression {
expression.transformChildrenVoid(this) expression.transformChildren(this, currentClass)
removeUnnecessaryTemporaryVariables(expression.statements) removeUnnecessaryTemporaryVariables(expression.statements)
return expression 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 // Replace IrGetValue of an immutable temporary variable with a constant
// initializer with the constant initializer. // initializer with the constant initializer.
val variable = expression.symbol.owner val variable = expression.symbol.owner
@@ -280,6 +342,7 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
else else
expression 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.IrTypeOperator
import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.types.makeNotNull 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.util.hasAnnotation
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -129,16 +130,7 @@ class PropertiesToFieldsLowering(val context: CommonBackendContext) : IrElementT
if (receiver != null && needBlock) { if (receiver != null && needBlock) {
// Evaluate `dispatchReceiver` for the sake of its side effects, then return `setOrGetExpr`. // Evaluate `dispatchReceiver` for the sake of its side effects, then return `setOrGetExpr`.
return context.createIrBuilder(setOrGetExpr.symbol, setOrGetExpr.startOffset, setOrGetExpr.endOffset).irBlock(setOrGetExpr) { return context.createIrBuilder(setOrGetExpr.symbol, setOrGetExpr.startOffset, setOrGetExpr.endOffset).irBlock(setOrGetExpr) {
// `coerceToUnit()` is private in InsertImplicitCasts, have to reproduce it here +receiver.coerceToUnit(context.irBuiltIns)
val receiverVoid = IrTypeOperatorCallImpl(
receiver.startOffset, receiver.endOffset,
context.irBuiltIns.unitType,
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT,
context.irBuiltIns.unitType,
receiver
)
+receiverVoid
setOrGetExpr.receiver = null setOrGetExpr.receiver = null
+setOrGetExpr +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.IrType
import org.jetbrains.kotlin.ir.types.impl.originalKotlinType import org.jetbrains.kotlin.ir.types.impl.originalKotlinType
import org.jetbrains.kotlin.ir.util.TypeTranslator 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.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.psi2ir.containsNull import org.jetbrains.kotlin.psi2ir.containsNull
@@ -147,7 +147,7 @@ internal class InsertImplicitCasts(
body.transformPostfix { body.transformPostfix {
statements.forEachIndexed { i, irStatement -> statements.forEachIndexed { i, irStatement ->
if (irStatement is IrExpression) { 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) if (i == lastIndex)
irStatement.cast(type) irStatement.cast(type)
else else
irStatement.coerceToUnit() irStatement.coerceToUnit(irBuiltIns)
} }
} }
} }
@@ -171,7 +171,7 @@ internal class InsertImplicitCasts(
override fun visitReturn(expression: IrReturn): IrExpression = override fun visitReturn(expression: IrReturn): IrExpression =
expression.transformPostfix { expression.transformPostfix {
value = if (expression.returnTargetSymbol is IrConstructorSymbol) { value = if (expression.returnTargetSymbol is IrConstructorSymbol) {
value.coerceToUnit() value.coerceToUnit(irBuiltIns)
} else { } else {
val returnTargetDescriptor = expression.returnTarget val returnTargetDescriptor = expression.returnTarget
val isLambdaReturnValue = returnTargetDescriptor is AnonymousFunctionDescriptor val isLambdaReturnValue = returnTargetDescriptor is AnonymousFunctionDescriptor
@@ -233,7 +233,7 @@ internal class InsertImplicitCasts(
override fun visitLoop(loop: IrLoop): IrExpression = override fun visitLoop(loop: IrLoop): IrExpression =
loop.transformPostfix { loop.transformPostfix {
condition = condition.cast(builtIns.booleanType) condition = condition.cast(builtIns.booleanType)
body = body?.coerceToUnit() body = body?.coerceToUnit(irBuiltIns)
} }
override fun visitThrow(expression: IrThrow): IrExpression = override fun visitThrow(expression: IrThrow): IrExpression =
@@ -249,7 +249,7 @@ internal class InsertImplicitCasts(
aCatch.result = aCatch.result.cast(type) aCatch.result = aCatch.result.cast(type)
} }
finallyExpression = finallyExpression?.coerceToUnit() finallyExpression = finallyExpression?.coerceToUnit(irBuiltIns)
} }
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression = override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression =
@@ -318,7 +318,7 @@ internal class InsertImplicitCasts(
return when { return when {
expectedType.isUnit() -> expectedType.isUnit() ->
coerceToUnit() coerceToUnit(irBuiltIns)
valueType.isDynamic() && !expectedType.isDynamic() -> valueType.isDynamic() && !expectedType.isDynamic() ->
if (expectedType.isNullableAny()) 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 = private fun KotlinType.isBuiltInIntegerType(): Boolean =
KotlinBuiltIns.isByte(this) || KotlinBuiltIns.isByte(this) ||
KotlinBuiltIns.isShort(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.expressions.impl.IrTypeOperatorCallImpl
import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.* 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.FqName
import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.psi.psiUtil.endOffset 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.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 { fun IrExpression.coerceToUnitIfNeeded(valueType: KotlinType, irBuiltIns: IrBuiltIns): IrExpression {
return if (KotlinTypeChecker.DEFAULT.isSubtypeOf(valueType, irBuiltIns.unitType.toKotlinType())) return if (KotlinTypeChecker.DEFAULT.isSubtypeOf(valueType, irBuiltIns.unitType.toKotlinType()))
this this
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
package b package b
abstract class B { abstract class B {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
val x = 1 val x = 1
class A { class A {
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
class Example class Example
{ {
var a1 = 0 var a1 = 0