JVM IR: Fix BridgeLowering

This commit is contained in:
Steven Schäfer
2020-02-03 14:56:38 +01:00
committed by Georgy Bronnikov
parent 461d8ef368
commit 07737f8fc6
11 changed files with 391 additions and 422 deletions
@@ -15,7 +15,9 @@ import org.jetbrains.kotlin.backend.jvm.descriptors.JvmDeclarationFactory
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.inline.coroutines.FOR_INLINE_SUFFIX
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.deserialization.PLATFORM_DEPENDENT_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.Scope
@@ -142,6 +144,7 @@ val IrFunction.propertyIfAccessor: IrDeclaration
get() = (this as? IrSimpleFunction)?.correspondingPropertySymbol?.owner ?: this
fun IrFunction.hasJvmDefault(): Boolean = propertyIfAccessor.hasAnnotation(JVM_DEFAULT_FQ_NAME)
fun IrFunction.hasPlatformDependent(): Boolean = propertyIfAccessor.hasAnnotation(PLATFORM_DEPENDENT_ANNOTATION_FQ_NAME)
fun IrFunction.getJvmVisibilityOfDefaultArgumentStub() =
if (Visibilities.isPrivate(visibility) || isInlineOnly()) JavaVisibilities.PACKAGE_VISIBILITY else Visibilities.PUBLIC
@@ -219,7 +222,6 @@ fun IrBody.replaceThisByStaticReference(
}
}, null)
// TODO: Interface Parameters
//
// The call sites using this function share that they are calling an
@@ -270,3 +272,12 @@ fun IrFunctionAccessExpression.copyFromWithPlaceholderTypeArguments(existingCall
putTypeArgument(i + offset, existingCall.getTypeArgument(i))
}
}
// Check whether a function maps to an abstract method.
// For non-interface methods or interface methods coming from Java the modality is correct. Kotlin interface methods
// are abstract unless they are annotated with @JvmDefault or @PlatformDependent or they override a method with
// such an annotation.
val IrSimpleFunction.isJvmAbstract: Boolean
get() = (modality == Modality.ABSTRACT) ||
(parentAsClass.isJvmInterface && !hasJvmDefault() && !hasPlatformDependent()
&& (!isFakeOverride || overriddenSymbols.all { it.owner.isJvmAbstract }))
@@ -5,287 +5,379 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.bridges.FunctionHandle
import org.jetbrains.kotlin.backend.common.bridges.findAllReachableDeclarations
import org.jetbrains.kotlin.backend.common.bridges.findConcreteSuperDeclaration
import org.jetbrains.kotlin.backend.common.bridges.generateBridges
import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.common.FileLoweringPass
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.eraseTypeParameters
import org.jetbrains.kotlin.backend.jvm.ir.hasJvmDefault
import org.jetbrains.kotlin.backend.jvm.ir.isJvmAbstract
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
import org.jetbrains.kotlin.ir.descriptors.WrappedReceiverParameterDescriptor
import org.jetbrains.kotlin.ir.descriptors.WrappedSimpleFunctionDescriptor
import org.jetbrains.kotlin.ir.descriptors.WrappedValueParameterDescriptor
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrExpressionBody
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
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.resolve.DescriptorUtils
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.getOrPutNullable
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
/*
* Generate bridge methods to fix virtual dispatch after type erasure and to adapt Kotlin collections to
* the Java collection interfaces. For example, consider the following Kotlin declaration
*
* interface I<T> { fun f(): T }
* abstract class A : MutableCollection<Int>, I<String> {
* override fun f(): String = "OK"
* override fun contains(o: Int): Boolean = false
* }
*
* After type erasure we essentially have the following definitions.
*
* interface I { fun f(): java.lang.Object }
* abstract class A : java.util.Collection, I {
* fun f(): java.lang.String = "OK"
* fun contains(o: Int): Boolean = false
* }
*
* In particular, method `A.f` no longer overrides method `I.f`, since the return types do not match.
* This is why we have to introduce a bridge method into `A.f` to redirect calls from `I.f` to `A.f` and
* to add type casts as needed.
*
* The second source of bridge methods in Kotlin are so-called special bridges, which mediate between
* the Kotlin and Java collection interfaces. Note that we map the type `MutableCollection` to its
* Java equivalent `java.util.Collection`. However, there is a mismatch in naming conventions and
* signatures between the Java and Kotlin version. For example, the method `contains` has signature
*
* interface kotlin.Collection<T> {
* fun contains(element: T): Boolean
* ...
* }
*
* in Kotlin, but a different signature
*
* interface java.util.Collection<T> {
* fun contains(element: java.lang.Object): Boolean
* ...
* }
*
* in Java. In particular, the Java version is not type-safe: it requires us to implement the method
* given arbitrary objects, even though we know based on the types that our collection can only contain
* members of type `T`. This is why we have to introduce type-safe wrappers into Kotlin collection classes.
* In the example above, we produce:
*
* abstract class A : java.util.Collection, I {
* ...
* fun contains(element: java.lang.Object): Boolean {
* if (element !is Int) return false
* return contains(element as Int)
* }
*
* fun contains(o: Int): Boolean = false
* }
*
* Similarly, the naming conventions sometimes differ between the Java interfaces and their Kotlin counterparts.
* Sticking with the example above, we find that `java.util.Collection` contains a method `fun size(): Int`,
* which maps to a Kotlin property `val size: Int`. The latter is compiled to a method `fun getSize(): Int` and
* we introduce a bridge to map calls from `size()` to `getSize()`.
*
* Finally, while bridges due to type erasure are marked as synthetic, we need special bridges to be visible to
* the Java compiler. After all, special bridges are the implementation methods for some Java interfaces. If
* they were synthetic, they would be invisible to javac and it would complain that a Kotlin collection implementation
* class does not implement all of its interfaces. Similarly, special bridges should be final, since otherwise
* a user coming from Java might override their implementation, leading to the Kotlin and Java collection
* implementations getting out of sync.
*
* In the other direction, it is possible that a user would reimplement a Kotlin collection in Java.
* In order to guarantee binary compatibility, we remap all calls to Kotlin collection methods to
* their Java equivalents instead.
*
* Apart from these complications, bridge generation is conceptually simple: For a given Kotlin method we
* generate bridges for all overridden methods with different signatures, unless a final method with
* the same signature already exists in a superclass. We only diverge from this idea to match the behavior of
* the JVM backend in a few corner cases.
*/
internal val bridgePhase = makeIrFilePhase(
::BridgeLowering,
name = "Bridge",
description = "Generate bridges"
)
private class BridgeLowering(val context: JvmBackendContext) : ClassLoweringPass {
private val methodSignatureMapper = context.methodSignatureMapper
private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoid() {
// Represents a synthetic bridge to `overridden` with a precomputed signature
private class Bridge(val overridden: IrSimpleFunction, val signature: Method)
// Represents a special bridge to `overridden`. Special bridges are always public and non-synthetic.
// There are ten type-safe wrappers for different collection methods, which have an additional `methodInfo`
// field. There is only one special bridge method which uses generic types in Java (MutableList.removeAt)
// and needs a `specializedReturnType` for its overrides. Finally, we sometimes need to use INVOKESPECIAL
// to invoke an existing special bridge implementation in a superclass, which is what `superQualifierSymbol`
// is for.
private data class SpecialBridge(
val overridden: IrSimpleFunction,
val signature: Method,
val specializedReturnType: IrType? = null,
val methodInfo: SpecialMethodWithDefaultInfo? = null,
val superQualifierSymbol: IrClassSymbol? = null
)
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid()
override fun visitClass(declaration: IrClass): IrStatement {
// Bridges in DefaultImpl classes are handled in InterfaceLowering.
if (declaration.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS || declaration.isAnnotationClass)
return super.visitClass(declaration)
// We generate bridges directly in the class, so we make a copy of the list of relevant members.
val potentialBridgeTargets = declaration.functions.filterTo(mutableListOf(), 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 (Visibilities.isPrivate(irFunction.visibility) || irFunction.isStatic || irFunction.overriddenSymbols.isEmpty())
return false
// We ignore (fake overrides of) default argument stubs and methods of Any.
// Default argument stubs only appear in the base class and are synthetic, so there is no reason to produce
// bridges for them. Similarly, none of the methods of Any have type parameters and so we will not need bridges
// for them.
if (irFunction.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER || irFunction.isMethodOfAny())
return false
// We don't produce bridges for abstract functions in interfaces.
if (irFunction.isJvmAbstract)
return !irFunction.parentAsClass.isJvmInterface
// Finally, the JVM backend also ignores concrete fake overrides which are implemented in interfaces.
// 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.
//
// In addition, there are @PlatformDependent methods which only exist on newer JDK versions
// (MutableMap.remove and getOrDefault). Trying to produce (special) bridges for these methods could
// result in incorrect bytecode on older JVM versions. However, all such methods are declared
// in interfaces and thus we don't need a separate check for them.
return !irFunction.isFakeOverride || !irFunction.resolveFakeOverride()!!.parentAsClass.isJvmInterface
})
for (member in potentialBridgeTargets) {
createBridges(declaration, member)
// For lambda classes, we move overrides from the `invoke` function to its bridge. This will allow us to avoid boxing
// the return type of `invoke` in codegen for lambdas with primitive return type.
if (member.name == OperatorNameConventions.INVOKE && declaration.origin == JvmLoweredDeclarationOrigin.LAMBDA_IMPL) {
member.overriddenSymbols = listOf()
}
}
return super.visitClass(declaration)
}
private fun createBridges(irClass: IrClass, irFunction: IrSimpleFunction) {
// Track final overrides and bridges to avoid clashes
val blacklist = mutableSetOf<Method>()
// Add the current method to the blacklist if it is concrete or final.
val targetMethod = (irFunction.resolveFakeOverride() ?: irFunction).jvmMethod
if (!irFunction.isFakeOverride || irFunction.modality == Modality.FINAL)
blacklist += targetMethod
// Generate special bridges
val specialBridge = irFunction.specialBridgeOrNull
var bridgeTarget = irFunction
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()
// We only generate a special bridge method if it does not clash with a final method in a superclass or the current method
if (specialBridge.signature !in blacklist && (!irFunction.isFakeOverride || irFunction.jvmMethod != specialBridge.signature)) {
// If irFunction is a fake override, we replace it with a stub and redirect all calls to irFunction with
// calls to the stub instead. Otherwise we'll end up calling the special method itself and get into an
// infinite loop.
//
// There are three cases to consider. If the method is abstract, then we simply generate a concrete abstract method
// to avoid generating a call to a method which does not exist in the current class. If the method is final,
// then we will not override it in a subclass and we do not need to generate an additional stub method.
//
// Finally, if we have a non-abstract, non-final fake-override we need to put in an additional bridge which uses
// INVOKESPECIAL to call the special bridge implementation in the superclass. We can be sure that an implementation
// exists in a superclass, since we do not generate bridges for fake overrides of interface methods.
if (irFunction.isFakeOverride) {
bridgeTarget = when {
irFunction.isJvmAbstract -> {
irClass.declarations.remove(irFunction)
irClass.addAbstractMethodStub(irFunction)
}
irFunction.modality != Modality.FINAL -> {
val superTarget = irFunction.overriddenSymbols.first { !it.owner.parentAsClass.isInterface }.owner
val superBridge = SpecialBridge(
irFunction, irFunction.jvmMethod, superQualifierSymbol = superTarget.parentAsClass.symbol,
methodInfo = specialBridge.methodInfo?.copy(argumentsToCheck = 0) // For potential argument boxing
)
irClass.declarations.remove(irFunction)
irClass.addSpecialBridge(superBridge, superTarget)
}
else -> irFunction
}
blacklist += bridgeTarget.jvmMethod
}
irClass.addSpecialBridge(specialBridge, bridgeTarget)
blacklist += specialBridge.signature
}
// Deal with existing function that override special bridge methods.
if (!irFunction.isFakeOverride && specialBridge.methodInfo != null) {
irFunction.rewriteSpecialMethodBody(targetMethod, specialBridge.signature, specialBridge.methodInfo)
}
} else if (irFunction.isJvmAbstract) {
// Do not generate bridge methods for abstract methods which do not override a special bridge method.
// This matches the behavior of the JVM backend, but it does mean that we generate superfluous bridges
// for abstract methods overriding a special bridge for which we do not create a bridge due to,
// e.g., signature clashes.
return
}
// Generate common bridges
val generated = mutableMapOf<Method, Bridge>()
irFunction.allOverridden().filter { !it.isFakeOverride }.forEach { override ->
val signature = override.jvmMethod
if (targetMethod != signature && signature !in generated && signature !in blacklist) {
generated[signature] = Bridge(override, signature)
}
}
// 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 }.forEach { override ->
override.allOverridden().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.
private val IrSimpleFunction.specialBridgeOrNull: SpecialBridge?
get() = specialBridgeCache.getOrPutNullable(symbol) {
val specialMethodInfo = specialBridgeMethods.getSpecialMethodInfo(this)
return when {
specialMethodInfo != null ->
// Note that there are type-safe special bridges with generic return types in Java (namely Map.getOrDefault,
// Map.get, and MutableMap.remove), but the JVM backend does not produce overrides with specialized return
// types for them. So for compatibility, neither do we.
SpecialBridge(this, jvmMethod, methodInfo = specialMethodInfo)
specialBridgeMethods.isBuiltInWithDifferentJvmName(this) ->
if (returnType.isTypeParameter())
SpecialBridge(this, jvmMethod, specializedReturnType = returnType)
else
SpecialBridge(this, jvmMethod)
else -> {
val overriddenSpecialBridge = overriddenSymbols.asSequence().mapNotNull { it.owner.specialBridgeOrNull }.firstOrNull()
if (overriddenSpecialBridge?.specializedReturnType != null) {
val specializedSignature = Method(
overriddenSpecialBridge.signature.name,
context.methodSignatureMapper.mapReturnType(this),
overriddenSpecialBridge.signature.argumentTypes
)
overriddenSpecialBridge.copy(signature = specializedSignature, specializedReturnType = returnType)
} else {
overriddenSpecialBridge
}
}
}
}
private val specialBridgeMethods = SpecialBridgeMethods(context)
private val specialBridgeCache = mutableMapOf<IrSimpleFunctionSymbol, SpecialBridge?>()
override fun lower(irClass: IrClass) {
if (irClass.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS) {
return
}
for (member in irClass.declarations.filterIsInstance<IrSimpleFunction>()) {
if (!irClass.isInterface || member.hasJvmDefault())
createBridges(member)
}
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.comesFromJava())
null
else
it.specialBridgeOrNull?.signature?.takeIf { bridgeSignature -> bridgeSignature != it.jvmMethod }
}
private fun createBridges(irFunction: IrSimpleFunction) {
if (irFunction.isStatic) return
if (irFunction.isMethodOfAny()) return
if (irFunction.origin === IrDeclarationOrigin.FAKE_OVERRIDE &&
irFunction.overriddenSymbols.all {
!it.owner.comesFromJava() &&
if ((it.owner.parent as? IrClass)?.isInterface == true)
it.owner.hasJvmDefault() // TODO: Remove this after modality is corrected in InterfaceLowering.
else
it.owner.modality !== Modality.ABSTRACT
}
) {
// All needed bridges will be generated where functions are implemented.
return
private fun IrClass.addAbstractMethodStub(irFunction: IrSimpleFunction) =
addFunction {
updateFrom(irFunction)
modality = Modality.ABSTRACT
origin = IrDeclarationOrigin.DEFINED
name = irFunction.name
returnType = irFunction.returnType
}.apply {
dispatchReceiverParameter = thisReceiver?.copyTo(this, type = defaultType)
extensionReceiverParameter = irFunction.extensionReceiverParameter?.copyTo(this)
valueParameters = irFunction.valueParameters.map { it.copyTo(this) }
}
val irClass = irFunction.parentAsClass
val ourSignature = irFunction.getJvmSignature()
val ourMethodName = ourSignature.name
val (specialOverride, specialOverrideInfo) =
specialBridgeMethods.findSpecialWithOverride(irFunction) ?: Pair(null, null)
val specialOverrideSignature = specialOverride?.getJvmSignature()
var targetForCommonBridges = irFunction
// Special case: fake override redirecting to an implementation with a different JVM name,
// or to a function with SpecialOverrideSignature.
// TODO: we assume here that all implementations come from classes. There may be a default implementation in
// an interface, If it comes from the same module, InterfaceDelegationLowering will build a redirection, and the following code will work.
// But in an imported module, there will be no redirection => failure!
if (irFunction.origin === IrDeclarationOrigin.FAKE_OVERRIDE &&
irFunction.modality !== Modality.ABSTRACT &&
irFunction.visibility !== Visibilities.INVISIBLE_FAKE &&
irFunction.overriddenInClasses().firstOrNull { it.getJvmSignature() != ourSignature || it.origin != IrDeclarationOrigin.FAKE_OVERRIDE }
?.let { (it.getJvmName() != ourMethodName || it.getJvmSignature() == specialOverrideSignature) && it.isExternalDeclaration() } == true
) {
val resolved = irFunction.findConcreteSuperDeclaration()!!
val resolvedSignature = resolved.getJvmSignature()
if (!resolvedSignature.sameCallAs(ourSignature)) {
val bridge = createBridgeHeader(irClass, resolved, irFunction, isSpecial = false, isSynthetic = false)
bridge.createBridgeBody(resolved, null, isSpecial = false, invokeStatically = true)
irClass.declarations.add(bridge)
targetForCommonBridges = bridge
}
} else if (irFunction.origin == IrDeclarationOrigin.FAKE_OVERRIDE &&
irFunction.modality == Modality.ABSTRACT &&
irFunction.overriddenSymbols.all { it.owner.getJvmName() != ourMethodName }
) {
// Bridges for abstract fake overrides whose JVM names differ from overridden functions.
// The orphaned copy will get irFunction's name; the original irFunction will be renamed
// according to its overrides,
val bridge = irFunction.orphanedCopy()
irClass.declarations.add(bridge)
targetForCommonBridges = bridge
private fun IrClass.addBridge(bridge: Bridge, target: IrSimpleFunction): IrSimpleFunction =
addFunction {
modality = Modality.OPEN
origin = IrDeclarationOrigin.BRIDGE
// Internal functions can be overridden by non-internal functions, which changes their names since the names of internal
// functions are mangled. In order to avoid mangling the name twice we reset the visibility for bridges to internal
// functions to public and use the mangled name directly.
visibility = bridge.overridden.visibility.takeUnless { it == Visibilities.INTERNAL } ?: Visibilities.PUBLIC
name = Name.identifier(bridge.signature.name)
returnType = bridge.overridden.returnType.eraseTypeParameters()
isSuspend = bridge.overridden.isSuspend
}.apply {
copyParametersWithErasure(this@addBridge, bridge.overridden)
body = context.createIrBuilder(symbol).run { irExprBody(delegatingCall(this@apply, target)) }
overriddenSymbols += bridge.overridden.symbol
}
val signaturesToSkip =
if (irFunction.origin != IrDeclarationOrigin.FAKE_OVERRIDE || irFunction.modality == Modality.ABSTRACT || targetForCommonBridges != irFunction)
mutableSetOf(ourSignature)
else
mutableSetOf()
signaturesToSkip += getFinalOverridden(irFunction).map { it.getJvmSignature() }
val firstOverridden = irFunction.overriddenInClasses().firstOrNull()
val firstOverriddenSignature = firstOverridden?.getJvmSignature()
val renamedOverridden = getRenamedOverridden(irFunction)
if (renamedOverridden != null) {
val renamer = irFunction.copyRenamingTo(Name.identifier(renamedOverridden.getJvmName()))
// Renaming bridge may have already been generated in parent.
if (firstOverridden == null || firstOverriddenSignature!!.name != ourMethodName || getRenamedOverridden(firstOverridden) == null) {
addBridge(
irClass, targetForCommonBridges, renamer, signaturesToSkip,
specialOverrideInfo = null,
isSpecial = true
)
} else {
// Renamer bridge in superclass.
signaturesToSkip.add(renamer.getJvmSignature())
}
}
if (specialOverride != null && (firstOverridden == null || firstOverriddenSignature != ourSignature) &&
specialOverrideSignature !in signaturesToSkip
) {
addBridge(
irClass, targetForCommonBridges, specialOverride, signaturesToSkip,
specialOverrideInfo,
isSpecial = true
private fun IrClass.addSpecialBridge(specialBridge: SpecialBridge, target: IrSimpleFunction): IrSimpleFunction =
addFunction {
// FIXME: This seems to be a work-around for superfluous specialized collection stubs
modality = if (!target.isCollectionStub()) Modality.FINAL else Modality.OPEN
origin = IrDeclarationOrigin.BRIDGE_SPECIAL
name = Name.identifier(specialBridge.signature.name)
returnType = specialBridge.specializedReturnType ?: specialBridge.overridden.returnType.eraseTypeParameters()
}.apply {
copyParametersWithErasure(
this@addSpecialBridge,
specialBridge.overridden,
specialBridge.methodInfo?.needsArgumentBoxing == true
)
}
// Deal with existing function that override special bridge methods.
if (!irFunction.isFakeOverride && specialOverride != null) {
irFunction.rewriteSpecialMethodBody(ourSignature, specialOverrideSignature!!, specialOverrideInfo!!)
}
val bridgeSignatures = generateBridges(
FunctionHandleForIrFunction(irFunction),
{ handle -> SignatureWithSource(handle.irFunction.getJvmSignature(), handle.irFunction) }
)
for (bridgeSignature in bridgeSignatures) {
val method = bridgeSignature.from.source
addBridge(
irClass, targetForCommonBridges, method, signaturesToSkip,
specialOverrideInfo = null,
isSpecial = targetForCommonBridges.isCollectionStub()
)
}
}
private fun addBridge(
irClass: IrClass,
target: IrSimpleFunction,
method: IrSimpleFunction,
signaturesToSkip: MutableSet<Method>,
specialOverrideInfo: SpecialMethodWithDefaultInfo?,
isSpecial: Boolean
) {
val signature = method.getJvmSignature()
if (signature in signaturesToSkip) return
val bridge = createBridgeHeader(irClass, target, method, isSpecial = isSpecial, isSynthetic = !isSpecial)
bridge.createBridgeBody(target, specialOverrideInfo, isSpecial)
irClass.declarations.add(bridge)
// For lambda classes, we move override from the `invoke` function to its bridge. This will allow us to avoid boxing
// the return type of `invoke` in codegen, in case lambda's return type is primitive.
if (method.name == OperatorNameConventions.INVOKE && irClass.origin == JvmLoweredDeclarationOrigin.LAMBDA_IMPL) {
target.overriddenSymbols = target.overriddenSymbols.filter { it != method.symbol }
bridge.overriddenSymbols += method.symbol
}
signaturesToSkip.add(signature)
}
private fun IrSimpleFunction.copyRenamingTo(newName: Name): IrSimpleFunction =
WrappedSimpleFunctionDescriptor(descriptor.annotations).let { newDescriptor ->
IrFunctionImpl(
startOffset, endOffset, origin,
IrSimpleFunctionSymbolImpl(newDescriptor),
newName,
visibility, modality, returnType,
isInline = isInline, isExternal = isExternal, isTailrec = isTailrec, isSuspend = isSuspend, isExpect = isExpect,
isFakeOverride = origin == IrDeclarationOrigin.FAKE_OVERRIDE,
isOperator = isOperator
).apply {
newDescriptor.bind(this)
parent = this@copyRenamingTo.parent
dispatchReceiverParameter = this@copyRenamingTo.dispatchReceiverParameter?.copyTo(this)
extensionReceiverParameter = this@copyRenamingTo.extensionReceiverParameter?.copyTo(this)
valueParameters = this@copyRenamingTo.valueParameters.map { it.copyTo(this) }
body = context.createIrBuilder(symbol).irBlockBody {
specialBridge.methodInfo?.let { info ->
valueParameters.take(info.argumentsToCheck).forEach {
+parameterTypeCheck(it, target.valueParameters[it.index].type, info.defaultValueGenerator(this@apply))
}
}
+irReturn(delegatingCall(this@apply, target, specialBridge.superQualifierSymbol))
}
overriddenSymbols += specialBridge.overridden.symbol
}
private fun createBridgeHeader(
irClass: IrClass,
target: IrSimpleFunction,
signatureFunction: IrSimpleFunction,
isSpecial: Boolean,
isSynthetic: Boolean
): IrSimpleFunction {
val modality = if (isSpecial && !target.isCollectionStub()) Modality.FINAL else Modality.OPEN
val origin = if (isSynthetic) IrDeclarationOrigin.BRIDGE else IrDeclarationOrigin.BRIDGE_SPECIAL
val visibility = if (signatureFunction.visibility === Visibilities.INTERNAL) Visibilities.PUBLIC else signatureFunction.visibility
val descriptor = WrappedSimpleFunctionDescriptor()
return IrFunctionImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
origin,
IrSimpleFunctionSymbolImpl(descriptor),
Name.identifier(signatureFunction.getJvmName()),
visibility,
modality,
returnType = signatureFunction.returnType.eraseTypeParameters(),
isInline = false,
isExternal = false,
isTailrec = false,
isSuspend = signatureFunction.isSuspend,
isExpect = false,
isFakeOverride = origin == IrDeclarationOrigin.FAKE_OVERRIDE,
isOperator = false
).apply {
descriptor.bind(this)
parent = irClass
copyTypeParametersFrom(target)
// Have to specify type explicitly to prevent an attempt to remap it.
dispatchReceiverParameter = irClass.thisReceiver?.copyTo(this, type = irClass.defaultType)
extensionReceiverParameter = signatureFunction.extensionReceiverParameter
?.copyWithTypeErasure(this)
valueParameters = signatureFunction.valueParameters.map { param ->
param.copyWithTypeErasure(this)
}
}
}
private fun IrStatementsBuilder<IrBlockBody>.addParameterTypeCheck(
parameter: IrValueParameter,
type: IrType,
defaultValueGenerator: ((IrSimpleFunction) -> IrExpression),
function: IrSimpleFunction
) {
+irIfThen(
context.irBuiltIns.unitType,
irNot(irIs(irGet(parameter), type)),
irReturn(defaultValueGenerator(function))
)
}
private fun IrSimpleFunction.rewriteSpecialMethodBody(
ourSignature: Method,
specialOverrideSignature: Method,
@@ -311,11 +403,10 @@ private class BridgeLowering(val context: JvmBackendContext) : ClassLoweringPass
val newParameter = it.copyTo(this@rewriteSpecialMethodBody, type = context.irBuiltIns.anyNType)
variableMap.put(valueParameters[it.index], newParameter)
newValueParameters[it.index] = newParameter
addParameterTypeCheck(
+parameterTypeCheck(
newParameter,
parameterType,
specialOverrideInfo.defaultValueGenerator,
this@rewriteSpecialMethodBody
specialOverrideInfo.defaultValueGenerator(this@rewriteSpecialMethodBody)
)
}
}
@@ -346,173 +437,50 @@ private class BridgeLowering(val context: JvmBackendContext) : ClassLoweringPass
if (variableMap.isNotEmpty()) {
body?.transform(VariableRemapper(variableMap), null)
}
}
private fun IrSimpleFunction.createBridgeBody(
private fun IrBuilderWithScope.parameterTypeCheck(parameter: IrValueParameter, type: IrType, defaultValue: IrExpression) =
irIfThen(context.irBuiltIns.unitType, irNot(irIs(irGet(parameter), type)), irReturn(defaultValue))
private fun IrSimpleFunction.copyParametersWithErasure(irClass: IrClass, from: IrSimpleFunction, forceArgumentBoxing: Boolean = false) {
// This is a workaround for a bug affecting fake overrides. Sometimes we encounter fake overrides
// with dispatch receivers pointing at a superclass instead of the current class.
dispatchReceiverParameter = irClass.thisReceiver?.copyTo(this, type = irClass.defaultType)
extensionReceiverParameter = from.extensionReceiverParameter?.copyWithTypeErasure(this, forceArgumentBoxing)
valueParameters = from.valueParameters.map { it.copyWithTypeErasure(this, forceArgumentBoxing) }
}
private fun IrValueParameter.copyWithTypeErasure(target: IrSimpleFunction, forceBoxing: Boolean = false): IrValueParameter = copyTo(
target, IrDeclarationOrigin.BRIDGE,
type =
// SuspendFunction{N} is Function{N+1} at runtime, thus, when we generate a bridge for suspend callable references,
// we need to replace the type of its continuation parameter with Any?
if (target.isSuspend && type.eraseTypeParameters().getClass()
?.fqNameWhenAvailable == DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME_RELEASE
) context.irBuiltIns.anyNType else type.eraseTypeParameters().let { if (forceBoxing) it.makeNullable() else it },
varargElementType = varargElementType?.eraseTypeParameters()
)
private fun IrBuilderWithScope.delegatingCall(
bridge: IrSimpleFunction,
target: IrSimpleFunction,
specialMethodInfo: SpecialMethodWithDefaultInfo?,
isSpecial: Boolean,
invokeStatically: Boolean = false
) {
context.createIrBuilder(symbol).run {
body = irBlockBody {
if (specialMethodInfo != null) {
valueParameters.take(specialMethodInfo.argumentsToCheck).forEach {
addParameterTypeCheck(
it,
target.valueParameters[it.index].type,
specialMethodInfo.defaultValueGenerator,
this@createBridgeBody
)
}
}
+irReturn(
irImplicitCast(
IrCallImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
target.returnType,
target.symbol, origin = IrStatementOrigin.BRIDGE_DELEGATION,
superQualifierSymbol = if (invokeStatically) target.parentAsClass.symbol else null
).apply {
passTypeArgumentsFrom(this@createBridgeBody)
dispatchReceiver = irGet(dispatchReceiverParameter!!)
extensionReceiverParameter?.let {
extensionReceiver = irImplicitCast(irGet(it), target.extensionReceiverParameter!!.type)
}
valueParameters.forEach {
putValueArgument(it.index, irImplicitCast(irGet(it), target.valueParameters[it.index].type))
}
},
returnType
)
)
}
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)
})
}
}
}, bridge.returnType)
/* A hacky way to make sure the code generator calls the right function, and not some standard interface it implements. */
private fun IrSimpleFunction.orphanedCopy() =
if (overriddenSymbols.size == 0)
this
else
WrappedSimpleFunctionDescriptor(descriptor.annotations).let { wrappedDescriptor ->
val newOrigin = if (origin == IrDeclarationOrigin.FAKE_OVERRIDE) IrDeclarationOrigin.DEFINED else origin
IrFunctionImpl(
startOffset, endOffset, newOrigin,
IrSimpleFunctionSymbolImpl(wrappedDescriptor),
Name.identifier(getJvmName()),
visibility, modality, returnType,
isInline = isInline, isExternal = isExternal, isTailrec = isTailrec, isSuspend = isSuspend, isExpect = isExpect,
isFakeOverride = newOrigin == IrDeclarationOrigin.FAKE_OVERRIDE,
isOperator = isOperator
).apply {
wrappedDescriptor.bind(this)
parent = this@orphanedCopy.parent
copyTypeParametersFrom(this@orphanedCopy)
this@orphanedCopy.dispatchReceiverParameter?.let { dispatchReceiverParameter = it.copyTo(this) }
this@orphanedCopy.extensionReceiverParameter?.let { extensionReceiverParameter = it.copyTo(this) }
valueParameters = this@orphanedCopy.valueParameters.map { it.copyTo(this) }
/* Do NOT copy overriddenSymbols */
}
}
private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, to: IrType): IrExpression =
if (expression.type == to || to.isAny() || to.isNullableAny()) expression else irImplicitCast(expression, to)
private fun IrValueParameter.copyWithTypeErasure(target: IrSimpleFunction): IrValueParameter {
val descriptor = if (this.descriptor is ReceiverParameterDescriptor) {
WrappedReceiverParameterDescriptor(this.descriptor.annotations)
} else {
WrappedValueParameterDescriptor(this.descriptor.annotations)
}
return IrValueParameterImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
IrDeclarationOrigin.BRIDGE,
IrValueParameterSymbolImpl(descriptor),
name,
index,
// SuspendFunction{N} is Function{N+1} at runtime, thus, when we generate a bridge for suspend callable reference,
// we need to replace type of its continuation parameter with Any?
if (target.isSuspend && type.eraseTypeParameters().getClass()
?.fqNameWhenAvailable == DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME_RELEASE
) context.irBuiltIns.anyNType else type.eraseTypeParameters(),
varargElementType?.eraseTypeParameters(),
isCrossinline,
isNoinline
).apply {
descriptor.bind(this)
parent = target
}
}
private fun IrSimpleFunction.findAllReachableDeclarations() =
findAllReachableDeclarations(FunctionHandleForIrFunction(this)).map { it.irFunction }
private fun getFinalOverridden(irFunction: IrSimpleFunction): List<IrSimpleFunction> {
return irFunction.findAllReachableDeclarations().filter { it.modality === Modality.FINAL }
}
// There are two sources of method name change:
// 1. Special methods renamed from java
// 2. Internal methods overridden by public ones.
// Here, we want to only deal with the first case.
private fun getRenamedOverridden(irFunction: IrSimpleFunction): IrSimpleFunction? {
val ourName = irFunction.getJvmName()
return irFunction.allOverridden().firstOrNull {
it.visibility == Visibilities.PUBLIC && it.getJvmName() != ourName
}
}
private inner class FunctionHandleForIrFunction(val irFunction: IrSimpleFunction) : FunctionHandle {
override val isDeclaration get() = irFunction.origin != IrDeclarationOrigin.FAKE_OVERRIDE || irFunction.findInterfaceImplementation() != null
override val isAbstract get() = irFunction.modality == Modality.ABSTRACT
override val mayBeUsedAsSuperImplementation get() = !irFunction.parentAsClass.isInterface || irFunction.hasJvmDefault()
override fun getOverridden() = irFunction.overriddenSymbols.map { FunctionHandleForIrFunction(it.owner) }
override fun hashCode(): Int =
irFunction.parent.safeAs<IrClass>()?.fqNameWhenAvailable.hashCode() + 31 * irFunction.getJvmSignature().hashCode()
override fun equals(other: Any?): Boolean =
other is FunctionHandleForIrFunction &&
irFunction.parent.safeAs<IrClass>()?.fqNameWhenAvailable == other.irFunction.parent.safeAs<IrClass>()?.fqNameWhenAvailable &&
irFunction.getJvmSignature() == other.irFunction.getJvmSignature()
override fun toString(): String =
irFunction.render()
}
fun IrSimpleFunction.findConcreteSuperDeclaration(): IrSimpleFunction? {
return findConcreteSuperDeclaration(FunctionHandleForIrFunction(this))?.irFunction
}
private fun IrFunction.getJvmSignature(): Method = methodSignatureMapper.mapAsmMethod(this)
private fun IrFunction.getJvmName(): String = getJvmSignature().name
private val IrFunction.jvmMethod: Method
get() = context.methodSignatureMapper.mapAsmMethod(this)
}
private data class SignatureWithSource(val signature: Method, val source: IrSimpleFunction) {
override fun hashCode(): Int {
return signature.hashCode()
}
override fun equals(other: Any?): Boolean {
return other is SignatureWithSource && signature == other.signature
}
}
fun IrSimpleFunction.overriddenInClasses(): Sequence<IrSimpleFunction> =
allOverridden().filterNot(IrDeclaration::hasInterfaceParent)
fun IrSimpleFunction.isCollectionStub(): Boolean =
private fun IrSimpleFunction.isCollectionStub(): Boolean =
origin == IrDeclarationOrigin.IR_BUILTINS_STUB
val EXTERNAL_ORIGIN = setOf(IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB, IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB)
fun IrDeclaration.comesFromJava() = parentAsClass.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
fun IrDeclaration.isExternalDeclaration() = parentAsClass.origin in EXTERNAL_ORIGIN
// Method has the same name, same arguments as `other`. Return types may differ.
fun Method.sameCallAs(other: Method) =
name == other.name &&
argumentTypes?.contentEquals(other.argumentTypes) == true
private fun IrDeclaration.comesFromJava() = parentAsClass.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
@@ -268,12 +268,12 @@ fun IrBuilderWithScope.irCall(callee: IrFunctionSymbol, descriptor: FunctionDesc
fun IrBuilderWithScope.irCall(callee: IrFunction): IrFunctionAccessExpression =
irCall(callee.symbol)
fun IrBuilderWithScope.irCall(callee: IrFunction, origin: IrStatementOrigin): IrCall =
fun IrBuilderWithScope.irCall(callee: IrFunction, origin: IrStatementOrigin, superQualifierSymbol: IrClassSymbol? = null): IrCall =
IrCallImpl(
startOffset, endOffset, callee.returnType,
callee.symbol as IrSimpleFunctionSymbol,
callee.typeParameters.size, callee.valueParameters.size,
origin
origin, superQualifierSymbol
)
fun IrBuilderWithScope.irDelegatingConstructorCall(callee: IrConstructor): IrDelegatingConstructorCall =
@@ -1,7 +1,6 @@
// !JVM_DEFAULT_MODE: enable
// IGNORE_BACKEND_FIR: JVM_IR
// SKIP_JDK6
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// JVM_TARGET: 1.8
// WITH_RUNTIME
@@ -1,6 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR
// SKIP_JDK6
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// FULL_JDK
@@ -24,4 +23,4 @@ fun box(): String {
if (!test.remove("O", "K")) return "fail 2: entry wasn't removed"
return test.getOrDefault("absent", "OK")
}
}
@@ -1,6 +1,5 @@
// !LANGUAGE: +InlineClasses
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
abstract class GenericBase<T> {
abstract fun foo(x: T): T
-1
View File
@@ -1,6 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
// FULL_JDK
// JVM_TARGET: 1.8
@@ -1,6 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR
// KJS_WITH_FULL_RUNTIME
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND: NATIVE
open class Base<Target : DatabaseEntity>() : HashSet<Target>() {
@@ -1,5 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND: NATIVE
open class A0<E> : MutableList<E> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
abstract class A1<T> : MutableList<T> {
override fun remove(x: T): Boolean = true
override fun removeAt(index: Int): T = null!!
@@ -1,8 +1,5 @@
//FULL_JDK
// In BridgeLowering, no concrete overwrite is found for getOrDefault.
// IGNORE_BACKEND: JVM_IR
class KotlinMap1<K, V> : java.util.AbstractMap<K, V>() {
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() = throw UnsupportedOperationException()