From b4c84aa2a643ca033556b7dda5328ae4652d58ec Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 10 Sep 2021 02:48:22 +0200 Subject: [PATCH] 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. --- .../kotlin/backend/jvm/JvmBackendContext.kt | 6 +- .../kotlin/backend/jvm/SpecialBridge.kt | 45 +++++++ .../backend/jvm/caches/BridgeLoweringCache.kt | 99 ++++++++++++++ .../jvm/caches/CollectionStubComputer.kt | 104 +++++++++++++++ .../backend/jvm/lower/BridgeLowering.kt | 123 +----------------- .../jvm/lower/CollectionStubMethodLowering.kt | 89 +------------ 6 files changed, 257 insertions(+), 209 deletions(-) create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/SpecialBridge.kt create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/caches/BridgeLoweringCache.kt create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/caches/CollectionStubComputer.kt diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt index d92cf7e086c..7f0d1a1107a 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt @@ -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.phaser.PhaseConfig 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.IrTypeMapper import org.jetbrains.kotlin.backend.jvm.codegen.MethodSignatureMapper import org.jetbrains.kotlin.backend.jvm.codegen.createFakeContinuation 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.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor @@ -113,7 +113,7 @@ class JvmBackendContext( fun getOverridesWithoutStubs(function: IrSimpleFunction): List = overridesWithoutStubs.getOrElse(function) { function.overriddenSymbols } - internal val bridgeLoweringCache = BridgeLowering.BridgeLoweringCache(this) + internal val bridgeLoweringCache = BridgeLoweringCache(this) internal val functionsWithSpecialBridges: MutableSet = ConcurrentHashMap.newKeySet() override var inVerbosePhase: Boolean = false // TODO: needs parallelizing diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/SpecialBridge.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/SpecialBridge.kt new file mode 100644 index 00000000000..4711923320a --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/SpecialBridge.kt @@ -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.keySet` method has a return type of `Set`, 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? = 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, +) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/caches/BridgeLoweringCache.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/caches/BridgeLoweringCache.kt new file mode 100644 index 00000000000..13ebb7b0605 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/caches/BridgeLoweringCache.kt @@ -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() + + 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 + } +} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/caches/CollectionStubComputer.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/caches/CollectionStubComputer.kt new file mode 100644 index 00000000000..767bcd4fc15 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/caches/CollectionStubComputer.kt @@ -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 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 : ListIterator'. + // 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 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>() + + fun stubsForCollectionClasses(irClass: IrClass): List = + stubsCache.getOrPut(irClass) { + computeStubsForCollectionClasses(irClass) + } + + private fun computeStubsForCollectionClasses(irClass: IrClass): List { + 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 +} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt index b12827e9f26..eb0e0a940c0 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt @@ -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.isMethodOfAny 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.jvm.JvmBackendContext 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.ir.* 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.builders.* 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.expressions.* 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.types.* 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.org.objectweb.asm.Type 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 @@ -127,39 +128,6 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass val overriddenSymbols: MutableList = 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.keySet` method has a return type of `Set`, 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? = 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) { irFile.transformChildrenVoid() } @@ -601,86 +569,6 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass private val IrFunction.jvmMethod: Method 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() - - 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. @@ -691,4 +579,3 @@ private fun IrSimpleFunction.resolvesToClass(): Boolean { private fun IrSimpleFunction.overriddenFromClass(): IrSimpleFunction? = overriddenSymbols.singleOrNull { !it.owner.parentAsClass.isJvmInterface }?.owner - diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CollectionStubMethodLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CollectionStubMethodLowering.kt index 1fb7a4beb06..19189815dd7 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CollectionStubMethodLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/CollectionStubMethodLowering.kt @@ -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.phaser.makeIrFilePhase 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.descriptors.Modality 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.TypeCheckerState import org.jetbrains.kotlin.utils.addToStdlib.cast -import java.util.concurrent.ConcurrentHashMap internal val collectionStubMethodLowering = makeIrFilePhase( ::CollectionStubMethodLowering, @@ -390,90 +390,3 @@ internal class CollectionStubMethodLowering(val context: JvmBackendContext) : Cl private val IrClass.superClassChain: Sequence get() = generateSequence(this) { it.superClass } } - -internal interface StubsForCollectionClass { - val readOnlyClass: IrClassSymbol - val mutableClass: IrClassSymbol - val candidatesForStubs: Collection -} - -internal class CollectionStubComputer(val context: JvmBackendContext) { - - private class LazyStubsForCollectionClass( - override val readOnlyClass: IrClassSymbol, - override val mutableClass: IrClassSymbol - ) : StubsForCollectionClass { - override val candidatesForStubs: Collection 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 : ListIterator'. - // 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 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>() - - fun stubsForCollectionClasses(irClass: IrClass): List = - stubsCache.getOrPut(irClass) { - computeStubsForCollectionClasses(irClass) - } - - private fun computeStubsForCollectionClasses(irClass: IrClass): List { - 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) } - } - } -}