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:
+90
-86
@@ -6,14 +6,16 @@
|
|||||||
package org.jetbrains.kotlin.backend.jvm.lower
|
package org.jetbrains.kotlin.backend.jvm.lower
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
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.lower.*
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||||
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
|
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.*
|
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.codegen.AsmUtil
|
||||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
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.util.*
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
import org.jetbrains.kotlin.name.Name
|
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.Type
|
||||||
import org.jetbrains.org.objectweb.asm.commons.Method
|
import org.jetbrains.org.objectweb.asm.commons.Method
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@@ -158,12 +160,8 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
val isOverriding: Boolean = true,
|
val isOverriding: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val potentialBridgeTargets = mutableListOf<IrSimpleFunction>()
|
|
||||||
|
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irFile: IrFile) {
|
||||||
irFile.transformChildrenVoid()
|
irFile.transformChildrenVoid()
|
||||||
generateBridges()
|
|
||||||
potentialBridgeTargets.clear()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitClass(declaration: IrClass): IrStatement {
|
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)
|
if (declaration.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS || declaration.isAnnotationClass)
|
||||||
return super.visitClass(declaration)
|
return super.visitClass(declaration)
|
||||||
|
|
||||||
declaration.functions.filterTo(potentialBridgeTargets, fun(irFunction: IrSimpleFunction): Boolean {
|
val bridgeTargets = declaration.functions.filterTo(SmartList()) { it.isPotentialBridgeTarget() }
|
||||||
// Only overrides may need bridges and so in particular, private and static functions do not.
|
if (bridgeTargets.isEmpty())
|
||||||
// Note that this includes the static replacements for inline class functions (which are static, but have
|
return super.visitClass(declaration)
|
||||||
// overriddenSymbols in order to produce correct signatures in the type mapper).
|
|
||||||
if (DescriptorVisibilities.isPrivate(irFunction.visibility) || irFunction.isStatic || irFunction.overriddenSymbols.isEmpty())
|
|
||||||
return false
|
|
||||||
|
|
||||||
// None of the methods of Any have type parameters and so we will not need bridges for them.
|
bridgeTargets.forEach { createBridges(declaration, it) }
|
||||||
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()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (declaration.isInline) {
|
if (declaration.isInline) {
|
||||||
// Inline class (implementing 'MutableCollection<T>', where T is Int or an inline class mapped to Int)
|
// 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)
|
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) {
|
private fun makeLastParameterNullable(irFunction: IrSimpleFunction) {
|
||||||
val oldValueParameter = irFunction.valueParameters.last()
|
val oldValueParameter = irFunction.valueParameters.last()
|
||||||
val newValueParameter = oldValueParameter.copyTo(irFunction, type = oldValueParameter.type.makeNullable())
|
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)
|
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) {
|
private fun createBridges(irClass: IrClass, irFunction: IrSimpleFunction) {
|
||||||
// Track final overrides and bridges to avoid clashes
|
// Track final overrides and bridges to avoid clashes
|
||||||
val blacklist = mutableSetOf<Method>()
|
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.
|
// Add the current method to the blacklist if it is concrete or final.
|
||||||
val targetMethod = (irFunction.resolveFakeOverride() ?: irFunction).jvmMethod
|
val targetMethod = (irFunction.resolveFakeOverride() ?: irFunction).jvmMethod
|
||||||
@@ -249,7 +243,7 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
if (specialBridge != null) {
|
if (specialBridge != null) {
|
||||||
// If the current function overrides a special bridge then it's possible that we already generated a final
|
// If the current function overrides a special bridge then it's possible that we already generated a final
|
||||||
// bridge methods in a superclass.
|
// bridge methods in a superclass.
|
||||||
blacklist += irFunction.overriddenFinalSpecialBridges()
|
blacklist += irFunction.overriddenFinalSpecialBridgeSignatures()
|
||||||
|
|
||||||
fun getSpecialBridgeTargetAddingExtraBridges(): IrSimpleFunction {
|
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
|
// 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
|
blacklist += bridgeTarget.jvmMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
if (specialBridge.signature in existingMethodSignatures) {
|
if (irClass.functions.any { !it.isFakeOverride && it.name == irFunction.name && specialBridge.signature == it.jvmMethod }) {
|
||||||
return irFunction
|
return irFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +327,21 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
return
|
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
|
// Generate common bridges
|
||||||
val generated = mutableMapOf<Method, Bridge>()
|
val generated = mutableMapOf<Method, Bridge>()
|
||||||
|
|
||||||
@@ -341,49 +350,36 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
|
|
||||||
val signature = override.jvmMethod
|
val signature = override.jvmMethod
|
||||||
if (targetMethod != signature && signature !in blacklist) {
|
if (targetMethod != signature && signature !in blacklist) {
|
||||||
generated.getOrPut(signature) {
|
val bridge = generated.getOrPut(signature) { Bridge(override, signature) }
|
||||||
Bridge(override, signature)
|
bridge.overriddenSymbols += override.symbol
|
||||||
}.overriddenSymbols += override.symbol
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (generated.isEmpty())
|
if (generated.isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
// For concrete fake overrides, some of the bridges may be inherited from the super-classes. Specifically, bridges for all
|
generated.values
|
||||||
// declarations that are reachable from all concrete immediate super-functions of the given function. Note that all such bridges are
|
.filter { it.signature !in blacklist }
|
||||||
// guaranteed to delegate to the same implementation as bridges for the given function, that's why it's safe to inherit them.
|
.forEach { irClass.addBridge(it, bridgeTarget) }
|
||||||
//
|
|
||||||
// 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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the special bridge overridden by the current methods if it exists.
|
// Returns the special bridge overridden by the current methods if it exists.
|
||||||
private val IrSimpleFunction.specialBridgeOrNull: SpecialBridge?
|
private val IrSimpleFunction.specialBridgeOrNull: SpecialBridge?
|
||||||
get() = context.bridgeLoweringCache.computeSpecialBridge(this)
|
get() = context.bridgeLoweringCache.computeSpecialBridge(this)
|
||||||
|
|
||||||
private fun IrSimpleFunction.overriddenFinalSpecialBridges(): List<Method> = allOverridden().mapNotNullTo(mutableListOf()) {
|
private fun IrSimpleFunction.overriddenFinalSpecialBridgeSignatures(): List<Method> =
|
||||||
// Ignore special bridges in interfaces or Java classes. While we never generate special bridges in Java
|
allOverridden().mapNotNullTo(mutableListOf()) {
|
||||||
// classes, we may generate special bridges in interfaces for methods annotated with @JvmDefault.
|
// Ignore special bridges in interfaces or Java classes. While we never generate special bridges in Java
|
||||||
// However, these bridges are not final and are thus safe to override.
|
// 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.
|
// This matches the behavior of the JVM backend, but it's probably a bad idea since this is an
|
||||||
if (it.parentAsClass.isInterface || it.isFromJava())
|
// opportunity for a Java and Kotlin implementation of the same interface to go out of sync.
|
||||||
null
|
if (it.parentAsClass.isInterface || it.isFromJava())
|
||||||
else
|
null
|
||||||
it.specialBridgeOrNull?.signature?.takeIf { bridgeSignature -> bridgeSignature != it.jvmMethod }
|
else
|
||||||
}
|
it.specialBridgeOrNull?.signature?.takeIf { bridgeSignature -> bridgeSignature != it.jvmMethod }
|
||||||
|
}
|
||||||
|
|
||||||
// List of special bridge methods which were not implemented in Kotlin superclasses.
|
// List of special bridge methods which were not implemented in Kotlin superclasses.
|
||||||
private fun IrSimpleFunction.overriddenSpecialBridges(): List<SpecialBridge> {
|
private fun IrSimpleFunction.overriddenSpecialBridges(): List<SpecialBridge> {
|
||||||
@@ -578,13 +574,23 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
bridge: IrSimpleFunction,
|
bridge: IrSimpleFunction,
|
||||||
target: IrSimpleFunction,
|
target: IrSimpleFunction,
|
||||||
superQualifierSymbol: IrClassSymbol? = null
|
superQualifierSymbol: IrClassSymbol? = null
|
||||||
) = irCastIfNeeded(irCall(target, origin = IrStatementOrigin.BRIDGE_DELEGATION, superQualifierSymbol = superQualifierSymbol).apply {
|
) =
|
||||||
for ((param, targetParam) in bridge.explicitParameters.zip(target.explicitParameters)) {
|
irCastIfNeeded(
|
||||||
putArgument(targetParam, irGet(param).let { argument ->
|
irCall(target, origin = IrStatementOrigin.BRIDGE_DELEGATION, superQualifierSymbol = superQualifierSymbol).apply {
|
||||||
if (param == bridge.dispatchReceiverParameter) argument else irCastIfNeeded(argument, targetParam.type.upperBound)
|
for ((param, targetParam) in bridge.explicitParameters.zip(target.explicitParameters)) {
|
||||||
})
|
putArgument(
|
||||||
}
|
targetParam,
|
||||||
}, bridge.returnType.upperBound)
|
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 =
|
private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, to: IrType): IrExpression =
|
||||||
if (expression.type == to || to.isAny() || to.isNullableAny()) expression else irImplicitCast(expression, to)
|
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? =
|
private fun IrSimpleFunction.overriddenFromClass(): IrSimpleFunction? =
|
||||||
overriddenSymbols.singleOrNull { !it.owner.parentAsClass.isJvmInterface }?.owner
|
overriddenSymbols.singleOrNull { !it.owner.parentAsClass.isJvmInterface }?.owner
|
||||||
|
|
||||||
private val IrType.isInlineClassErasingToAny: Boolean
|
|
||||||
get() = unboxInlineClass().let { unboxed -> unboxed != this && (unboxed.isAny() || unboxed.isNullableAny()) }
|
|
||||||
|
|||||||
Reference in New Issue
Block a user