JVM_IR: do not generate excessive stubs for immutable collections

This commit is contained in:
Georgy Bronnikov
2020-03-05 15:36:42 +03:00
parent 9955f342c0
commit 92268c8144
4 changed files with 66 additions and 12 deletions
@@ -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");
@@ -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");