JVM_IR: Cache stubs in CollectionStubMethodLowering

This commit is contained in:
Georgy Bronnikov
2020-03-12 14:51:18 +03:00
parent 92268c8144
commit 426738b7d4
2 changed files with 59 additions and 58 deletions
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.backend.jvm.codegen.createFakeContinuation
import org.jetbrains.kotlin.backend.jvm.descriptors.JvmDeclarationFactory
import org.jetbrains.kotlin.backend.jvm.descriptors.JvmSharedVariablesManager
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
import org.jetbrains.kotlin.backend.jvm.lower.CollectionStubComputer
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.MemoizedInlineClassReplacements
import org.jetbrains.kotlin.backend.jvm.lower.suspendFunctionOriginal
@@ -107,6 +108,8 @@ class JvmBackendContext(
internal val hiddenConstructors = mutableMapOf<IrConstructor, IrConstructorImpl>()
internal val collectionStubComputer = CollectionStubComputer(this)
override var inVerbosePhase: Boolean = false
override val configuration get() = state.configuration
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irThrow
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
@@ -34,8 +35,8 @@ internal val collectionStubMethodLowering = makeIrFilePhase(
description = "Generate Collection stub methods"
)
private class CollectionStubMethodLowering(val context: JvmBackendContext) : ClassLoweringPass {
private val methodSignatureMapper = context.methodSignatureMapper
internal class CollectionStubMethodLowering(val context: JvmBackendContext) : ClassLoweringPass {
private val collectionStubComputer = context.collectionStubComputer
override fun lower(irClass: IrClass) {
if (irClass.isInterface) {
@@ -48,12 +49,12 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla
// We don't need to generate stub for existing methods, but for FAKE_OVERRIDE methods with ABSTRACT modality,
// it means an abstract function in superclass that is not implemented yet,
// stub generation is still needed to avoid invocation error.
val existingMethodSignatures = irClass.functions.filterNot {
val existingMethodsBySignature = irClass.functions.filterNot {
it.modality == Modality.ABSTRACT && it.origin == IrDeclarationOrigin.FAKE_OVERRIDE
}.associateBy { it.toSignature() }
for (member in methodStubsToGenerate) {
val existingMethod = existingMethodSignatures[member.toSignature()]
val existingMethod = existingMethodsBySignature[member.toSignature()]
if (existingMethod != null) {
// In the case that we find a defined method that matches the stub signature, we add the overridden symbols to that
// defined method, so that bridge lowering can still generate correct bridge for that method
@@ -64,7 +65,7 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla
}
}
private fun IrSimpleFunction.toSignature(): String = methodSignatureMapper.mapAsmMethod(this).toString()
private fun IrSimpleFunction.toSignature(): String = collectionStubComputer.getSignature(this)
private fun createStubMethod(
function: IrSimpleFunction, irClass: IrClass, substitutionMap: Map<IrTypeParameterSymbol, IrType>
@@ -136,15 +137,58 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla
return mutableClass.typeParameters.map { it.symbol }.zip(readOnlyClassTypeArguments).toMap()
}
private inner class StubsForCollectionClass(
// Compute stubs that should be generated, compare based on signature
private fun generateRelevantStubMethods(irClass: IrClass): Set<IrSimpleFunction> {
val ourStubsForCollectionClasses = collectionStubComputer.stubsForCollectionClasses(irClass)
val superStubClasses = irClass.superClass?.superClassChain?.map { superClass ->
collectionStubComputer.stubsForCollectionClasses(superClass).map { it.readOnlyClass }
}?.fold(emptySet<IrClassSymbol>(), { a, b -> a union b }) ?: emptySet()
// do a second filtering to ensure only most relevant classes are included.
val redundantClasses = ourStubsForCollectionClasses.filter { (readOnlyClass) ->
ourStubsForCollectionClasses.any { readOnlyClass != it.readOnlyClass && it.readOnlyClass.isSubtypeOfClass(readOnlyClass) }
}.map { it.readOnlyClass }
// perform type substitution and type erasure here
return ourStubsForCollectionClasses.filter { (readOnlyClass) ->
readOnlyClass !in redundantClasses && readOnlyClass !in superStubClasses
}.flatMap { (readOnlyClass, mutableClass, mutableOnlyMethods) ->
val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass)
mutableOnlyMethods.map { function ->
createStubMethod(function, irClass, substitutionMap)
}
}.toHashSet()
}
private fun Collection<IrType>.findMostSpecificTypeForClass(classifier: IrClassSymbol): IrType {
val types = this.filter { it.classifierOrNull == classifier }
if (types.isEmpty()) error("No supertype of $classifier in $this")
if (types.size == 1) return types.first()
// Find the first type in the list such that it's a subtype of every other type in that list
return types.first { type ->
types.all { other -> type.isSubtypeOfClass(other.classOrNull!!) }
}
}
private val IrClass.superClass: IrClass?
get() = superTypes.mapNotNull { it.getClass() }.singleOrNull { !it.isJvmInterface }
private val IrClass.superClassChain: Sequence<IrClass>
get() = generateSequence(this) { it.superClass }
}
internal class CollectionStubComputer(val context: JvmBackendContext) {
fun getSignature(irFunction: IrSimpleFunction): String = context.methodSignatureMapper.mapAsmMethod(irFunction).toString()
inner class StubsForCollectionClass(
val readOnlyClass: IrClassSymbol,
val mutableClass: IrClassSymbol
) {
val mutableOnlyMethods: Collection<IrSimpleFunction> by lazy {
val readOnlyMethodSignatures = readOnlyClass.functions.map { it.owner.toSignature() }.toHashSet()
val readOnlyMethodSignatures = readOnlyClass.functions.map { getSignature(it.owner) }.toHashSet()
mutableClass.functions
.map { it.owner }
.filter { it.toSignature() !in readOnlyMethodSignatures }
.filter { getSignature(it) !in readOnlyMethodSignatures }
.toHashSet()
}
@@ -170,62 +214,16 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla
}
}
// Compute stubs that should be generated, compare based on signature
private fun generateRelevantStubMethods(irClass: IrClass): Set<IrSimpleFunction> {
val ourStubsForCollectionClasses = stubsForCollectionClasses(irClass)
val superStubClasses = irClass.superClass?.superClassChain?.map { superClass ->
stubsForCollectionClasses(superClass).map { it.readOnlyClass }
}?.fold(emptySet<IrClassSymbol>(), { a, b -> a union b }) ?: emptySet()
// do a second filtering to ensure only most relevant classes are included.
val redundantClasses = ourStubsForCollectionClasses.filter { (readOnlyClass) ->
ourStubsForCollectionClasses.any { readOnlyClass != it.readOnlyClass && it.readOnlyClass.isSubtypeOfClass(readOnlyClass) }
}.map { it.readOnlyClass }
// perform type substitution and type erasure here
return ourStubsForCollectionClasses.filter { (readOnlyClass) ->
readOnlyClass !in redundantClasses && readOnlyClass !in superStubClasses
}.flatMap { (readOnlyClass, mutableClass, mutableOnlyMethods) ->
val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass)
mutableOnlyMethods.map { function ->
createStubMethod(function, irClass, substitutionMap)
}
}.toHashSet()
}
fun IrClass.comesFromJava() = origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
private fun Collection<IrType>.findMostSpecificTypeForClass(classifier: IrClassSymbol): IrType {
val types = this.filter { it.classifierOrNull == classifier }
if (types.isEmpty()) error("No supertype of $classifier in $this")
if (types.size == 1) return types.first()
// Find the first type in the list such that it's a subtype of every other type in that list
return types.first { type ->
types.all { other -> type.isSubtypeOfClass(other.classOrNull!!) }
}
}
private val stubsCache = mutableMapOf<IrClass, Collection<StubsForCollectionClass>>()
private fun stubsForCollectionClasses(irClass: IrClass): Collection<StubsForCollectionClass> =
fun stubsForCollectionClasses(irClass: IrClass): Collection<StubsForCollectionClass> =
stubsCache.getOrPut(irClass) {
if (irClass.comesFromJava()) emptySet()
else preComputedStubs.filter { (readOnlyClass, mutableClass) ->
irClass.superTypes.any { supertypeSymbol ->
val supertype = supertypeSymbol.classOrNull?.owner
// We need to generate stub methods for following 2 cases:
// current class's direct super type is a java class or kotlin interface, and is an subtype of an immutable collection
supertype != null
&& (supertype.comesFromJava() || supertype.isInterface)
&& supertypeSymbol.isSubtypeOfClass(readOnlyClass)
&& !irClass.symbol.isSubtypeOfClass(mutableClass)
}
!irClass.symbol.isSubtypeOfClass(mutableClass) &&
irClass.superTypes.any { it.isSubtypeOfClass(readOnlyClass) }
}
}
private val IrClass.superClass: IrClass?
get() = superTypes.mapNotNull { (it as? IrSimpleType)?.classifier?.owner as? IrClass }.singleOrNull { !it.isInterface }
private val IrClass.superClassChain: Sequence<IrClass>
get() = generateSequence(this) { it.superClass }
private fun IrClass.comesFromJava() = origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB
}