JVM_IR: refactor MoveCompanionObjectFieldsLowering

* Extract replacement of IrGetField/IrSetField into a separate
   file-level lowering (should reduce the amount of work to linear in
   the number of classes rather than potentially quadratic)

 * Extract static backing field construction into JvmDeclarationFactory
   and move that lowering after PropertiesToFields lowering to reduce
   code duplication
This commit is contained in:
pyos
2019-11-19 11:52:38 +01:00
committed by Alexander Udalov
parent afd59f0d46
commit d221c1f242
13 changed files with 181 additions and 171 deletions
@@ -225,6 +225,7 @@ private val jvmFilePhases =
propertyReferencePhase then
constPhase then
propertiesToFieldsPhase then
remapObjectFieldAccesses then
propertiesPhase then
renameFieldsPhase then
anonymousObjectSuperConstructorPhase then
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.MethodSignatureMapper
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.replaceThisByStaticReference
import org.jetbrains.kotlin.builtins.CompanionObjectMapping.isMappedIntrinsicCompanionObject
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.*
import org.jetbrains.kotlin.ir.descriptors.WrappedClassConstructorDescriptor
import org.jetbrains.kotlin.ir.descriptors.WrappedClassDescriptor
import org.jetbrains.kotlin.ir.descriptors.WrappedValueParameterDescriptor
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.symbols.impl.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.*
@@ -37,6 +39,7 @@ class JvmDeclarationFactory(
private val interfaceCompanionFieldDeclarations = HashMap<IrSymbolOwner, IrField>()
private val outerThisDeclarations = HashMap<IrClass, IrField>()
private val innerClassConstructors = HashMap<IrConstructor, IrConstructor>()
private val staticBackingFields = HashMap<IrProperty, IrField>()
private val defaultImplsMethods = HashMap<IrSimpleFunction, IrSimpleFunction>()
private val defaultImplsClasses = HashMap<IrClass, IrClass>()
@@ -152,6 +155,36 @@ class JvmDeclarationFactory(
else
getFieldForObjectInstance(singleton)
fun getStaticBackingField(irProperty: IrProperty): IrField? {
// Only fields defined directly in objects should be made static.
// Fake overrides never point to those, as objects are final.
if (irProperty.origin == IrDeclarationOrigin.FAKE_OVERRIDE) return null
val oldField = irProperty.backingField ?: return null
val oldParent = irProperty.parent as? IrClass ?: return null
if (!oldParent.isObject) return null
return staticBackingFields.getOrPut(irProperty) {
buildField {
updateFrom(oldField)
name = oldField.name
isStatic = true
}.apply {
// We don't move fields to interfaces unless all fields are annotated with @JvmField.
// It is an error to annotate only some of the fields of an interface companion with
// @JvmField, so checking the current field only should be enough.
val hasJvmField = oldField.hasAnnotation(JvmAbi.JVM_FIELD_ANNOTATION_FQ_NAME)
parent = if (oldParent.isCompanion && (!oldParent.parentAsClass.isJvmInterface || hasJvmField))
oldParent.parentAsClass
else
oldParent
annotations += oldField.annotations
initializer = oldField.initializer
?.replaceThisByStaticReference(this@JvmDeclarationFactory, oldParent, oldParent.thisReceiver!!)
?.deepCopyWithSymbols(this) as IrExpressionBody?
(this as IrFieldImpl).metadata = oldField.metadata
}
}
}
fun getDefaultImplsFunction(interfaceFun: IrSimpleFunction): IrSimpleFunction {
val parent = interfaceFun.parentAsClass
assert(parent.isJvmInterface) { "Parent of ${interfaceFun.dump()} should be interface" }
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.lower.IrLoweringContext
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmSymbols
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.descriptors.JvmDeclarationFactory
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.hasMangledParameters
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.Visibilities
@@ -152,14 +153,14 @@ fun IrExpression.isSmartcastFromHigherThanNullable(context: JvmBackendContext) =
!this.argument.type.isSubtypeOf(type.makeNullable(), context.irBuiltIns)
fun IrBody.replaceThisByStaticReference(
context: JvmBackendContext,
declarationFactory: JvmDeclarationFactory,
irClass: IrClass,
oldThisReceiverParameter: IrValueParameter
): IrBody =
transform(object : IrElementTransformerVoid() {
override fun visitGetValue(expression: IrGetValue): IrExpression {
if (expression.symbol == oldThisReceiverParameter.symbol) {
val instanceField = context.declarationFactory.getPrivateFieldForObjectInstance(irClass)
val instanceField = declarationFactory.getPrivateFieldForObjectInstance(irClass)
return IrGetFieldImpl(
expression.startOffset,
expression.endOffset,
@@ -181,7 +181,7 @@ private class SingletonObjectJvmStaticLowering(
}
fun modifyBody(irFunction: IrFunction, irClass: IrClass, oldDispatchReceiverParameter: IrValueParameter) {
irFunction.body = irFunction.body?.replaceThisByStaticReference(context, irClass, oldDispatchReceiverParameter)
irFunction.body = irFunction.body?.replaceThisByStaticReference(context.declarationFactory, irClass, oldDispatchReceiverParameter)
}
}
@@ -6,34 +6,20 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.replaceThisByStaticReference
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.declarations.addField
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrAnonymousInitializerImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFieldImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
import org.jetbrains.kotlin.ir.descriptors.WrappedFieldDescriptor
import org.jetbrains.kotlin.ir.descriptors.WrappedVariableDescriptor
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.expressions.impl.IrSetFieldImpl
import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isObject
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.load.java.JvmAbi.JVM_FIELD_ANNOTATION_FQ_NAME
internal val moveOrCopyCompanionObjectFieldsPhase = makeIrFilePhase(
::MoveOrCopyCompanionObjectFieldsLowering,
@@ -41,186 +27,110 @@ internal val moveOrCopyCompanionObjectFieldsPhase = makeIrFilePhase(
description = "Move and/or copy companion object fields to static fields of companion's owner"
)
internal val remapObjectFieldAccesses = makeIrFilePhase(
::RemapObjectFieldAccesses,
name = "RemapObjectFieldAccesses",
description = "Make IrGetField/IrSetField to objects' fields point to the static versions",
prerequisite = setOf(propertiesToFieldsPhase)
)
private class MoveOrCopyCompanionObjectFieldsLowering(val context: JvmBackendContext) : ClassLoweringPass {
override fun lower(irClass: IrClass) {
val fieldReplacementMap = mutableMapOf<IrFieldSymbol, IrFieldSymbol>()
if (irClass.isObject && !irClass.isCompanion && irClass.visibility != Visibilities.LOCAL) {
handleObject(irClass, fieldReplacementMap)
if (irClass.isObject && !irClass.isCompanion) {
irClass.handle()
} else {
handleClass(irClass, fieldReplacementMap)
if (irClass.isJvmInterface)
copyConsts(irClass)
(irClass.declarations.singleOrNull { it is IrClass && it.isCompanion } as IrClass?)?.handle()
}
irClass.replaceFieldReferences(fieldReplacementMap)
}
private fun handleObject(irObject: IrClass, fieldReplacementMap: MutableMap<IrFieldSymbol, IrFieldSymbol>) {
irObject.declarations.replaceAll {
private fun IrClass.handle() {
val newDeclarations = declarations.map {
when (it) {
is IrProperty -> {
// The field is not actually moved, just replaced by a static field
movePropertyFieldToStaticParent(it, irObject, irObject, fieldReplacementMap)
it
is IrProperty -> context.declarationFactory.getStaticBackingField(it)?.also { newField ->
it.backingField = newField
newField.correspondingPropertySymbol = it.symbol
}
is IrAnonymousInitializer -> moveAnonymousInitializerToStaticParent(it, irObject, irObject)
else -> it
}
}
}
private fun handleClass(irClass: IrClass, fieldReplacementMap: MutableMap<IrFieldSymbol, IrFieldSymbol>) {
val companion = irClass.declarations.find {
it is IrClass && it.isCompanion
} as IrClass? ?: return
// We don't move fields to interfaces unless all fields are annotated with @JvmField.
// It is an error to annotate only some of the fields of an interface companion with @JvmField.
val newParent = if (irClass.isJvmInterface && !companion.allFieldsAreJvmField()) companion else irClass
val newDeclarations = companion.declarations.map {
when (it) {
is IrProperty ->
movePropertyFieldToStaticParent(it, companion, newParent, fieldReplacementMap)
is IrAnonymousInitializer ->
moveAnonymousInitializerToStaticParent(it, companion, newParent)
else ->
null
else -> null
}
}
if (newParent === companion) {
// Keep fields as children of `IrProperty`, but replace anonymous initializers with static ones,
// preserving the relative ordering of anonymous initializers and property initializers.
for ((i, declaration) in newDeclarations.withIndex())
if (declaration is IrAnonymousInitializer)
companion.declarations[i] = declaration
val companionParent = if (isCompanion) parentAsClass else null
// In case a companion contains no fields, move the anonymous initializers to the parent
// anyway, as otherwise there will probably be no references to the companion class at all
// and therefore its class initializer will never be invoked.
val newParent = newDeclarations.firstOrNull { it != null }?.parentAsClass ?: companionParent ?: this
if (newParent === this) {
declarations.replaceAll {
// Anonymous initializers must be made static to correctly initialize the new static
// fields when the class is loaded.
if (it is IrAnonymousInitializer) makeAnonymousInitializerStatic(it, newParent) else it
}
if (companionParent != null) {
for (declaration in declarations) {
if (declaration is IrProperty && declaration.isConst && declaration.hasPublicVisibility) {
copyConstField(declaration.backingField!!, companionParent)
}
}
}
} else {
// Move all touched declarations to the parent.
companion.declarations.removeAll { it is IrAnonymousInitializer }
newDeclarations.filterNotNullTo(newParent.declarations)
// Anonymous initializers must also be moved and their ordering relative to the fields
// must be preserved, as the fields can have expression initializers themselves.
for ((i, newField) in newDeclarations.withIndex()) {
if (newField != null)
newParent.declarations += newField
if (declarations[i] is IrAnonymousInitializer)
newParent.declarations += makeAnonymousInitializerStatic(declarations[i] as IrAnonymousInitializer, newParent)
}
declarations.removeAll { it is IrAnonymousInitializer }
}
}
private fun copyConsts(irClass: IrClass) {
val companion = irClass.declarations.find {
it is IrClass && it.isCompanion
} as IrClass? ?: return
companion.declarations.filter { it is IrProperty && it.isConst && it.hasPublicVisibility }
.mapNotNullTo(irClass.declarations) { copyPropertyFieldToStaticParent(it as IrProperty, companion, irClass) }
}
private val IrProperty.hasPublicVisibility: Boolean
get() = !Visibilities.isPrivate(visibility) && visibility != Visibilities.PROTECTED
private fun IrClass.allFieldsAreJvmField() =
declarations.filterIsInstance<IrProperty>()
.mapNotNull { it.backingField }.all { it.hasAnnotation(JVM_FIELD_ANNOTATION_FQ_NAME) }
// If fieldReplacementMap is null / unspecified, keep the old field and don't update the references.
private fun moveOrCopyPropertyFieldToStaticParent(
irProperty: IrProperty,
propertyParent: IrClass,
fieldParent: IrClass,
fieldReplacementMap: MutableMap<IrFieldSymbol, IrFieldSymbol>? = null
): IrField? {
if (irProperty.origin == IrDeclarationOrigin.FAKE_OVERRIDE) return null
val oldField = irProperty.backingField ?: return null
val newField = createStaticBackingField(oldField, propertyParent, fieldParent)
fieldReplacementMap?.run {
irProperty.backingField = newField
newField.correspondingPropertySymbol = irProperty.symbol
put(oldField.symbol, newField.symbol)
}
return newField
}
private fun movePropertyFieldToStaticParent(
irProperty: IrProperty,
propertyParent: IrClass,
fieldParent: IrClass,
fieldReplacementMap: MutableMap<IrFieldSymbol, IrFieldSymbol>? = null
): IrField? = moveOrCopyPropertyFieldToStaticParent(irProperty, propertyParent, fieldParent, fieldReplacementMap)
private fun copyPropertyFieldToStaticParent(
irProperty: IrProperty,
propertyParent: IrClass,
fieldParent: IrClass
): IrField? = moveOrCopyPropertyFieldToStaticParent(irProperty, propertyParent, fieldParent)
private fun moveAnonymousInitializerToStaticParent(
oldInitializer: IrAnonymousInitializer,
oldParent: IrClass,
newParent: IrClass
): IrAnonymousInitializer =
private fun makeAnonymousInitializerStatic(oldInitializer: IrAnonymousInitializer, newParent: IrClass) =
with(oldInitializer) {
IrAnonymousInitializerImpl(
startOffset, endOffset, origin, IrAnonymousInitializerSymbolImpl(newParent.symbol),
isStatic = true
).apply {
val oldParent = parentAsClass
val newSymbol = IrAnonymousInitializerSymbolImpl(newParent.symbol)
IrAnonymousInitializerImpl(startOffset, endOffset, origin, newSymbol, isStatic = true).apply {
parent = newParent
body = oldInitializer.body
.replaceThisByStaticReference(context, oldParent, oldParent.thisReceiver!!)
body = this@with.body
.replaceThisByStaticReference(context.declarationFactory, oldParent, oldParent.thisReceiver!!)
.patchDeclarationParents(newParent) as IrBlockBody
}
}
private fun createStaticBackingField(oldField: IrField, propertyParent: IrClass, fieldParent: IrClass): IrField {
val descriptor = WrappedFieldDescriptor(oldField.descriptor.annotations, oldField.descriptor.source)
val field = IrFieldImpl(
oldField.startOffset, oldField.endOffset,
IrDeclarationOrigin.PROPERTY_BACKING_FIELD,
IrFieldSymbolImpl(descriptor),
oldField.name, oldField.type, oldField.visibility,
isFinal = oldField.isFinal,
isExternal = oldField.isExternal,
isStatic = true,
isFakeOverride = false
).apply {
descriptor.bind(this)
parent = fieldParent
annotations.addAll(oldField.annotations)
metadata = oldField.metadata
private fun copyConstField(oldField: IrField, newParent: IrClass) =
newParent.addField {
updateFrom(oldField)
name = oldField.name
isStatic = true
}.apply {
parent = newParent
annotations += oldField.annotations
initializer = oldField.initializer?.deepCopyWithSymbols(this)
}
val oldInitializer = oldField.initializer
if (oldInitializer != null) {
field.initializer = oldInitializer
.replaceThisByStaticReference(context, propertyParent, propertyParent.thisReceiver!!)
.deepCopyWithSymbols(field) as IrExpressionBody
}
return field
}
}
private fun IrElement.replaceFieldReferences(replacementMap: Map<IrFieldSymbol, IrFieldSymbol>) {
transformChildrenVoid(FieldReplacer(replacementMap))
}
private class RemapObjectFieldAccesses(val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoid() {
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
private fun IrField.remap(): IrField? =
correspondingPropertySymbol?.owner?.let(context.declarationFactory::getStaticBackingField)
private class FieldReplacer(val replacementMap: Map<IrFieldSymbol, IrFieldSymbol>) : IrElementTransformerVoid() {
override fun visitGetField(expression: IrGetField): IrExpression =
replacementMap[expression.symbol]?.let { newSymbol ->
IrGetFieldImpl(
expression.startOffset, expression.endOffset,
newSymbol,
expression.type,
/* receiver = */ null,
expression.origin,
expression.superQualifierSymbol
)
expression.symbol.owner.remap()?.let {
with(expression) {
IrGetFieldImpl(startOffset, endOffset, it.symbol, type, /* receiver = */ null, origin, superQualifierSymbol)
}
} ?: super.visitGetField(expression)
override fun visitSetField(expression: IrSetField): IrExpression =
replacementMap[expression.symbol]?.let { _ ->
IrSetFieldImpl(
expression.startOffset, expression.endOffset,
replacementMap.getValue(expression.symbol),
/* receiver = */ null,
visitExpression(expression.value),
expression.type,
expression.origin,
expression.superQualifierSymbol
)
expression.symbol.owner.remap()?.let {
with(expression) {
val newValue = value.transform(this@RemapObjectFieldAccesses, null)
IrSetFieldImpl(startOffset, endOffset, it.symbol, /* receiver = */ null, newValue, type, origin, superQualifierSymbol)
}
} ?: super.visitSetField(expression)
}
+10
View File
@@ -0,0 +1,10 @@
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: box.kt
fun box(): String = Z.result
// FILE: z.kt
object Z {
@JvmField
val result: String = "OK"
}
@@ -0,0 +1,17 @@
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: box.kt
fun box(): String = A.result
object A {
@JvmField
val result: String
init { result = Z.result }
}
// FILE: z.kt
object Z {
@JvmField
val result: String = "OK"
}
@@ -1,5 +1,4 @@
// !LANGUAGE: +JvmFieldInInterface +NestedClassesInAnnotations
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@@ -1,5 +1,4 @@
// !LANGUAGE: +JvmFieldInInterface
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@@ -15058,6 +15058,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/jvmField/constructorProperty.kt");
}
@TestMetadata("fileOrder.kt")
public void testFileOrder() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/fileOrder.kt");
}
@TestMetadata("fileOrderWithCopying.kt")
public void testFileOrderWithCopying() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/fileOrderWithCopying.kt");
}
@TestMetadata("initializersOrder.kt")
public void testInitializersOrder() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/initializersOrder.kt");
@@ -15058,6 +15058,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/jvmField/constructorProperty.kt");
}
@TestMetadata("fileOrder.kt")
public void testFileOrder() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/fileOrder.kt");
}
@TestMetadata("fileOrderWithCopying.kt")
public void testFileOrderWithCopying() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/fileOrderWithCopying.kt");
}
@TestMetadata("initializersOrder.kt")
public void testInitializersOrder() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/initializersOrder.kt");
@@ -13913,6 +13913,16 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/jvmField/constructorProperty.kt");
}
@TestMetadata("fileOrder.kt")
public void testFileOrder() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/fileOrder.kt");
}
@TestMetadata("fileOrderWithCopying.kt")
public void testFileOrderWithCopying() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/fileOrderWithCopying.kt");
}
@TestMetadata("initializersOrder.kt")
public void testInitializersOrder() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/initializersOrder.kt");
@@ -13913,6 +13913,16 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/jvmField/constructorProperty.kt");
}
@TestMetadata("fileOrder.kt")
public void testFileOrder() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/fileOrder.kt");
}
@TestMetadata("fileOrderWithCopying.kt")
public void testFileOrderWithCopying() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/fileOrderWithCopying.kt");
}
@TestMetadata("initializersOrder.kt")
public void testInitializersOrder() throws Exception {
runTest("compiler/testData/codegen/box/jvmField/initializersOrder.kt");