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:
+12
@@ -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 {
|
||||
|
||||
+59
-17
@@ -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)
|
||||
|
||||
+16
@@ -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
|
||||
}
|
||||
+1
@@ -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
|
||||
|
||||
|
||||
+12
@@ -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 {
|
||||
|
||||
+12
@@ -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 {
|
||||
|
||||
+10
@@ -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");
|
||||
|
||||
js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java
Generated
+10
@@ -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");
|
||||
|
||||
Generated
+10
@@ -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");
|
||||
|
||||
Generated
+10
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user