diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index df397c41160..749320eafda 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -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 diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmBuiltinOptimizationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmOptimizationLowering.kt similarity index 72% rename from compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmBuiltinOptimizationLowering.kt rename to compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmOptimizationLowering.kt index 66373d75973..d547596abb7 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmBuiltinOptimizationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmOptimizationLowering.kt @@ -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 { + + // 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) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertiesToFieldsLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertiesToFieldsLowering.kt index e39973e86c0..c1417840ef3 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertiesToFieldsLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertiesToFieldsLowering.kt @@ -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 } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InsertImplicitCasts.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InsertImplicitCasts.kt index 2436d156a16..16160c94f09 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InsertImplicitCasts.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InsertImplicitCasts.kt @@ -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) || diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt index 5d7db2bc5d9..54799e11fef 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/accessorForOverridenVal.kt b/compiler/testData/codegen/bytecodeText/accessorForOverridenVal.kt index 68228903fc5..4a3016e4743 100644 --- a/compiler/testData/codegen/bytecodeText/accessorForOverridenVal.kt +++ b/compiler/testData/codegen/bytecodeText/accessorForOverridenVal.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR package b abstract class B { diff --git a/compiler/testData/codegen/bytecodeText/constants/noInlineNonConst.kt b/compiler/testData/codegen/bytecodeText/constants/noInlineNonConst.kt index 22d136b4577..b505c039a64 100644 --- a/compiler/testData/codegen/bytecodeText/constants/noInlineNonConst.kt +++ b/compiler/testData/codegen/bytecodeText/constants/noInlineNonConst.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR val x = 1 class A { diff --git a/compiler/testData/codegen/bytecodeText/kt3845.kt b/compiler/testData/codegen/bytecodeText/kt3845.kt index 6c5ecb66962..be2bdbaf62d 100644 --- a/compiler/testData/codegen/bytecodeText/kt3845.kt +++ b/compiler/testData/codegen/bytecodeText/kt3845.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR class Example { var a1 = 0