JVM IR: move out bridge/collection lowering caches to separate files
Both BridgeLoweringCache and CollectionStubComputer are not tecnically part of the lowerings, so they shouldn't be in the "lower" package.
This commit is contained in:
@@ -11,13 +11,13 @@ import org.jetbrains.kotlin.backend.common.Mapping
|
|||||||
import org.jetbrains.kotlin.backend.common.ir.Ir
|
import org.jetbrains.kotlin.backend.common.ir.Ir
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
||||||
import org.jetbrains.kotlin.backend.common.psi.PsiErrorBuilder
|
import org.jetbrains.kotlin.backend.common.psi.PsiErrorBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.caches.BridgeLoweringCache
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.caches.CollectionStubComputer
|
||||||
import org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen
|
import org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen
|
||||||
import org.jetbrains.kotlin.backend.jvm.codegen.IrTypeMapper
|
import org.jetbrains.kotlin.backend.jvm.codegen.IrTypeMapper
|
||||||
import org.jetbrains.kotlin.backend.jvm.codegen.MethodSignatureMapper
|
import org.jetbrains.kotlin.backend.jvm.codegen.MethodSignatureMapper
|
||||||
import org.jetbrains.kotlin.backend.jvm.codegen.createFakeContinuation
|
import org.jetbrains.kotlin.backend.jvm.codegen.createFakeContinuation
|
||||||
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
|
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
|
||||||
import org.jetbrains.kotlin.backend.jvm.lower.BridgeLowering
|
|
||||||
import org.jetbrains.kotlin.backend.jvm.lower.CollectionStubComputer
|
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||||
@@ -113,7 +113,7 @@ class JvmBackendContext(
|
|||||||
fun getOverridesWithoutStubs(function: IrSimpleFunction): List<IrSimpleFunctionSymbol> =
|
fun getOverridesWithoutStubs(function: IrSimpleFunction): List<IrSimpleFunctionSymbol> =
|
||||||
overridesWithoutStubs.getOrElse(function) { function.overriddenSymbols }
|
overridesWithoutStubs.getOrElse(function) { function.overriddenSymbols }
|
||||||
|
|
||||||
internal val bridgeLoweringCache = BridgeLowering.BridgeLoweringCache(this)
|
internal val bridgeLoweringCache = BridgeLoweringCache(this)
|
||||||
internal val functionsWithSpecialBridges: MutableSet<IrFunction> = ConcurrentHashMap.newKeySet()
|
internal val functionsWithSpecialBridges: MutableSet<IrFunction> = ConcurrentHashMap.newKeySet()
|
||||||
|
|
||||||
override var inVerbosePhase: Boolean = false // TODO: needs parallelizing
|
override var inVerbosePhase: Boolean = false // TODO: needs parallelizing
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.backend.jvm
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.SpecialMethodWithDefaultInfo
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
|
import org.jetbrains.org.objectweb.asm.commons.Method
|
||||||
|
|
||||||
|
// Represents a special bridge to `overridden`. Special bridges are overrides for Java methods which are
|
||||||
|
// exposed to Kotlin with a different name or different types. Typically, the Java version of a method is
|
||||||
|
// non-generic, while Kotlin exposes a generic method. In this case, the bridge method needs to perform
|
||||||
|
// additional type checking at runtime. The behavior in case of type errors is configured in `methodInfo`.
|
||||||
|
//
|
||||||
|
// Since special bridges may be exposed to Java as non-synthetic methods, we need correct generic signatures.
|
||||||
|
// There are a total of seven generic special bridge methods (Map.getOrDefault, Map.get, MutableMap.remove with
|
||||||
|
// only one argument, Map.keys, Map.values, Map.entries, and MutableList.removeAt). Of these seven there is only
|
||||||
|
// one which the JVM backend currently handles correctly (MutableList.removeAt). For the rest, it's impossible
|
||||||
|
// to reproduce the behavior of the JVM backend in this lowering.
|
||||||
|
//
|
||||||
|
// Finally, we sometimes need to use INVOKESPECIAL to invoke an existing special bridge implementation in a
|
||||||
|
// superclass, which is what `superQualifierSymbol` is for.
|
||||||
|
data class SpecialBridge(
|
||||||
|
val overridden: IrSimpleFunction,
|
||||||
|
val signature: Method,
|
||||||
|
// We need to produce a generic signature if the underlying Java method contains type parameters.
|
||||||
|
// E.g., the `java.util.Map<K, V>.keySet` method has a return type of `Set<K>`, and hence overrides
|
||||||
|
// need to generate a generic signature.
|
||||||
|
val needsGenericSignature: Boolean = false,
|
||||||
|
// The result of substituting type parameters in the overridden Java method. This is different from
|
||||||
|
// substituting into the overridden Kotlin method. For example, Map.getOrDefault has two arguments
|
||||||
|
// with generic types in Kotlin, but only the second parameter is generic in Java.
|
||||||
|
// May be null if the underlying Java method does not contain generic types.
|
||||||
|
val substitutedParameterTypes: List<IrType>? = null,
|
||||||
|
val substitutedReturnType: IrType? = null,
|
||||||
|
val methodInfo: SpecialMethodWithDefaultInfo? = null,
|
||||||
|
val superQualifierSymbol: IrClassSymbol? = null,
|
||||||
|
val isFinal: Boolean = true,
|
||||||
|
val isSynthetic: Boolean = false,
|
||||||
|
val isOverriding: Boolean = true,
|
||||||
|
)
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.backend.jvm.caches
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.SpecialBridgeMethods
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.SpecialBridge
|
||||||
|
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.org.objectweb.asm.commons.Method
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
internal class BridgeLoweringCache(private val context: JvmBackendContext) {
|
||||||
|
private val specialBridgeMethods = SpecialBridgeMethods(context)
|
||||||
|
|
||||||
|
// TODO: consider moving this cache out to the backend context and using it everywhere throughout the codegen.
|
||||||
|
// It might benefit performance, but can lead to confusing behavior if some declarations are changed along the way.
|
||||||
|
// For example, adding an override for a declaration whose signature is already cached can result in incorrect signature
|
||||||
|
// if its return type is a primitive type, and the new override's return type is an object type.
|
||||||
|
private val signatureCache = ConcurrentHashMap<IrFunctionSymbol, Method>()
|
||||||
|
|
||||||
|
fun computeJvmMethod(function: IrFunction): Method =
|
||||||
|
signatureCache.getOrPut(function.symbol) { context.methodSignatureMapper.mapAsmMethod(function) }
|
||||||
|
|
||||||
|
private fun canHaveSpecialBridge(function: IrSimpleFunction): Boolean {
|
||||||
|
if (function.name in specialBridgeMethods.specialMethodNames)
|
||||||
|
return true
|
||||||
|
// Function name could be mangled by inline class rules
|
||||||
|
val functionName = function.name.asString()
|
||||||
|
if (specialBridgeMethods.specialMethodNames.any { functionName.startsWith(it.asString() + "-") })
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun computeSpecialBridge(function: IrSimpleFunction): SpecialBridge? {
|
||||||
|
// Optimization: do not try to compute special bridge for irrelevant methods.
|
||||||
|
val correspondingProperty = function.correspondingPropertySymbol
|
||||||
|
if (correspondingProperty != null) {
|
||||||
|
if (correspondingProperty.owner.name !in specialBridgeMethods.specialPropertyNames) return null
|
||||||
|
} else {
|
||||||
|
if (!canHaveSpecialBridge(function)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val specialMethodInfo = specialBridgeMethods.getSpecialMethodInfo(function)
|
||||||
|
if (specialMethodInfo != null)
|
||||||
|
return SpecialBridge(
|
||||||
|
function, computeJvmMethod(function), specialMethodInfo.needsGenericSignature, methodInfo = specialMethodInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
val specialBuiltInInfo = specialBridgeMethods.getBuiltInWithDifferentJvmName(function)
|
||||||
|
if (specialBuiltInInfo != null)
|
||||||
|
return SpecialBridge(
|
||||||
|
function, computeJvmMethod(function), specialBuiltInInfo.needsGenericSignature,
|
||||||
|
isOverriding = specialBuiltInInfo.isOverriding
|
||||||
|
)
|
||||||
|
|
||||||
|
for (overridden in function.overriddenSymbols) {
|
||||||
|
val specialBridge = computeSpecialBridge(overridden.owner) ?: continue
|
||||||
|
if (!specialBridge.needsGenericSignature) return specialBridge
|
||||||
|
|
||||||
|
// Compute the substituted signature.
|
||||||
|
val erasedParameterCount = specialBridge.methodInfo?.argumentsToCheck ?: 0
|
||||||
|
val substitutedParameterTypes = function.valueParameters.mapIndexed { index, param ->
|
||||||
|
if (index < erasedParameterCount) context.irBuiltIns.anyNType else param.type
|
||||||
|
}
|
||||||
|
|
||||||
|
val substitutedOverride = context.irFactory.buildFun {
|
||||||
|
updateFrom(specialBridge.overridden)
|
||||||
|
name = Name.identifier(specialBridge.signature.name)
|
||||||
|
returnType = function.returnType
|
||||||
|
}.apply {
|
||||||
|
// All existing special bridges only have value parameter types.
|
||||||
|
valueParameters = function.valueParameters.zip(substitutedParameterTypes).map { (param, type) ->
|
||||||
|
param.copyTo(this, IrDeclarationOrigin.BRIDGE, type = type)
|
||||||
|
}
|
||||||
|
overriddenSymbols = listOf(specialBridge.overridden.symbol)
|
||||||
|
parent = function.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
return specialBridge.copy(
|
||||||
|
signature = computeJvmMethod(substitutedOverride),
|
||||||
|
substitutedParameterTypes = substitutedParameterTypes,
|
||||||
|
substitutedReturnType = function.returnType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.backend.jvm.caches
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.types.isStrictSubtypeOfClass
|
||||||
|
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
|
||||||
|
import org.jetbrains.kotlin.ir.util.functions
|
||||||
|
import org.jetbrains.kotlin.ir.util.isFromJava
|
||||||
|
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
internal class CollectionStubComputer(val context: JvmBackendContext) {
|
||||||
|
private class LazyStubsForCollectionClass(
|
||||||
|
override val readOnlyClass: IrClassSymbol,
|
||||||
|
override val mutableClass: IrClassSymbol
|
||||||
|
) : StubsForCollectionClass {
|
||||||
|
override val candidatesForStubs: Collection<IrSimpleFunction> by lazy {
|
||||||
|
// Old back-end generates stubs for 'class A : C', where
|
||||||
|
// 'C' is some "read-only collection" interface from kotlin.collections,
|
||||||
|
// 'MC' is a corresponding "mutable collection" interface from kotlin.collections,
|
||||||
|
// by building fake overrides for a special 'class X : A(), MC'
|
||||||
|
// and taking fake overrides that override members from 'MC'.
|
||||||
|
//
|
||||||
|
// Here we are looking at this problem from a slightly different angle:
|
||||||
|
// we select suitable member functions 'f' in 'MC' that might potentially require stubs (we are here!),
|
||||||
|
// and then we generate stubs for functions 'f' that are not effectively overridden by members of 'A'
|
||||||
|
// (this happens in the lowering itself).
|
||||||
|
//
|
||||||
|
// In order for this to be equivalent to the old back-end approach,
|
||||||
|
// we should take 'f' in 'MC' such that any of the following conditions is true:
|
||||||
|
// - 'f' is declared in 'MC' - that is, 'f' itself is not a fake override;
|
||||||
|
// - 'f' is abstract and doesn't override anything from 'C'.
|
||||||
|
//
|
||||||
|
// NB1 it also covers default methods from JDK collection classes case
|
||||||
|
// (since that's the only way a member function in 'MC' might be non-abstract).
|
||||||
|
//
|
||||||
|
// NB2 the scheme of stub method generation in the old back-end depends too much on
|
||||||
|
// which particular declarations are present in 'MC'.
|
||||||
|
// Some of these declarations are redundant from the stub generation point of view -
|
||||||
|
// for example, 'kotlin.collections.MutableListIterator' contains the following (redundant) declarations:
|
||||||
|
// override fun next(): T
|
||||||
|
// override fun hasNext(): Boolean
|
||||||
|
// which cause stubs for 'next' and 'hasNext' to be generated in a 'abstract class A<T> : ListIterator<T>'.
|
||||||
|
// See https://youtrack.jetbrains.com/issue/KT-36724.
|
||||||
|
// In the ideal world, it should be enough to check that
|
||||||
|
// the given member function 'f' from 'MC' doesn't override anything from 'C'.
|
||||||
|
|
||||||
|
mutableClass.owner.functions
|
||||||
|
.filter { memberFun ->
|
||||||
|
!memberFun.isFakeOverride ||
|
||||||
|
(memberFun.modality == Modality.ABSTRACT && memberFun.overriddenSymbols.none { overriddenFun ->
|
||||||
|
overriddenFun.owner.parentAsClass.symbol == readOnlyClass
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val preComputedStubs: Collection<StubsForCollectionClass> by lazy {
|
||||||
|
with(context.ir.symbols) {
|
||||||
|
listOf(
|
||||||
|
LazyStubsForCollectionClass(collection, mutableCollection),
|
||||||
|
LazyStubsForCollectionClass(set, mutableSet),
|
||||||
|
LazyStubsForCollectionClass(list, mutableList),
|
||||||
|
LazyStubsForCollectionClass(map, mutableMap),
|
||||||
|
LazyStubsForCollectionClass(mapEntry, mutableMapEntry),
|
||||||
|
LazyStubsForCollectionClass(iterable, mutableIterable),
|
||||||
|
LazyStubsForCollectionClass(iterator, mutableIterator),
|
||||||
|
LazyStubsForCollectionClass(listIterator, mutableListIterator)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val stubsCache = ConcurrentHashMap<IrClass, List<StubsForCollectionClass>>()
|
||||||
|
|
||||||
|
fun stubsForCollectionClasses(irClass: IrClass): List<StubsForCollectionClass> =
|
||||||
|
stubsCache.getOrPut(irClass) {
|
||||||
|
computeStubsForCollectionClasses(irClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeStubsForCollectionClasses(irClass: IrClass): List<StubsForCollectionClass> {
|
||||||
|
if (irClass.isFromJava()) return emptyList()
|
||||||
|
val stubs = preComputedStubs.filter {
|
||||||
|
irClass.symbol.isStrictSubtypeOfClass(it.readOnlyClass) && !irClass.symbol.isSubtypeOfClass(it.mutableClass)
|
||||||
|
}
|
||||||
|
return stubs.filter {
|
||||||
|
stubs.none { other -> it.readOnlyClass != other.readOnlyClass && other.readOnlyClass.isSubtypeOfClass(it.readOnlyClass) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StubsForCollectionClass {
|
||||||
|
val readOnlyClass: IrClassSymbol
|
||||||
|
val mutableClass: IrClassSymbol
|
||||||
|
val candidatesForStubs: Collection<IrSimpleFunction>
|
||||||
|
}
|
||||||
+5
-118
@@ -10,10 +10,14 @@ import org.jetbrains.kotlin.backend.common.ir.allOverridden
|
|||||||
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||||
import org.jetbrains.kotlin.backend.common.ir.isMethodOfAny
|
import org.jetbrains.kotlin.backend.common.ir.isMethodOfAny
|
||||||
import org.jetbrains.kotlin.backend.common.ir.isStatic
|
import org.jetbrains.kotlin.backend.common.ir.isStatic
|
||||||
import org.jetbrains.kotlin.backend.common.lower.*
|
import org.jetbrains.kotlin.backend.common.lower.SpecialMethodWithDefaultInfo
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.VariableRemapper
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.irNot
|
||||||
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.SpecialBridge
|
||||||
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.codegen.AsmUtil
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
@@ -22,11 +26,9 @@ import org.jetbrains.kotlin.descriptors.Modality
|
|||||||
import org.jetbrains.kotlin.ir.IrStatement
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
|
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
@@ -35,7 +37,6 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
import org.jetbrains.kotlin.utils.SmartList
|
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
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generate bridge methods to fix virtual dispatch after type erasure and to adapt Kotlin collections to
|
* Generate bridge methods to fix virtual dispatch after type erasure and to adapt Kotlin collections to
|
||||||
@@ -127,39 +128,6 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
val overriddenSymbols: MutableList<IrSimpleFunctionSymbol> = mutableListOf()
|
val overriddenSymbols: MutableList<IrSimpleFunctionSymbol> = mutableListOf()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Represents a special bridge to `overridden`. Special bridges are overrides for Java methods which are
|
|
||||||
// exposed to Kotlin with a different name or different types. Typically, the Java version of a method is
|
|
||||||
// non-generic, while Kotlin exposes a generic method. In this case, the bridge method needs to perform
|
|
||||||
// additional type checking at runtime. The behavior in case of type errors is configured in `methodInfo`.
|
|
||||||
//
|
|
||||||
// Since special bridges may be exposed to Java as non-synthetic methods, we need correct generic signatures.
|
|
||||||
// There are a total of seven generic special bridge methods (Map.getOrDefault, Map.get, MutableMap.remove with
|
|
||||||
// only one argument, Map.keys, Map.values, Map.entries, and MutableList.removeAt). Of these seven there is only
|
|
||||||
// one which the JVM backend currently handles correctly (MutableList.removeAt). For the rest, it's impossible
|
|
||||||
// to reproduce the behavior of the JVM backend in this lowering.
|
|
||||||
//
|
|
||||||
// Finally, we sometimes need to use INVOKESPECIAL to invoke an existing special bridge implementation in a
|
|
||||||
// superclass, which is what `superQualifierSymbol` is for.
|
|
||||||
data class SpecialBridge(
|
|
||||||
val overridden: IrSimpleFunction,
|
|
||||||
val signature: Method,
|
|
||||||
// We need to produce a generic signature if the underlying Java method contains type parameters.
|
|
||||||
// E.g., the `java.util.Map<K, V>.keySet` method has a return type of `Set<K>`, and hence overrides
|
|
||||||
// need to generate a generic signature.
|
|
||||||
val needsGenericSignature: Boolean = false,
|
|
||||||
// The result of substituting type parameters in the overridden Java method. This is different from
|
|
||||||
// substituting into the overridden Kotlin method. For example, Map.getOrDefault has two arguments
|
|
||||||
// with generic types in Kotlin, but only the second parameter is generic in Java.
|
|
||||||
// May be null if the underlying Java method does not contain generic types.
|
|
||||||
val substitutedParameterTypes: List<IrType>? = null,
|
|
||||||
val substitutedReturnType: IrType? = null,
|
|
||||||
val methodInfo: SpecialMethodWithDefaultInfo? = null,
|
|
||||||
val superQualifierSymbol: IrClassSymbol? = null,
|
|
||||||
val isFinal: Boolean = true,
|
|
||||||
val isSynthetic: Boolean = false,
|
|
||||||
val isOverriding: Boolean = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irFile: IrFile) {
|
||||||
irFile.transformChildrenVoid()
|
irFile.transformChildrenVoid()
|
||||||
}
|
}
|
||||||
@@ -601,86 +569,6 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass
|
|||||||
|
|
||||||
private val IrFunction.jvmMethod: Method
|
private val IrFunction.jvmMethod: Method
|
||||||
get() = context.bridgeLoweringCache.computeJvmMethod(this)
|
get() = context.bridgeLoweringCache.computeJvmMethod(this)
|
||||||
|
|
||||||
internal class BridgeLoweringCache(private val context: JvmBackendContext) {
|
|
||||||
private val specialBridgeMethods = SpecialBridgeMethods(context)
|
|
||||||
|
|
||||||
// TODO: consider moving this cache out to the backend context and using it everywhere throughout the codegen.
|
|
||||||
// It might benefit performance, but can lead to confusing behavior if some declarations are changed along the way.
|
|
||||||
// For example, adding an override for a declaration whose signature is already cached can result in incorrect signature
|
|
||||||
// if its return type is a primitive type, and the new override's return type is an object type.
|
|
||||||
private val signatureCache = ConcurrentHashMap<IrFunctionSymbol, Method>()
|
|
||||||
|
|
||||||
fun computeJvmMethod(function: IrFunction): Method =
|
|
||||||
signatureCache.getOrPut(function.symbol) { context.methodSignatureMapper.mapAsmMethod(function) }
|
|
||||||
|
|
||||||
private fun canHaveSpecialBridge(function: IrSimpleFunction): Boolean {
|
|
||||||
if (function.name in specialBridgeMethods.specialMethodNames)
|
|
||||||
return true
|
|
||||||
// Function name could be mangled by inline class rules
|
|
||||||
val functionName = function.name.asString()
|
|
||||||
if (specialBridgeMethods.specialMethodNames.any { functionName.startsWith(it.asString() + "-") })
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun computeSpecialBridge(function: IrSimpleFunction): SpecialBridge? {
|
|
||||||
// Optimization: do not try to compute special bridge for irrelevant methods.
|
|
||||||
val correspondingProperty = function.correspondingPropertySymbol
|
|
||||||
if (correspondingProperty != null) {
|
|
||||||
if (correspondingProperty.owner.name !in specialBridgeMethods.specialPropertyNames) return null
|
|
||||||
} else {
|
|
||||||
if (!canHaveSpecialBridge(function)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val specialMethodInfo = specialBridgeMethods.getSpecialMethodInfo(function)
|
|
||||||
if (specialMethodInfo != null)
|
|
||||||
return SpecialBridge(
|
|
||||||
function, computeJvmMethod(function), specialMethodInfo.needsGenericSignature, methodInfo = specialMethodInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
val specialBuiltInInfo = specialBridgeMethods.getBuiltInWithDifferentJvmName(function)
|
|
||||||
if (specialBuiltInInfo != null)
|
|
||||||
return SpecialBridge(
|
|
||||||
function, computeJvmMethod(function), specialBuiltInInfo.needsGenericSignature,
|
|
||||||
isOverriding = specialBuiltInInfo.isOverriding
|
|
||||||
)
|
|
||||||
|
|
||||||
for (overridden in function.overriddenSymbols) {
|
|
||||||
val specialBridge = computeSpecialBridge(overridden.owner) ?: continue
|
|
||||||
if (!specialBridge.needsGenericSignature) return specialBridge
|
|
||||||
|
|
||||||
// Compute the substituted signature.
|
|
||||||
val erasedParameterCount = specialBridge.methodInfo?.argumentsToCheck ?: 0
|
|
||||||
val substitutedParameterTypes = function.valueParameters.mapIndexed { index, param ->
|
|
||||||
if (index < erasedParameterCount) context.irBuiltIns.anyNType else param.type
|
|
||||||
}
|
|
||||||
|
|
||||||
val substitutedOverride = context.irFactory.buildFun {
|
|
||||||
updateFrom(specialBridge.overridden)
|
|
||||||
name = Name.identifier(specialBridge.signature.name)
|
|
||||||
returnType = function.returnType
|
|
||||||
}.apply {
|
|
||||||
// All existing special bridges only have value parameter types.
|
|
||||||
valueParameters = function.valueParameters.zip(substitutedParameterTypes).map { (param, type) ->
|
|
||||||
param.copyTo(this, IrDeclarationOrigin.BRIDGE, type = type)
|
|
||||||
}
|
|
||||||
overriddenSymbols = listOf(specialBridge.overridden.symbol)
|
|
||||||
parent = function.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
return specialBridge.copy(
|
|
||||||
signature = computeJvmMethod(substitutedOverride),
|
|
||||||
substitutedParameterTypes = substitutedParameterTypes,
|
|
||||||
substitutedReturnType = function.returnType
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether a fake override will resolve to an implementation in class, not an interface.
|
// Check whether a fake override will resolve to an implementation in class, not an interface.
|
||||||
@@ -691,4 +579,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
|
||||||
|
|
||||||
|
|||||||
+1
-88
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.ClassLoweringPass
|
|||||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
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.caches.StubsForCollectionClass
|
||||||
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
|
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||||
@@ -28,7 +29,6 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
import org.jetbrains.kotlin.types.TypeCheckerState
|
import org.jetbrains.kotlin.types.TypeCheckerState
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
internal val collectionStubMethodLowering = makeIrFilePhase(
|
internal val collectionStubMethodLowering = makeIrFilePhase(
|
||||||
::CollectionStubMethodLowering,
|
::CollectionStubMethodLowering,
|
||||||
@@ -390,90 +390,3 @@ internal class CollectionStubMethodLowering(val context: JvmBackendContext) : Cl
|
|||||||
private val IrClass.superClassChain: Sequence<IrClass>
|
private val IrClass.superClassChain: Sequence<IrClass>
|
||||||
get() = generateSequence(this) { it.superClass }
|
get() = generateSequence(this) { it.superClass }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface StubsForCollectionClass {
|
|
||||||
val readOnlyClass: IrClassSymbol
|
|
||||||
val mutableClass: IrClassSymbol
|
|
||||||
val candidatesForStubs: Collection<IrSimpleFunction>
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CollectionStubComputer(val context: JvmBackendContext) {
|
|
||||||
|
|
||||||
private class LazyStubsForCollectionClass(
|
|
||||||
override val readOnlyClass: IrClassSymbol,
|
|
||||||
override val mutableClass: IrClassSymbol
|
|
||||||
) : StubsForCollectionClass {
|
|
||||||
override val candidatesForStubs: Collection<IrSimpleFunction> by lazy {
|
|
||||||
// Old back-end generates stubs for 'class A : C', where
|
|
||||||
// 'C' is some "read-only collection" interface from kotlin.collections,
|
|
||||||
// 'MC' is a corresponding "mutable collection" interface from kotlin.collections,
|
|
||||||
// by building fake overrides for a special 'class X : A(), MC'
|
|
||||||
// and taking fake overrides that override members from 'MC'.
|
|
||||||
//
|
|
||||||
// Here we are looking at this problem from a slightly different angle:
|
|
||||||
// we select suitable member functions 'f' in 'MC' that might potentially require stubs (we are here!),
|
|
||||||
// and then we generate stubs for functions 'f' that are not effectively overridden by members of 'A'
|
|
||||||
// (this happens in the lowering itself).
|
|
||||||
//
|
|
||||||
// In order for this to be equivalent to the old back-end approach,
|
|
||||||
// we should take 'f' in 'MC' such that any of the following conditions is true:
|
|
||||||
// - 'f' is declared in 'MC' - that is, 'f' itself is not a fake override;
|
|
||||||
// - 'f' is abstract and doesn't override anything from 'C'.
|
|
||||||
//
|
|
||||||
// NB1 it also covers default methods from JDK collection classes case
|
|
||||||
// (since that's the only way a member function in 'MC' might be non-abstract).
|
|
||||||
//
|
|
||||||
// NB2 the scheme of stub method generation in the old back-end depends too much on
|
|
||||||
// which particular declarations are present in 'MC'.
|
|
||||||
// Some of these declarations are redundant from the stub generation point of view -
|
|
||||||
// for example, 'kotlin.collections.MutableListIterator' contains the following (redundant) declarations:
|
|
||||||
// override fun next(): T
|
|
||||||
// override fun hasNext(): Boolean
|
|
||||||
// which cause stubs for 'next' and 'hasNext' to be generated in a 'abstract class A<T> : ListIterator<T>'.
|
|
||||||
// See https://youtrack.jetbrains.com/issue/KT-36724.
|
|
||||||
// In the ideal world, it should be enough to check that
|
|
||||||
// the given member function 'f' from 'MC' doesn't override anything from 'C'.
|
|
||||||
|
|
||||||
mutableClass.owner.functions
|
|
||||||
.filter { memberFun ->
|
|
||||||
!memberFun.isFakeOverride ||
|
|
||||||
(memberFun.modality == Modality.ABSTRACT && memberFun.overriddenSymbols.none { overriddenFun ->
|
|
||||||
overriddenFun.owner.parentAsClass.symbol == readOnlyClass
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val preComputedStubs: Collection<StubsForCollectionClass> by lazy {
|
|
||||||
with(context.ir.symbols) {
|
|
||||||
listOf(
|
|
||||||
LazyStubsForCollectionClass(collection, mutableCollection),
|
|
||||||
LazyStubsForCollectionClass(set, mutableSet),
|
|
||||||
LazyStubsForCollectionClass(list, mutableList),
|
|
||||||
LazyStubsForCollectionClass(map, mutableMap),
|
|
||||||
LazyStubsForCollectionClass(mapEntry, mutableMapEntry),
|
|
||||||
LazyStubsForCollectionClass(iterable, mutableIterable),
|
|
||||||
LazyStubsForCollectionClass(iterator, mutableIterator),
|
|
||||||
LazyStubsForCollectionClass(listIterator, mutableListIterator)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val stubsCache = ConcurrentHashMap<IrClass, List<StubsForCollectionClass>>()
|
|
||||||
|
|
||||||
fun stubsForCollectionClasses(irClass: IrClass): List<StubsForCollectionClass> =
|
|
||||||
stubsCache.getOrPut(irClass) {
|
|
||||||
computeStubsForCollectionClasses(irClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun computeStubsForCollectionClasses(irClass: IrClass): List<StubsForCollectionClass> {
|
|
||||||
if (irClass.isFromJava()) return emptyList()
|
|
||||||
val stubs = preComputedStubs.filter {
|
|
||||||
irClass.symbol.isStrictSubtypeOfClass(it.readOnlyClass) && !irClass.symbol.isSubtypeOfClass(it.mutableClass)
|
|
||||||
}
|
|
||||||
return stubs.filter {
|
|
||||||
stubs.none { other -> it.readOnlyClass != other.readOnlyClass && other.readOnlyClass.isSubtypeOfClass(it.readOnlyClass) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user