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
|
// Compute stubs that should be generated, compare based on signature
|
||||||
private fun generateRelevantStubMethods(irClass: IrClass): Set<IrSimpleFunction> {
|
private fun generateRelevantStubMethods(irClass: IrClass): Set<IrSimpleFunction> {
|
||||||
val ourStubsForCollectionClasses = preComputedStubs.filter { (readOnlyClass, mutableClass) ->
|
val ourStubsForCollectionClasses = stubsForCollectionClasses(irClass)
|
||||||
irClass.superTypes.any { supertypeSymbol ->
|
val superStubClasses = irClass.superClass?.superClassChain?.map { superClass ->
|
||||||
val supertype = supertypeSymbol.classOrNull?.owner
|
stubsForCollectionClasses(superClass).map { it.readOnlyClass }
|
||||||
// We need to generate stub methods for following 2 cases:
|
}?.fold(emptySet<IrClassSymbol>(), { a, b -> a union b }) ?: emptySet()
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// do a second filtering to ensure only most relevant classes are included.
|
// do a second filtering to ensure only most relevant classes are included.
|
||||||
val redundantClasses = ourStubsForCollectionClasses.filter { (readOnlyClass) ->
|
val redundantClasses = ourStubsForCollectionClasses.filter { (readOnlyClass) ->
|
||||||
@@ -191,7 +184,7 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla
|
|||||||
|
|
||||||
// perform type substitution and type erasure here
|
// perform type substitution and type erasure here
|
||||||
return ourStubsForCollectionClasses.filter { (readOnlyClass) ->
|
return ourStubsForCollectionClasses.filter { (readOnlyClass) ->
|
||||||
readOnlyClass !in redundantClasses
|
readOnlyClass !in redundantClasses && readOnlyClass !in superStubClasses
|
||||||
}.flatMap { (readOnlyClass, mutableClass, mutableOnlyMethods) ->
|
}.flatMap { (readOnlyClass, mutableClass, mutableOnlyMethods) ->
|
||||||
val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass)
|
val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass)
|
||||||
mutableOnlyMethods.map { function ->
|
mutableOnlyMethods.map { function ->
|
||||||
@@ -211,4 +204,28 @@ private class CollectionStubMethodLowering(val context: JvmBackendContext) : Cla
|
|||||||
types.all { other -> type.isSubtypeOfClass(other.classOrNull!!) }
|
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");
|
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")
|
@TestMetadata("componentEvaluatesOnlyOnce.kt")
|
||||||
public void testComponentEvaluatesOnlyOnce() throws Exception {
|
public void testComponentEvaluatesOnlyOnce() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/componentEvaluatesOnlyOnce.kt");
|
runTest("compiler/testData/codegen/bytecodeText/componentEvaluatesOnlyOnce.kt");
|
||||||
|
|||||||
+5
@@ -84,6 +84,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/charConstant.kt");
|
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")
|
@TestMetadata("componentEvaluatesOnlyOnce.kt")
|
||||||
public void testComponentEvaluatesOnlyOnce() throws Exception {
|
public void testComponentEvaluatesOnlyOnce() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/componentEvaluatesOnlyOnce.kt");
|
runTest("compiler/testData/codegen/bytecodeText/componentEvaluatesOnlyOnce.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user