JVM_IR: Lower IrGetEnumValue to this whenever possible.
The reference can be lowered to `this` if it is captured in the lexical scope of the corresponding enum entry, and not used by the enum entry's super constructor. Otherwise, it is lowered to `GETFIELD SomeEnum.SomeEntry`.
This commit is contained in:
committed by
max-kammerer
parent
faa6eacb25
commit
0dd09ea7de
@@ -81,6 +81,7 @@ val jvmPhases = namedIrFilePhase(
|
||||
|
||||
makePatchParentsPhase(1) then
|
||||
|
||||
singletonReferencesPhase then
|
||||
jvmLocalDeclarationsPhase then
|
||||
singleAbstractMethodPhase then
|
||||
callableReferencePhase then
|
||||
@@ -95,7 +96,6 @@ val jvmPhases = namedIrFilePhase(
|
||||
enumClassPhase then
|
||||
objectClassPhase then
|
||||
makeInitializersPhase(JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER, true) then
|
||||
singletonReferencesPhase then
|
||||
syntheticAccessorPhase then
|
||||
bridgePhase then
|
||||
jvmOverloadsAnnotationPhase then
|
||||
|
||||
+72
-10
@@ -5,14 +5,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.backend.jvm.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.common.pop
|
||||
import org.jetbrains.kotlin.backend.common.push
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetEnumValue
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSymbolOwner
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
|
||||
@@ -22,18 +25,77 @@ internal val singletonReferencesPhase = makeIrFilePhase(
|
||||
description = "Handle singleton references"
|
||||
)
|
||||
|
||||
private class SingletonReferencesLowering(val context: JvmBackendContext) : BodyLoweringPass, IrElementTransformerVoid() {
|
||||
override fun lower(irBody: IrBody) {
|
||||
irBody.transformChildrenVoid(this)
|
||||
private class SingletonReferencesLowering(val context: JvmBackendContext) : ClassLoweringPass, IrElementTransformerVoid() {
|
||||
private lateinit var containingClass: IrClass
|
||||
private val constructingEnums = arrayListOf()
|
||||
|
||||
override fun lower(irClass: IrClass) {
|
||||
containingClass = irClass
|
||||
irClass.transformChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitEnumConstructorCall(expression: IrEnumConstructorCall): IrExpression {
|
||||
constructingEnums.push(expression.symbol.owner.parent)
|
||||
val call = super.visitEnumConstructorCall(expression)
|
||||
constructingEnums.pop()
|
||||
return call
|
||||
}
|
||||
|
||||
override fun visitGetEnumValue(expression: IrGetEnumValue): IrExpression {
|
||||
val entrySymbol = context.declarationFactory.getFieldForEnumEntry(expression.symbol.owner, expression.type)
|
||||
return IrGetFieldImpl(expression.startOffset, expression.endOffset, entrySymbol.symbol, expression.type)
|
||||
val candidate = expression.symbol.owner.correspondingClass
|
||||
|
||||
return if (candidate != null && isInScope(candidate) && !isVisitingSuperConstructor(candidate)) {
|
||||
// Replace `SomeEnumClass.SomeEnumEntry` with `this`, if possible.
|
||||
//
|
||||
// SomeEnumEntry is a singleton, which is assigned (SETFIELD) to SomeEnumClass after the construction of the singleton is done.
|
||||
// Therefore, during the construction of SomeEnumEntry, SomeEnumClass.SomeEnumEntry isn't available yet. All references to it
|
||||
// must be replaced with `SomeEnumEntry.this`.
|
||||
IrGetValueImpl(expression.startOffset, expression.endOffset, expression.type, candidate.thisReceiver!!.symbol)
|
||||
} else {
|
||||
val entrySymbol = context.declarationFactory.getFieldForEnumEntry(expression.symbol.owner, expression.type)
|
||||
IrGetFieldImpl(expression.startOffset, expression.endOffset, entrySymbol.symbol, expression.type)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitGetObjectValue(expression: IrGetObjectValue): IrExpression {
|
||||
val instanceField = context.declarationFactory.getFieldForObjectInstance(expression.symbol.owner)
|
||||
return IrGetFieldImpl(expression.startOffset, expression.endOffset, instanceField.symbol, expression.type)
|
||||
}
|
||||
|
||||
// `this` is generally available while the reference is within the lexical scope of the containing enum entry.
|
||||
private fun isInScope(symbol: IrSymbolOwner?): Boolean {
|
||||
var candidate: IrDeclaration? = containingClass
|
||||
|
||||
while (candidate != null && symbol != candidate)
|
||||
candidate = candidate.parent as? IrDeclaration
|
||||
|
||||
return candidate != null
|
||||
}
|
||||
|
||||
// `this` isn't usable before `super.<init>`. Consider the example,
|
||||
//
|
||||
// 1: enum class Test(val x: String, val closure1: () -> String) {
|
||||
// 2: FOO("O", { FOO.x }) {
|
||||
// 3: val y: String = run { FOO.x }
|
||||
// 4: };
|
||||
// 5: }
|
||||
//
|
||||
// The constructing sequence would look like the following, if the reference was lowered to `this`:
|
||||
//
|
||||
// FOO.<init>(this) {
|
||||
// val lambda1 = new lambda_in_line_1_type
|
||||
// lambda_in_line_1_type.<init>(lambda1, this)
|
||||
// Test.<init>(this, "O", lambda1)
|
||||
// ...
|
||||
// val lambda3 = new lambda_in_line_3_type
|
||||
// lambda_in_line_3_type.<init>(lambda3, this)
|
||||
// }
|
||||
//
|
||||
// Before and after `Test.<init>`, the type of `this` is `uninitializedThis` and `Test`, respectively. Therefore, passing `this` to
|
||||
// `lambda_containing_foo_x_type.<init>` results in a type mismatch. Passing `this` to `lambda_containing_foo_y_type.<init>` is fine.
|
||||
//
|
||||
// Assumptions:
|
||||
// 1. An enum entry's declaration parent is always the enum class.
|
||||
// 2. Enums are constructed in <clinit>, so there's no interleaving constructor calls from unrelated enums.
|
||||
private fun isVisitingSuperConstructor(irClass: IrClass) = irClass.parent == constructingEnums.lastOrNull()
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
enum class A {
|
||||
X {
|
||||
val x = "OK"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
enum class A {
|
||||
X {
|
||||
val k = "K"
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
interface IFoo {
|
||||
fun foo(): String
|
||||
}
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
interface IFoo {
|
||||
fun foo(): String
|
||||
}
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
interface IFoo {
|
||||
fun foo(): String
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
enum class A {
|
||||
X {
|
||||
val x = "OK"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
enum class A {
|
||||
X {
|
||||
val x = "OK"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class Test(val x: String, val closure1: () -> String) {
|
||||
FOO("O", { FOO.x }) {
|
||||
override val y: String = "K"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class Test(val x: String, val closure1: () -> String) {
|
||||
FOO("O", run { { FOO.x } }) {
|
||||
override val y: String = "K"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class X {
|
||||
B {
|
||||
val value2 = "K"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class X {
|
||||
B {
|
||||
val value2 = "K"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class X {
|
||||
B {
|
||||
val value2 = "K"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
enum class X {
|
||||
B {
|
||||
val k = "K"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class X {
|
||||
B {
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class X {
|
||||
B {
|
||||
override val value2 = "K"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class X {
|
||||
B {
|
||||
override val value2 = "K"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
enum class X {
|
||||
B {
|
||||
val value2 = "K"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
fun <T, R> T.letNoInline(fn: (T) -> R) =
|
||||
fn(this)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user