JVM_IR: optimize out redundant delegated property receiver fields

Now this:

    class C {
        val x = something
        val y by x::property
    }

is *exactly* the same as this:

    class C {
        val x = something
        val y get() = x.property
    }

(plus a `getY$delegate` method)
This commit is contained in:
pyos
2021-07-02 12:35:02 +02:00
committed by Alexander Udalov
parent 2fe7cf27ad
commit d988853c11
11 changed files with 175 additions and 17 deletions
@@ -13216,12 +13216,24 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherReflection.kt");
}
@Test
@TestMetadata("delegateToAnotherWithSideEffects.kt")
public void testDelegateToAnotherWithSideEffects() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherWithSideEffects.kt");
}
@Test
@TestMetadata("delegateToGenericJavaProperty.kt")
public void testDelegateToGenericJavaProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToGenericJavaProperty.kt");
}
@Test
@TestMetadata("delegateToOpenProperty.kt")
public void testDelegateToOpenProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToOpenProperty.kt");
}
@Test
@TestMetadata("delegateWithPrivateSet.kt")
public void testDelegateWithPrivateSet() throws Exception {
@@ -9,20 +9,20 @@ import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.fileParent
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
import org.jetbrains.kotlin.backend.jvm.lower.JvmPropertiesLowering.Companion.createSyntheticMethodForPropertyDelegate
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.buildField
import org.jetbrains.kotlin.ir.builders.declarations.buildVariable
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrPropertyReferenceImpl
import org.jetbrains.kotlin.ir.util.getPackageFragment
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.util.transformDeclarationsFlat
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.name.Name
@@ -39,14 +39,15 @@ private class PropertyReferenceDelegationLowering(val context: JvmBackendContext
}
private class PropertyReferenceDelegationTransformer(val context: JvmBackendContext) : IrElementTransformerVoid() {
private fun IrSimpleFunction.accessorBody(delegate: IrPropertyReference, receiverField: IrDeclaration?) =
private fun IrSimpleFunction.accessorBody(delegate: IrPropertyReference, receiverFieldOrExpression: IrStatement?) =
context.createIrBuilder(symbol, startOffset, endOffset).run {
val value = valueParameters.singleOrNull()?.let(::irGet)
var boundReceiver = when (receiverField) {
var boundReceiver = when (receiverFieldOrExpression) {
null -> null
is IrField -> irGetField(dispatchReceiverParameter?.let(::irGet), receiverField)
is IrValueDeclaration -> irGet(receiverField)
else -> throw AssertionError("not a field/variable: ${receiverField.render()}")
is IrField -> irGetField(dispatchReceiverParameter?.let(::irGet), receiverFieldOrExpression)
is IrValueDeclaration -> irGet(receiverFieldOrExpression)
is IrExpression -> receiverFieldOrExpression
else -> throw AssertionError("not a field/variable/expression: ${receiverFieldOrExpression.render()}")
}
val unboundReceiver = extensionReceiverParameter ?: dispatchReceiverParameter
val field = delegate.field?.owner
@@ -84,6 +85,35 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont
private val IrStatement.isStdlibCall: Boolean
get() = this is IrCall && symbol.owner.getPackageFragment()?.fqName == StandardNames.BUILT_INS_PACKAGE_FQ_NAME
// Constants, object accesses, reads of immutable variables, and reads of immutable properties in the same file
// don't need to be cached in a field; reads of mutable properties have to be as we should ignore further assignments
// to those, and immutable properties in other files might become mutable without breaking ABI.
private fun IrExpression.canInline(currentFile: IrFile): Boolean = when (this) {
is IrGetValue -> !symbol.owner.let { it is IrVariable && it.isVar }
is IrGetField -> symbol.owner.let { it.isFinal && it.fileParent == currentFile } && receiver?.canInline(currentFile) != false
is IrCall -> symbol.owner.let { it.isFinalDefaultValGetter && it.fileParent == currentFile } &&
dispatchReceiver?.canInline(currentFile) != false && extensionReceiver?.canInline(currentFile) != false
else -> isTrivial()
}
private val IrSimpleFunction.isFinalDefaultValGetter: Boolean
get() = origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR &&
correspondingPropertySymbol?.let { it.owner.getter == this && it.owner.setter == null } == true &&
modality == Modality.FINAL
private fun IrExpression.inline(oldReceiver: IrValueParameter?, newReceiver: IrValueParameter?): IrExpression = when (this) {
is IrGetField ->
IrGetFieldImpl(startOffset, endOffset, symbol, type, receiver?.inline(oldReceiver, newReceiver), origin, superQualifierSymbol)
is IrGetValue ->
IrGetValueImpl(startOffset, endOffset, type, newReceiver?.symbol.takeIf { symbol == oldReceiver?.symbol } ?: symbol, origin)
is IrCall ->
IrCallImpl(startOffset, endOffset, type, symbol, typeArgumentsCount, valueArgumentsCount, origin, superQualifierSymbol).apply {
dispatchReceiver = this@inline.dispatchReceiver?.inline(oldReceiver, newReceiver)
extensionReceiver = this@inline.extensionReceiver?.inline(oldReceiver, newReceiver)
}
else -> shallowCopy()
}
override fun visitClass(declaration: IrClass): IrStatement {
declaration.transformChildren(this, null)
declaration.transformDeclarationsFlat {
@@ -102,23 +132,27 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont
setter?.returnsResultOfStdlibCall == false
) return null
backingField = (delegate.dispatchReceiver ?: delegate.extensionReceiver)?.let { receiver ->
val receiver = (delegate.dispatchReceiver ?: delegate.extensionReceiver)
?.transform(this@PropertyReferenceDelegationTransformer, null)
backingField = receiver?.takeIf { !it.canInline(fileParent) }?.let {
context.irFactory.buildField {
updateFrom(oldField)
name = Name.identifier("${this@transform.name}\$receiver")
type = receiver.type
}.apply {
parent = oldField.parent
initializer = context.irFactory.createExpressionBody(receiver.transform(this@PropertyReferenceDelegationTransformer, null))
initializer = context.irFactory.createExpressionBody(it)
}
}
getter?.apply { body = accessorBody(delegate, backingField) }
setter?.apply { body = accessorBody(delegate, backingField) }
val originalThis = parentAsClass.thisReceiver
getter?.apply { body = accessorBody(delegate, backingField ?: receiver?.inline(originalThis, dispatchReceiverParameter)) }
setter?.apply { body = accessorBody(delegate, backingField ?: receiver?.inline(originalThis, dispatchReceiverParameter)) }
val delegateMethod = context.createSyntheticMethodForPropertyDelegate(this).apply {
body = context.createJvmIrBuilder(symbol).run {
val propertyOwner = if (getter?.dispatchReceiverParameter != null) irGet(valueParameters[0]) else null
val boundReceiver = backingField?.let { irGetField(propertyOwner, it) }
val propertyOwner = if (getter?.dispatchReceiverParameter != null) valueParameters[0] else null
val boundReceiver = backingField?.let { irGetField(propertyOwner?.let(::irGet), it) }
?: receiver?.inline(originalThis, propertyOwner)
irExprBody(with(delegate) {
val origin = PropertyReferenceLowering.REFLECTED_PROPERTY_REFERENCE
IrPropertyReferenceImpl(startOffset, endOffset, type, symbol, typeArgumentsCount, field, getter, setter, origin)
@@ -130,7 +164,14 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont
})
}
}
return listOf(this, delegateMethod)
// When the receiver is inlined, it can have side effects in form of class initialization, so it should be evaluated here.
val receiverBlock = receiver.takeIf { backingField == null }?.let {
val symbol = IrAnonymousInitializerSymbolImpl(parentAsClass.symbol)
context.irFactory.createAnonymousInitializer(it.startOffset, it.endOffset, IrDeclarationOrigin.DEFINED, symbol).apply {
body = context.irFactory.createBlockBody(startOffset, endOffset, listOf(it.inline(null, null)))
}
}
return listOfNotNull(this, delegateMethod, receiverBlock)
}
override fun visitLocalDelegatedProperty(declaration: IrLocalDelegatedProperty): IrStatement {
@@ -140,6 +181,7 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont
declaration.setter?.returnsResultOfStdlibCall == false
) return super.visitLocalDelegatedProperty(declaration)
// Variables are cheap, so optimizing them out is not really necessary.
val receiver = (delegate.dispatchReceiver ?: delegate.extensionReceiver)?.let { receiver ->
with(declaration.delegate) { buildVariable(parent, startOffset, endOffset, origin, name, receiver.type) }.apply {
initializer = receiver.transform(this@PropertyReferenceDelegationTransformer, null)
@@ -0,0 +1,16 @@
// WITH_RUNTIME
var result = "Fail"
object O {
val z = 42
init { result = "OK" }
}
class A {
val x by O::z
}
fun box(): String {
A()
return result
}
@@ -0,0 +1,23 @@
// WITH_RUNTIME
val String.foo: String
get() = this
abstract class A {
abstract val x: String
val y by x::foo
}
var storage = "OK"
class B : A() {
override var x: String
get() = storage
set(value) { storage = value }
}
fun box(): String {
val b = B()
b.x = "fail"
return b.y
}
@@ -33,6 +33,7 @@ fun local() {
// Optimized all to direct accesses, with `$delegate` methods generating reflected references on demand:
// 0 extends kotlin/jvm/internal/MutablePropertyReference[0-2]Impl
// 0 private final( static)? Lkotlin/reflect/KMutableProperty[0-2]; [xyz]m?\$delegate
// 2 private final( static)? LC; [xyz]m?\$receiver
// 0 LOCALVARIABLE [xyz]m? Lkotlin/reflect/KMutableProperty[0-2];
// 12 static get[XYZ]m?\$delegate
@@ -13216,12 +13216,24 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherReflection.kt");
}
@Test
@TestMetadata("delegateToAnotherWithSideEffects.kt")
public void testDelegateToAnotherWithSideEffects() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherWithSideEffects.kt");
}
@Test
@TestMetadata("delegateToGenericJavaProperty.kt")
public void testDelegateToGenericJavaProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToGenericJavaProperty.kt");
}
@Test
@TestMetadata("delegateToOpenProperty.kt")
public void testDelegateToOpenProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToOpenProperty.kt");
}
@Test
@TestMetadata("delegateWithPrivateSet.kt")
public void testDelegateWithPrivateSet() throws Exception {
@@ -13216,12 +13216,24 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherReflection.kt");
}
@Test
@TestMetadata("delegateToAnotherWithSideEffects.kt")
public void testDelegateToAnotherWithSideEffects() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherWithSideEffects.kt");
}
@Test
@TestMetadata("delegateToGenericJavaProperty.kt")
public void testDelegateToGenericJavaProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToGenericJavaProperty.kt");
}
@Test
@TestMetadata("delegateToOpenProperty.kt")
public void testDelegateToOpenProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToOpenProperty.kt");
}
@Test
@TestMetadata("delegateWithPrivateSet.kt")
public void testDelegateWithPrivateSet() throws Exception {
@@ -10737,11 +10737,21 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherReflection.kt");
}
@TestMetadata("delegateToAnotherWithSideEffects.kt")
public void testDelegateToAnotherWithSideEffects() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherWithSideEffects.kt");
}
@TestMetadata("delegateToGenericJavaProperty.kt")
public void testDelegateToGenericJavaProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToGenericJavaProperty.kt");
}
@TestMetadata("delegateToOpenProperty.kt")
public void testDelegateToOpenProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToOpenProperty.kt");
}
@TestMetadata("delegateWithPrivateSet.kt")
public void testDelegateWithPrivateSet() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateWithPrivateSet.kt");
@@ -9551,6 +9551,16 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
}
@TestMetadata("delegateToAnotherWithSideEffects.kt")
public void testDelegateToAnotherWithSideEffects() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherWithSideEffects.kt");
}
@TestMetadata("delegateToOpenProperty.kt")
public void testDelegateToOpenProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToOpenProperty.kt");
}
@TestMetadata("delegateWithPrivateSet.kt")
public void testDelegateWithPrivateSet() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateWithPrivateSet.kt");
@@ -8957,6 +8957,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
}
@TestMetadata("delegateToAnotherWithSideEffects.kt")
public void testDelegateToAnotherWithSideEffects() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherWithSideEffects.kt");
}
@TestMetadata("delegateToOpenProperty.kt")
public void testDelegateToOpenProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToOpenProperty.kt");
}
@TestMetadata("delegateWithPrivateSet.kt")
public void testDelegateWithPrivateSet() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateWithPrivateSet.kt");
@@ -8957,6 +8957,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
}
@TestMetadata("delegateToAnotherWithSideEffects.kt")
public void testDelegateToAnotherWithSideEffects() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherWithSideEffects.kt");
}
@TestMetadata("delegateToOpenProperty.kt")
public void testDelegateToOpenProperty() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToOpenProperty.kt");
}
@TestMetadata("delegateWithPrivateSet.kt")
public void testDelegateWithPrivateSet() throws Exception {
runTest("compiler/testData/codegen/box/delegatedProperty/delegateWithPrivateSet.kt");