JVM_IR: optimize delegation by property references
E.g. a statement like
var x by ::y
is semantically equivalent to
var x
get() = y
set(value) { y = value }
and thus does not need a full property reference object, or even a field
if the receiver is not bound.
#KT-39054 Fixed
#KT-47102 Fixed
This commit is contained in:
+12
@@ -13198,6 +13198,18 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToAnotherMutable.kt")
|
||||
public void testDelegateToAnotherMutable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToGenericJavaProperty.kt")
|
||||
public void testDelegateToGenericJavaProperty() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToGenericJavaProperty.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateWithPrivateSet.kt")
|
||||
public void testDelegateWithPrivateSet() throws Exception {
|
||||
|
||||
+6
@@ -4708,6 +4708,12 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
|
||||
runTest("compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/definedInSources.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToAnother.kt")
|
||||
public void testDelegateToAnother() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("inSeparateModule.kt")
|
||||
public void testInSeparateModule() throws Exception {
|
||||
|
||||
@@ -334,6 +334,7 @@ private val jvmFilePhases = listOf(
|
||||
inlineCallableReferenceToLambdaPhase,
|
||||
functionReferencePhase,
|
||||
suspendLambdaPhase,
|
||||
propertyReferenceDelegationPhase,
|
||||
propertyReferencePhase,
|
||||
arrayConstructorPhase,
|
||||
constPhase1,
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
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.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
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.util.getPackageFragment
|
||||
import org.jetbrains.kotlin.ir.util.render
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
internal val propertyReferenceDelegationPhase = makeIrFilePhase(
|
||||
::PropertyReferenceDelegationLowering,
|
||||
name = "PropertyReferenceDelegation",
|
||||
description = "Optimize `val x by ::y`: there is no need to construct a KProperty instance"
|
||||
)
|
||||
|
||||
private class PropertyReferenceDelegationLowering(val context: JvmBackendContext) : FileLoweringPass {
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.transform(PropertyReferenceDelegationTransformer(context), null)
|
||||
}
|
||||
}
|
||||
|
||||
private class PropertyReferenceDelegationTransformer(val context: JvmBackendContext) : IrElementTransformerVoid() {
|
||||
private fun IrSimpleFunction.accessorBody(delegate: IrPropertyReference, receiverField: IrDeclaration?) =
|
||||
context.createIrBuilder(symbol, startOffset, endOffset).run {
|
||||
val value = valueParameters.singleOrNull()?.let(::irGet)
|
||||
var boundReceiver = when (receiverField) {
|
||||
null -> null
|
||||
is IrField -> irGetField(dispatchReceiverParameter?.let(::irGet), receiverField)
|
||||
is IrValueDeclaration -> irGet(receiverField)
|
||||
else -> throw AssertionError("not a field/variable: ${receiverField.render()}")
|
||||
}
|
||||
val unboundReceiver = extensionReceiverParameter ?: dispatchReceiverParameter
|
||||
val field = delegate.field?.owner
|
||||
val access = if (field == null) {
|
||||
val accessor = if (value == null) delegate.getter!! else delegate.setter!!
|
||||
irCall(accessor).apply {
|
||||
// This has the same assumptions about receivers as `PropertyReferenceLowering.propertyReferenceKindFor`:
|
||||
// only one receiver can be bound, and if the property has both, the extension receiver cannot be bound.
|
||||
// The frontend must also ensure the receiver of the delegated property (extension if present, dispatch
|
||||
// otherwise) is a subtype of the unbound receiver (if there is one; and there can *only* be one).
|
||||
if (accessor.owner.dispatchReceiverParameter != null) {
|
||||
dispatchReceiver = boundReceiver.also { boundReceiver = null } ?: irGet(unboundReceiver!!)
|
||||
}
|
||||
if (accessor.owner.extensionReceiverParameter != null) {
|
||||
extensionReceiver = boundReceiver.also { boundReceiver = null } ?: irGet(unboundReceiver!!)
|
||||
}
|
||||
if (value != null) {
|
||||
putValueArgument(0, value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val receiver = if (field.isStatic) null else boundReceiver ?: irGet(unboundReceiver!!)
|
||||
if (value == null) irGetField(receiver, field) else irSetField(receiver, field, value)
|
||||
}
|
||||
irExprBody(access)
|
||||
}
|
||||
|
||||
private val IrSimpleFunction.returnsResultOfStdlibCall: Boolean
|
||||
get() = when (val body = body) {
|
||||
is IrExpressionBody -> body.expression.isStdlibCall
|
||||
is IrBlockBody -> body.statements.singleOrNull()?.let { it.isStdlibCall || (it is IrReturn && it.value.isStdlibCall) } == true
|
||||
else -> false
|
||||
}
|
||||
|
||||
private val IrStatement.isStdlibCall: Boolean
|
||||
get() = this is IrCall && symbol.owner.getPackageFragment()?.fqName == StandardNames.BUILT_INS_PACKAGE_FQ_NAME
|
||||
|
||||
override fun visitProperty(declaration: IrProperty): IrStatement {
|
||||
if (!declaration.isDelegated || declaration.isFakeOverride) return super.visitProperty(declaration)
|
||||
|
||||
val oldField = declaration.backingField
|
||||
val delegate = oldField?.initializer?.expression
|
||||
if (delegate !is IrPropertyReference ||
|
||||
declaration.getter?.returnsResultOfStdlibCall == false ||
|
||||
declaration.setter?.returnsResultOfStdlibCall == false
|
||||
) return super.visitProperty(declaration)
|
||||
|
||||
declaration.backingField = (delegate.dispatchReceiver ?: delegate.extensionReceiver)?.let { receiver ->
|
||||
context.irFactory.buildField {
|
||||
updateFrom(oldField)
|
||||
name = Name.identifier("${declaration.name}\$receiver")
|
||||
type = receiver.type
|
||||
}.apply {
|
||||
parent = oldField.parent
|
||||
initializer = context.irFactory.createExpressionBody(receiver.transform(this@PropertyReferenceDelegationTransformer, null))
|
||||
}
|
||||
}
|
||||
declaration.getter?.apply { body = accessorBody(delegate, declaration.backingField) }
|
||||
declaration.setter?.apply { body = accessorBody(delegate, declaration.backingField) }
|
||||
return declaration
|
||||
}
|
||||
|
||||
override fun visitLocalDelegatedProperty(declaration: IrLocalDelegatedProperty): IrStatement {
|
||||
val delegate = declaration.delegate.initializer
|
||||
if (delegate !is IrPropertyReference ||
|
||||
!declaration.getter.returnsResultOfStdlibCall ||
|
||||
declaration.setter?.returnsResultOfStdlibCall == false
|
||||
) return super.visitLocalDelegatedProperty(declaration)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
// TODO: just like in `PropertyReferenceLowering`, probably better to inline the getter/setter rather than
|
||||
// generate them as local functions.
|
||||
val getter = declaration.getter.apply { body = accessorBody(delegate, receiver) }
|
||||
val setter = declaration.setter?.apply { body = accessorBody(delegate, receiver) }
|
||||
val statements = listOfNotNull(receiver, getter, setter)
|
||||
return statements.singleOrNull()
|
||||
?: IrCompositeImpl(declaration.startOffset, declaration.endOffset, context.irBuiltIns.unitType, null, statements)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -60,7 +60,7 @@ internal val propertyReferencePhase = makeIrFilePhase(
|
||||
description = "Construct KProperty instances returned by expressions such as A::x and A()::x",
|
||||
// This must be done after contents of functions are extracted into separate classes, or else the `$$delegatedProperties`
|
||||
// field will end up in the wrong class (not the one that declares the delegated property).
|
||||
prerequisite = setOf(functionReferencePhase, suspendLambdaPhase)
|
||||
prerequisite = setOf(functionReferencePhase, suspendLambdaPhase, propertyReferenceDelegationPhase)
|
||||
)
|
||||
|
||||
private class PropertyReferenceLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// WITH_REFLECT
|
||||
// WITH_RUNTIME
|
||||
class C(val x: String)
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// WITH_RUNTIME
|
||||
// IGNORE_BACKEND: JS
|
||||
class C(var x: String)
|
||||
|
||||
var x = "fail"
|
||||
var y by ::x
|
||||
var z by C("fail")::x
|
||||
|
||||
fun box(): String {
|
||||
y = "O"
|
||||
z = "K"
|
||||
return y + z
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// TARGET_BACKEND: JVM
|
||||
// v-- fir2ir produces an IrFunctionReference of type KProperty0 instead of an IrPropertyReference
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// WITH_REFLECT
|
||||
// WITH_RUNTIME
|
||||
// FILE: J.java
|
||||
public interface J<T> {
|
||||
public T getValue();
|
||||
}
|
||||
|
||||
// FILE: box.kt
|
||||
class Impl(val x: String) : J<String> {
|
||||
override fun getValue() = x
|
||||
}
|
||||
|
||||
val j1: J<String> = Impl("O")
|
||||
// Note that taking a reference to `J<T>::value` is not permitted by the frontend
|
||||
// in any context except as a direct argument to `by`; e.g. `val x by run { j1::value }`
|
||||
// would produce an error.
|
||||
val x by j1::value
|
||||
|
||||
fun box(): String {
|
||||
val j2: J<String> = Impl("K")
|
||||
val y by j2::value
|
||||
return x + y
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// WITH_RUNTIME
|
||||
class C(var x: Int) {
|
||||
val y by C::x
|
||||
var ym by C::x
|
||||
val z by ::x
|
||||
var zm by ::x
|
||||
}
|
||||
|
||||
class D(val c: C) {
|
||||
val y by c::x
|
||||
var ym by c::x
|
||||
val C.z by C::x
|
||||
var C.zm by C::x
|
||||
}
|
||||
|
||||
var x = 1
|
||||
val y by ::x
|
||||
var ym by ::x
|
||||
val z by C(1)::x
|
||||
var zm by C(1)::x
|
||||
|
||||
fun local() {
|
||||
val y by ::x
|
||||
var ym by ::x
|
||||
val z by C(1)::x
|
||||
var zm by C(1)::x
|
||||
}
|
||||
|
||||
// 0 \$\$delegatedProperties
|
||||
// 0 kotlin/jvm/internal/PropertyReference[0-2]Impl\.\<init\>
|
||||
|
||||
// JVM_IR_TEMPLATES
|
||||
// Optimized all to direct accesses:
|
||||
// 0 kotlin/jvm/internal/MutablePropertyReference[0-2]Impl\.\<init\>
|
||||
|
||||
// JVM_TEMPLATES
|
||||
// Not optimized:
|
||||
// 16 kotlin/jvm/internal/MutablePropertyReference[0-2]Impl\.\<init\>
|
||||
+12
@@ -13198,6 +13198,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToAnotherMutable.kt")
|
||||
public void testDelegateToAnotherMutable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToGenericJavaProperty.kt")
|
||||
public void testDelegateToGenericJavaProperty() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToGenericJavaProperty.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateWithPrivateSet.kt")
|
||||
public void testDelegateWithPrivateSet() throws Exception {
|
||||
|
||||
+6
@@ -4570,6 +4570,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
runTest("compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/definedInSources.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToAnother.kt")
|
||||
public void testDelegateToAnother() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("inSeparateModule.kt")
|
||||
public void testInSeparateModule() throws Exception {
|
||||
|
||||
+12
@@ -13198,6 +13198,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToAnotherMutable.kt")
|
||||
public void testDelegateToAnotherMutable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToGenericJavaProperty.kt")
|
||||
public void testDelegateToGenericJavaProperty() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToGenericJavaProperty.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateWithPrivateSet.kt")
|
||||
public void testDelegateWithPrivateSet() throws Exception {
|
||||
|
||||
+6
@@ -4708,6 +4708,12 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
||||
runTest("compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/definedInSources.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("delegateToAnother.kt")
|
||||
public void testDelegateToAnother() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("inSeparateModule.kt")
|
||||
public void testInSeparateModule() throws Exception {
|
||||
|
||||
+10
@@ -10722,6 +10722,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("delegateToAnotherMutable.kt")
|
||||
public void testDelegateToAnotherMutable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("delegateToGenericJavaProperty.kt")
|
||||
public void testDelegateToGenericJavaProperty() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToGenericJavaProperty.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
+5
@@ -9541,6 +9541,11 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("delegateToAnotherMutable.kt")
|
||||
public void testDelegateToAnotherMutable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("delegateWithPrivateSet.kt")
|
||||
public void testDelegateWithPrivateSet() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateWithPrivateSet.kt");
|
||||
|
||||
Generated
+5
@@ -8947,6 +8947,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("delegateToAnotherMutable.kt")
|
||||
public void testDelegateToAnotherMutable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("delegateWithPrivateSet.kt")
|
||||
public void testDelegateWithPrivateSet() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateWithPrivateSet.kt");
|
||||
|
||||
Generated
+5
@@ -8947,6 +8947,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnother.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("delegateToAnotherMutable.kt")
|
||||
public void testDelegateToAnotherMutable() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateToAnotherMutable.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("delegateWithPrivateSet.kt")
|
||||
public void testDelegateWithPrivateSet() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/delegatedProperty/delegateWithPrivateSet.kt");
|
||||
|
||||
Reference in New Issue
Block a user