JVM_IR simplify BridgeLowering

Generate bridges immediately for each class.

No need to compute existing signatures for a given method name unless it
requires a special bridge.
This commit is contained in:
Dmitry Petrov
2021-07-06 21:26:32 +03:00
parent 8317daa00b
commit 1a0a3df9d2
@@ -6,14 +6,16 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.common.ir.allOverridden
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.isMethodOfAny
import org.jetbrains.kotlin.backend.common.ir.isStatic
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
@@ -30,7 +32,7 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
import java.util.concurrent.ConcurrentHashMap
@@ -158,12 +160,8 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
val isOverriding: Boolean = true,
)
private val potentialBridgeTargets = mutableListOf<IrSimpleFunction>()
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid()
generateBridges()
potentialBridgeTargets.clear()
}
override fun visitClass(declaration: IrClass): IrStatement {
@@ -171,34 +169,11 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
if (declaration.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS || declaration.isAnnotationClass)
return super.visitClass(declaration)
declaration.functions.filterTo(potentialBridgeTargets, fun(irFunction: IrSimpleFunction): Boolean {
// Only overrides may need bridges and so in particular, private and static functions do not.
// Note that this includes the static replacements for inline class functions (which are static, but have
// overriddenSymbols in order to produce correct signatures in the type mapper).
if (DescriptorVisibilities.isPrivate(irFunction.visibility) || irFunction.isStatic || irFunction.overriddenSymbols.isEmpty())
return false
val bridgeTargets = declaration.functions.filterTo(SmartList()) { it.isPotentialBridgeTarget() }
if (bridgeTargets.isEmpty())
return super.visitClass(declaration)
// None of the methods of Any have type parameters and so we will not need bridges for them.
if (irFunction.isMethodOfAny())
return false
// We don't produce bridges for abstract functions in interfaces.
if (irFunction.isJvmAbstract(context.state.jvmDefaultMode)) {
if (irFunction.parentAsClass.isJvmInterface) {
// If function requires a special bridge, we should record it for generic signatures generation.
if (irFunction.specialBridgeOrNull != null) {
context.functionsWithSpecialBridges.add(irFunction)
}
return false
}
return true
}
// Finally, the JVM backend also ignores concrete fake overrides whose implementation is directly inherited from an interface.
// This is sound, since we do not generate type-specialized versions of fake overrides and if the method
// were to override several interface methods the frontend would require a separate implementation.
return !irFunction.isFakeOverride || irFunction.resolvesToClass()
})
bridgeTargets.forEach { createBridges(declaration, it) }
if (declaration.isInline) {
// Inline class (implementing 'MutableCollection<T>', where T is Int or an inline class mapped to Int)
@@ -217,6 +192,35 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
return super.visitClass(declaration)
}
private fun IrSimpleFunction.isPotentialBridgeTarget(): Boolean {
// Only overrides may need bridges and so in particular, private and static functions do not.
// Note that this includes the static replacements for inline class functions (which are static, but have
// overriddenSymbols in order to produce correct signatures in the type mapper).
if (DescriptorVisibilities.isPrivate(visibility) || isStatic || overriddenSymbols.isEmpty())
return false
// None of the methods of Any have type parameters and so we will not need bridges for them.
if (isMethodOfAny())
return false
// We don't produce bridges for abstract functions in interfaces.
if (isJvmAbstract(context.state.jvmDefaultMode)) {
if (parentAsClass.isJvmInterface) {
// If function requires a special bridge, we should record it for generic signatures generation.
if (specialBridgeOrNull != null) {
context.functionsWithSpecialBridges.add(this)
}
return false
}
return true
}
// Finally, the JVM backend also ignores concrete fake overrides whose implementation is directly inherited from an interface.
// This is sound, since we do not generate type-specialized versions of fake overrides and if the method
// were to override several interface methods the frontend would require a separate implementation.
return !isFakeOverride || resolvesToClass()
}
private fun makeLastParameterNullable(irFunction: IrSimpleFunction) {
val oldValueParameter = irFunction.valueParameters.last()
val newValueParameter = oldValueParameter.copyTo(irFunction, type = oldValueParameter.type.makeNullable())
@@ -224,19 +228,9 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
irFunction.body?.transform(VariableRemapper(mapOf(oldValueParameter to newValueParameter)), null)
}
private fun generateBridges() {
for (member in potentialBridgeTargets) {
val parent = member.parentAsClass
createBridges(parent, member)
}
}
private fun createBridges(irClass: IrClass, irFunction: IrSimpleFunction) {
// Track final overrides and bridges to avoid clashes
val blacklist = mutableSetOf<Method>()
val existingMethodSignatures = irClass.functions
.filter { !it.isFakeOverride && it.name == irFunction.name }
.mapTo(HashSet()) { it.jvmMethod }
// Add the current method to the blacklist if it is concrete or final.
val targetMethod = (irFunction.resolveFakeOverride() ?: irFunction).jvmMethod
@@ -249,7 +243,7 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
if (specialBridge != null) {
// If the current function overrides a special bridge then it's possible that we already generated a final
// bridge methods in a superclass.
blacklist += irFunction.overriddenFinalSpecialBridges()
blacklist += irFunction.overriddenFinalSpecialBridgeSignatures()
fun getSpecialBridgeTargetAddingExtraBridges(): IrSimpleFunction {
// We only generate a special bridge method if it does not clash with a final method in a superclass or the current method
@@ -301,7 +295,7 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
blacklist += bridgeTarget.jvmMethod
}
if (specialBridge.signature in existingMethodSignatures) {
if (irClass.functions.any { !it.isFakeOverride && it.name == irFunction.name && specialBridge.signature == it.jvmMethod }) {
return irFunction
}
@@ -333,6 +327,21 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
return
}
// For concrete fake overrides, some of the bridges may be inherited from the super-classes. Specifically, bridges for all
// declarations that are reachable from all concrete immediate super-functions of the given function. Note that all such bridges are
// guaranteed to delegate to the same implementation as bridges for the given function, that's why it's safe to inherit them.
//
// This can still break binary compatibility, but it matches the behavior of the JVM backend.
if (irFunction.isFakeOverride) {
for (overriddenSymbol in irFunction.overriddenSymbols) {
val override = overriddenSymbol.owner
if (override.isJvmAbstract(context.state.jvmDefaultMode)) continue
override.allOverridden()
.filter { !it.isFakeOverride }
.mapTo(blacklist) { it.jvmMethod }
}
}
// Generate common bridges
val generated = mutableMapOf<Method, Bridge>()
@@ -341,49 +350,36 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
val signature = override.jvmMethod
if (targetMethod != signature && signature !in blacklist) {
generated.getOrPut(signature) {
Bridge(override, signature)
}.overriddenSymbols += override.symbol
val bridge = generated.getOrPut(signature) { Bridge(override, signature) }
bridge.overriddenSymbols += override.symbol
}
}
if (generated.isEmpty())
return
// For concrete fake overrides, some of the bridges may be inherited from the super-classes. Specifically, bridges for all
// declarations that are reachable from all concrete immediate super-functions of the given function. Note that all such bridges are
// guaranteed to delegate to the same implementation as bridges for the given function, that's why it's safe to inherit them.
//
// This can still break binary compatibility, but it matches the behavior of the JVM backend.
if (irFunction.isFakeOverride) {
irFunction.overriddenSymbols.asSequence()
.map { it.owner }.filter { !it.isJvmAbstract(context.state.jvmDefaultMode) }
.forEach { override ->
override.allOverridden()
.filter { !it.isFakeOverride }
.mapTo(blacklist) { it.jvmMethod }
}
}
generated.values.filter { it.signature !in blacklist }.forEach { irClass.addBridge(it, bridgeTarget) }
generated.values
.filter { it.signature !in blacklist }
.forEach { irClass.addBridge(it, bridgeTarget) }
}
// Returns the special bridge overridden by the current methods if it exists.
private val IrSimpleFunction.specialBridgeOrNull: SpecialBridge?
get() = context.bridgeLoweringCache.computeSpecialBridge(this)
private fun IrSimpleFunction.overriddenFinalSpecialBridges(): List<Method> = allOverridden().mapNotNullTo(mutableListOf()) {
// Ignore special bridges in interfaces or Java classes. While we never generate special bridges in Java
// classes, we may generate special bridges in interfaces for methods annotated with @JvmDefault.
// However, these bridges are not final and are thus safe to override.
//
// This matches the behavior of the JVM backend, but it's probably a bad idea since this is an
// opportunity for a Java and Kotlin implementation of the same interface to go out of sync.
if (it.parentAsClass.isInterface || it.isFromJava())
null
else
it.specialBridgeOrNull?.signature?.takeIf { bridgeSignature -> bridgeSignature != it.jvmMethod }
}
private fun IrSimpleFunction.overriddenFinalSpecialBridgeSignatures(): List<Method> =
allOverridden().mapNotNullTo(mutableListOf()) {
// Ignore special bridges in interfaces or Java classes. While we never generate special bridges in Java
// classes, we may generate special bridges in interfaces for methods annotated with @JvmDefault.
// However, these bridges are not final and are thus safe to override.
//
// This matches the behavior of the JVM backend, but it's probably a bad idea since this is an
// opportunity for a Java and Kotlin implementation of the same interface to go out of sync.
if (it.parentAsClass.isInterface || it.isFromJava())
null
else
it.specialBridgeOrNull?.signature?.takeIf { bridgeSignature -> bridgeSignature != it.jvmMethod }
}
// List of special bridge methods which were not implemented in Kotlin superclasses.
private fun IrSimpleFunction.overriddenSpecialBridges(): List<SpecialBridge> {
@@ -578,13 +574,23 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
bridge: IrSimpleFunction,
target: IrSimpleFunction,
superQualifierSymbol: IrClassSymbol? = null
) = irCastIfNeeded(irCall(target, origin = IrStatementOrigin.BRIDGE_DELEGATION, superQualifierSymbol = superQualifierSymbol).apply {
for ((param, targetParam) in bridge.explicitParameters.zip(target.explicitParameters)) {
putArgument(targetParam, irGet(param).let { argument ->
if (param == bridge.dispatchReceiverParameter) argument else irCastIfNeeded(argument, targetParam.type.upperBound)
})
}
}, bridge.returnType.upperBound)
) =
irCastIfNeeded(
irCall(target, origin = IrStatementOrigin.BRIDGE_DELEGATION, superQualifierSymbol = superQualifierSymbol).apply {
for ((param, targetParam) in bridge.explicitParameters.zip(target.explicitParameters)) {
putArgument(
targetParam,
irGet(param).let { argument ->
if (param == bridge.dispatchReceiverParameter)
argument
else
irCastIfNeeded(argument, targetParam.type.upperBound)
}
)
}
},
bridge.returnType.upperBound
)
private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, to: IrType): IrExpression =
if (expression.type == to || to.isAny() || to.isNullableAny()) expression else irImplicitCast(expression, to)
@@ -682,5 +688,3 @@ private fun IrSimpleFunction.resolvesToClass(): Boolean {
private fun IrSimpleFunction.overriddenFromClass(): IrSimpleFunction? =
overriddenSymbols.singleOrNull { !it.owner.parentAsClass.isJvmInterface }?.owner
private val IrType.isInlineClassErasingToAny: Boolean
get() = unboxInlineClass().let { unboxed -> unboxed != this && (unboxed.isAny() || unboxed.isNullableAny()) }