JVM_IR: do not generate excessive stubs for immutable collections
This commit is contained in:
+29
-12
@@ -172,17 +172,10 @@ 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 = 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)
|
||||
}
|
||||
}
|
||||
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) ->
|
||||
@@ -191,7 +184,7 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla
|
||||
|
||||
// perform type substitution and type erasure here
|
||||
return ourStubsForCollectionClasses.filter { (readOnlyClass) ->
|
||||
readOnlyClass !in redundantClasses
|
||||
readOnlyClass !in redundantClasses && readOnlyClass !in superStubClasses
|
||||
}.flatMap { (readOnlyClass, mutableClass, mutableOnlyMethods) ->
|
||||
val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass)
|
||||
mutableOnlyMethods.map { function ->
|
||||
@@ -211,4 +204,28 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla
|
||||
types.all { other -> type.isSubtypeOfClass(other.classOrNull!!) }
|
||||
}
|
||||
}
|
||||
|
||||
private val stubsCache = mutableMapOf<IrClass, Collection<StubsForCollectionClass>>()
|
||||
|
||||
private 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
open class BaseEmptyList<T> : List<T> {
|
||||
override val size: Int = 0
|
||||
override fun contains(element: T): Boolean = false
|
||||
override fun containsAll(elements: Collection<T>): Boolean = false
|
||||
override fun get(index: Int): T = error("Do not call")
|
||||
override fun indexOf(element: T): Int = -1
|
||||
override fun isEmpty(): Boolean = true
|
||||
override fun iterator(): Iterator<T> = emptyIterator()
|
||||
override fun lastIndexOf(element: T): Int = -1
|
||||
override fun listIterator(): ListIterator<T> = emptyIterator()
|
||||
override fun listIterator(index: Int): ListIterator<T> = emptyIterator()
|
||||
override fun subList(fromIndex: Int, toIndex: Int): List<T> = this
|
||||
|
||||
private fun emptyIterator() = object : ListIterator<T> {
|
||||
override fun hasNext(): Boolean = false
|
||||
override fun next(): T = error("Do not call")
|
||||
override fun hasPrevious(): Boolean = false
|
||||
override fun nextIndex(): Int = 0
|
||||
override fun previous(): T = error("Do not call")
|
||||
override fun previousIndex(): Int = 0
|
||||
}
|
||||
}
|
||||
|
||||
class DerivedEmptyList : BaseEmptyList<String>(), List<String>
|
||||
|
||||
// add() stub should be generated for BaseEmptyList, but not for DerivedEmptyList
|
||||
// 1 public add\(ILjava/lang/Object;\)V
|
||||
@@ -84,6 +84,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
runTest("compiler/testData/codegen/bytecodeText/charConstant.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("collectionStubs.kt")
|
||||
public void testCollectionStubs() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/collectionStubs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("componentEvaluatesOnlyOnce.kt")
|
||||
public void testComponentEvaluatesOnlyOnce() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/componentEvaluatesOnlyOnce.kt");
|
||||
|
||||
+5
@@ -84,6 +84,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
||||
runTest("compiler/testData/codegen/bytecodeText/charConstant.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("collectionStubs.kt")
|
||||
public void testCollectionStubs() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/collectionStubs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("componentEvaluatesOnlyOnce.kt")
|
||||
public void testComponentEvaluatesOnlyOnce() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/componentEvaluatesOnlyOnce.kt");
|
||||
|
||||
Reference in New Issue
Block a user