From 9d63412b3e74e06452c3147b3cce7284f20dc333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Sch=C3=A4fer?= Date: Thu, 2 Jul 2020 10:58:03 +0200 Subject: [PATCH] JVM IR: Produce correct generic signatures for special bridge methods --- .../common/lower/SpecialBridgeMethods.kt | 81 +++++---- .../backend/jvm/lower/BridgeLowering.kt | 165 +++++++++++++----- .../codegen/box/jvm8/treeMapBridge.kt | 1 + .../bytecodeListing/immutableCollection.kt | 1 - .../removeAtTwoSpecialBridges.kt | 1 - .../specialBridges/signatures/genericClass.kt | 1 - .../signatures/nonGenericClass.kt | 2 +- .../signatures/nonGenericClass_ir.txt | 57 ++++++ .../signatures/partiallySpecializedClass.kt | 2 +- .../partiallySpecializedClass_ir.txt | 51 ++++++ .../bytecodeText/builtinFunctions/removeAt.kt | 25 ++- .../codegen/bytecodeText/mapGetOrDefault.kt | 10 ++ 12 files changed, 308 insertions(+), 89 deletions(-) create mode 100644 compiler/testData/codegen/bytecodeListing/specialBridges/signatures/nonGenericClass_ir.txt create mode 100644 compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass_ir.txt diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SpecialBridgeMethods.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SpecialBridgeMethods.kt index a08b2b48fc8..4031763819e 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SpecialBridgeMethods.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/SpecialBridgeMethods.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2020 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. */ @@ -19,7 +19,14 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name data class SpecialMethodWithDefaultInfo( - val defaultValueGenerator: (IrSimpleFunction) -> IrExpression, val argumentsToCheck: Int, val needsArgumentBoxing: Boolean = false + val defaultValueGenerator: (IrSimpleFunction) -> IrExpression, + val argumentsToCheck: Int, + val needsArgumentBoxing: Boolean = false, + val needsGenericSignature: Boolean = false, +) + +class BuiltInWithDifferentJvmName( + val needsGenericSignature: Boolean = false, ) class SpecialBridgeMethods(val context: CommonBackendContext) { @@ -54,36 +61,46 @@ class SpecialBridgeMethods(val context: CommonBackendContext) { IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, bridge.valueParameters[1].symbol) private val SPECIAL_METHODS_WITH_DEFAULTS_MAP = mapOf( - makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "contains", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1), - makeDescription(KotlinBuiltIns.FQ_NAMES.mutableCollection, "remove", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1, true), - makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsKey", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1), - makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsValue", 1) to SpecialMethodWithDefaultInfo(::constFalse, 1), - makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 2) to SpecialMethodWithDefaultInfo(::constFalse, 2), - makeDescription(KotlinBuiltIns.FQ_NAMES.map, "getOrDefault", 2) to SpecialMethodWithDefaultInfo(::getSecondArg, 1), - makeDescription(KotlinBuiltIns.FQ_NAMES.map, "get", 1) to SpecialMethodWithDefaultInfo(::constNull, 1), - makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 1) to SpecialMethodWithDefaultInfo(::constNull, 1), - makeDescription(KotlinBuiltIns.FQ_NAMES.list, "indexOf", 1) to SpecialMethodWithDefaultInfo(::constMinusOne, 1), - makeDescription(KotlinBuiltIns.FQ_NAMES.list, "lastIndexOf", 1) to SpecialMethodWithDefaultInfo(::constMinusOne, 1) + makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "contains", 1) to + SpecialMethodWithDefaultInfo(::constFalse, 1), + makeDescription(KotlinBuiltIns.FQ_NAMES.mutableCollection, "remove", 1) to + SpecialMethodWithDefaultInfo(::constFalse, 1, needsArgumentBoxing = true), + makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsKey", 1) to + SpecialMethodWithDefaultInfo(::constFalse, 1), + makeDescription(KotlinBuiltIns.FQ_NAMES.map, "containsValue", 1) to + SpecialMethodWithDefaultInfo(::constFalse, 1), + makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 2) to + SpecialMethodWithDefaultInfo(::constFalse, 2), + makeDescription(KotlinBuiltIns.FQ_NAMES.list, "indexOf", 1) to + SpecialMethodWithDefaultInfo(::constMinusOne, 1), + makeDescription(KotlinBuiltIns.FQ_NAMES.list, "lastIndexOf", 1) to + SpecialMethodWithDefaultInfo(::constMinusOne, 1), + makeDescription(KotlinBuiltIns.FQ_NAMES.map, "getOrDefault", 2) to + SpecialMethodWithDefaultInfo(::getSecondArg, 1, needsGenericSignature = true), + makeDescription(KotlinBuiltIns.FQ_NAMES.map, "get", 1) to + SpecialMethodWithDefaultInfo(::constNull, 1, needsGenericSignature = true), + makeDescription(KotlinBuiltIns.FQ_NAMES.mutableMap, "remove", 1) to + SpecialMethodWithDefaultInfo(::constNull, 1, needsGenericSignature = true) ) - private val SPECIAL_PROPERTIES_SET = setOf( - makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "size"), - makeDescription(KotlinBuiltIns.FQ_NAMES.map, "size"), - makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "length"), - makeDescription(KotlinBuiltIns.FQ_NAMES.map, "keys"), - makeDescription(KotlinBuiltIns.FQ_NAMES.map, "values"), - makeDescription(KotlinBuiltIns.FQ_NAMES.map, "entries") + private val SPECIAL_PROPERTIES_SET = mapOf( + makeDescription(KotlinBuiltIns.FQ_NAMES.collection, "size") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.map, "size") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "length") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.map, "keys") to BuiltInWithDifferentJvmName(needsGenericSignature = true), + makeDescription(KotlinBuiltIns.FQ_NAMES.map, "values") to BuiltInWithDifferentJvmName(needsGenericSignature = true), + makeDescription(KotlinBuiltIns.FQ_NAMES.map, "entries") to BuiltInWithDifferentJvmName(needsGenericSignature = true) ) - private val SPECIAL_METHODS_SETS = setOf( - makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toByte"), - makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toShort"), - makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toInt"), - makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toLong"), - makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toFloat"), - makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toDouble"), - makeDescription(KotlinBuiltIns.FQ_NAMES.mutableList, "removeAt", 1), - makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "get", 1) + private val SPECIAL_METHODS_SETS = mapOf( + makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toByte") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toShort") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toInt") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toLong") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toFloat") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.number.toSafe(), "toDouble") to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.charSequence.toSafe(), "get", 1) to BuiltInWithDifferentJvmName(), + makeDescription(KotlinBuiltIns.FQ_NAMES.mutableList, "removeAt", 1) to BuiltInWithDifferentJvmName(needsGenericSignature = true) ) fun findSpecialWithOverride(irFunction: IrSimpleFunction): Pair? { @@ -101,15 +118,15 @@ class SpecialBridgeMethods(val context: CommonBackendContext) { return SPECIAL_METHODS_WITH_DEFAULTS_MAP[description] } - fun isBuiltInWithDifferentJvmName(irFunction: IrSimpleFunction): Boolean { + fun getBuiltInWithDifferentJvmName(irFunction: IrSimpleFunction): BuiltInWithDifferentJvmName? { irFunction.correspondingPropertySymbol?.let { val classFqName = irFunction.parentAsClass.fqNameWhenAvailable - ?: return false + ?: return null - return makeDescription(classFqName, it.owner.name.asString()) in SPECIAL_PROPERTIES_SET + return SPECIAL_PROPERTIES_SET[makeDescription(classFqName, it.owner.name.asString())] } - return irFunction.toDescription() in SPECIAL_METHODS_SETS + return SPECIAL_METHODS_SETS[irFunction.toDescription()] } } 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 03e089eae71..ed4cc58ce31 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 @@ -24,9 +24,11 @@ import org.jetbrains.kotlin.descriptors.Visibilities 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.* @@ -127,19 +129,36 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, val overriddenSymbols: MutableList = mutableListOf() ) - // Represents a special bridge to `overridden`. Special bridges are always public and non-synthetic. - // There are ten type-safe wrappers for different collection methods, which have an additional `methodInfo` - // field. There is only one special bridge method which uses generic types in Java (MutableList.removeAt) - // and needs a `specializedReturnType` for its overrides. Finally, we sometimes need to use INVOKESPECIAL - // to invoke an existing special bridge implementation in a superclass, which is what `superQualifierSymbol` - // is for. + // 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. private data class SpecialBridge( val overridden: IrSimpleFunction, val signature: Method, - val specializedReturnType: IrType? = null, + // 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, ) override fun lower(irFile: IrFile) = irFile.transformChildrenVoid() @@ -206,7 +225,9 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, blacklist += irFunction.overriddenFinalSpecialBridges() // We only generate a special bridge method if it does not clash with a final method in a superclass or the current method - if (specialBridge.signature !in blacklist && (!irFunction.isFakeOverride || irFunction.jvmMethod != specialBridge.signature)) { + val specialBridgeTarget = if ( + specialBridge.signature !in blacklist && (!irFunction.isFakeOverride || irFunction.jvmMethod != specialBridge.signature) + ) { // If irFunction is a fake override, we replace it with a stub and redirect all calls to irFunction with // calls to the stub instead. Otherwise we'll end up calling the special method itself and get into an // infinite loop. @@ -243,14 +264,26 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, blacklist += bridgeTarget.jvmMethod } - irClass.addSpecialBridge(specialBridge, bridgeTarget) blacklist += specialBridge.signature + irClass.addSpecialBridge(specialBridge, bridgeTarget) + } else { + irFunction } // Deal with existing function that override special bridge methods. if (!irFunction.isFakeOverride && specialBridge.methodInfo != null) { irFunction.rewriteSpecialMethodBody(targetMethod, specialBridge.signature, specialBridge.methodInfo) } + + // For generic special bridge methods we need to generate bridges for generic overrides coming from Java or Kotlin interfaces. + if (specialBridge.substitutedReturnType != null) { + for (overriddenSpecialBridge in irFunction.overriddenSpecialBridges()) { + if (overriddenSpecialBridge.signature !in blacklist) { + irClass.addSpecialBridge(overriddenSpecialBridge, specialBridgeTarget) + blacklist += overriddenSpecialBridge.signature + } + } + } } else if (irFunction.isJvmAbstract(context.state.jvmDefaultMode)) { // Do not generate bridge methods for abstract methods which do not override a special bridge method. // This matches the behavior of the JVM backend, but it does mean that we generate superfluous bridges @@ -271,6 +304,9 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, } } + if (generated.isEmpty()) + return + // For concrete fake overrides, some of the bridges may be inherited from the super-classes. Specifically, bridges for all // declarations that are reachable from all concrete immediate super-functions of the given function. Note that all such bridges are // guaranteed to delegate to the same implementation as bridges for the given function, that's why it's safe to inherit them. @@ -289,32 +325,38 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, // Returns the special bridge overridden by the current methods if it exists. private val IrSimpleFunction.specialBridgeOrNull: SpecialBridge? get() = specialBridgeCache.getOrPutNullable(symbol) { - val specialMethodInfo = specialBridgeMethods.getSpecialMethodInfo(this) - return when { - specialMethodInfo != null -> - // Note that there are type-safe special bridges with generic return types in Java (namely Map.getOrDefault, - // Map.get, and MutableMap.remove), but the JVM backend does not produce overrides with specialized return - // types for them. So for compatibility, neither do we. - SpecialBridge(this, jvmMethod, methodInfo = specialMethodInfo) - - specialBridgeMethods.isBuiltInWithDifferentJvmName(this) -> - if (returnType.isTypeParameter()) - SpecialBridge(this, jvmMethod, specializedReturnType = returnType) - else - SpecialBridge(this, jvmMethod) - - else -> { - val overriddenSpecialBridge = overriddenSymbols.asSequence().mapNotNull { it.owner.specialBridgeOrNull }.firstOrNull() - if (overriddenSpecialBridge?.specializedReturnType != null) { - val specializedSignature = Method( - overriddenSpecialBridge.signature.name, - context.methodSignatureMapper.mapReturnType(this), - overriddenSpecialBridge.signature.argumentTypes - ) - overriddenSpecialBridge.copy(signature = specializedSignature, specializedReturnType = returnType) - } else { - overriddenSpecialBridge + specialBridgeMethods.getSpecialMethodInfo(this)?.let { specialMethodInfo -> + SpecialBridge(this, jvmMethod, methodInfo = specialMethodInfo, needsGenericSignature = specialMethodInfo.needsGenericSignature) + } ?: specialBridgeMethods.getBuiltInWithDifferentJvmName(this)?.let { specialBuiltInInfo -> + SpecialBridge(this, jvmMethod, needsGenericSignature = specialBuiltInInfo.needsGenericSignature) + } ?: overriddenSymbols.asSequence().mapNotNull { it.owner.specialBridgeOrNull }.firstOrNull()?.let { specialBridge -> + if (specialBridge.needsGenericSignature) { + // Compute the substituted signature. + val erasedParameterCount = specialBridge.methodInfo?.argumentsToCheck ?: 0 + val substitutedParameterTypes = valueParameters.mapIndexed { index, param -> + if (index < erasedParameterCount) context.irBuiltIns.anyNType else param.type } + + val substitutedOverride = buildFun { + updateFrom(specialBridge.overridden) + name = Name.identifier(specialBridge.signature.name) + returnType = this@specialBridgeOrNull.returnType + }.apply { + // All existing special bridges only have value parameter types. + valueParameters = this@specialBridgeOrNull.valueParameters.zip(substitutedParameterTypes).map { (param, type) -> + param.copyTo(this, IrDeclarationOrigin.BRIDGE, type = type) + } + overriddenSymbols = listOf(specialBridge.overridden.symbol) + parent = this@specialBridgeOrNull.parent + } + + specialBridge.copy( + signature = substitutedOverride.jvmMethod, + substitutedParameterTypes = substitutedParameterTypes, + substitutedReturnType = returnType + ) + } else { + specialBridge } } } @@ -334,6 +376,14 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, it.specialBridgeOrNull?.signature?.takeIf { bridgeSignature -> bridgeSignature != it.jvmMethod } } + // Sequence of special bridge methods which were not implemented in Kotlin superclasses. + private fun IrSimpleFunction.overriddenSpecialBridges(): Sequence = allOverridden().mapNotNull { + if (it.parentAsClass.isInterface || it.comesFromJava()) + it.specialBridgeOrNull?.takeIf { bridge -> bridge.signature != jvmMethod } + ?.copy(isFinal = false, isSynthetic = true, methodInfo = null) + else null + } + private fun IrClass.addAbstractMethodStub(irFunction: IrSimpleFunction, needsArgumentBoxing: Boolean) = addFunction { updateFrom(irFunction) @@ -347,7 +397,10 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, // However, we cannot link in the new function as the new accessor for the property, since there might still // be references to the original fake override stub. copyCorrespondingPropertyFrom(irFunction) - copyParametersWithErasure(this@addAbstractMethodStub, irFunction, needsArgumentBoxing) + dispatchReceiverParameter = thisReceiver?.copyTo(this, type = defaultType) + valueParameters = irFunction.valueParameters.map { param -> + param.copyTo(this, type = if (needsArgumentBoxing) param.type.makeNullable() else param.type) + } } private fun IrClass.addBridge(bridge: Bridge, target: IrSimpleFunction): IrSimpleFunction = @@ -379,15 +432,17 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, private fun IrClass.addSpecialBridge(specialBridge: SpecialBridge, target: IrSimpleFunction): IrSimpleFunction = addFunction { modality = if (specialBridge.isFinal) Modality.FINAL else Modality.OPEN - origin = IrDeclarationOrigin.BRIDGE_SPECIAL + origin = if (specialBridge.isSynthetic) IrDeclarationOrigin.BRIDGE else IrDeclarationOrigin.BRIDGE_SPECIAL name = Name.identifier(specialBridge.signature.name) - returnType = specialBridge.specializedReturnType ?: specialBridge.overridden.returnType.eraseTypeParameters() + returnType = specialBridge.substitutedReturnType ?: specialBridge.overridden.returnType.eraseTypeParameters() }.apply { copyParametersWithErasure( this@addSpecialBridge, specialBridge.overridden, - specialBridge.methodInfo?.needsArgumentBoxing == true + specialBridge.methodInfo?.needsArgumentBoxing == true, + specialBridge.substitutedParameterTypes ) + body = context.createIrBuilder(symbol).irBlockBody { specialBridge.methodInfo?.let { info -> valueParameters.take(info.argumentsToCheck).forEach { @@ -396,7 +451,7 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, } +irReturn(delegatingCall(this@apply, target, specialBridge.superQualifierSymbol)) } - overriddenSymbols += specialBridge.overridden.symbol + overriddenSymbols = listOf(specialBridge.overridden.symbol) } private fun IrSimpleFunction.rewriteSpecialMethodBody( @@ -421,8 +476,8 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, argumentsToCheck.forEach { val parameterType = it.type if (!parameterType.isNullable()) { - val newParameter = it.copyTo(this@rewriteSpecialMethodBody, type = context.irBuiltIns.anyNType) - variableMap.put(valueParameters[it.index], newParameter) + val newParameter = it.copyTo(this@rewriteSpecialMethodBody, type = parameterType.makeNullable()) + variableMap[valueParameters[it.index]] = newParameter newValueParameters[it.index] = newParameter +parameterTypeCheck( newParameter, @@ -463,17 +518,33 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, private fun IrBuilderWithScope.parameterTypeCheck(parameter: IrValueParameter, type: IrType, defaultValue: IrExpression) = irIfThen(context.irBuiltIns.unitType, irNot(irIs(irGet(parameter), type)), irReturn(defaultValue)) - private fun IrSimpleFunction.copyParametersWithErasure(irClass: IrClass, from: IrSimpleFunction, forceArgumentBoxing: Boolean = false) { + private fun IrSimpleFunction.copyParametersWithErasure( + irClass: IrClass, + from: IrSimpleFunction, + forceArgumentBoxing: Boolean = false, + substitutedParameterTypes: List? = null + ) { // This is a workaround for a bug affecting fake overrides. Sometimes we encounter fake overrides // with dispatch receivers pointing at a superclass instead of the current class. dispatchReceiverParameter = irClass.thisReceiver?.copyTo(this, type = irClass.defaultType) extensionReceiverParameter = from.extensionReceiverParameter?.copyWithTypeErasure(this, forceArgumentBoxing) - valueParameters = from.valueParameters.map { it.copyWithTypeErasure(this, forceArgumentBoxing) } + valueParameters = if (substitutedParameterTypes != null) { + from.valueParameters.zip(substitutedParameterTypes).map { (param, type) -> + param.copyWithTypeErasure(this, forceArgumentBoxing, type) + } + } else { + from.valueParameters.map { it.copyWithTypeErasure(this, forceArgumentBoxing) } + } } - private fun IrValueParameter.copyWithTypeErasure(target: IrSimpleFunction, forceBoxing: Boolean = false): IrValueParameter = copyTo( + private fun IrValueParameter.copyWithTypeErasure( + target: IrSimpleFunction, + forceArgumentBoxing: Boolean = false, + substitutedType: IrType? = null + ): IrValueParameter = copyTo( target, IrDeclarationOrigin.BRIDGE, - type = type.eraseTypeParameters().let { if (forceBoxing) it.makeNullable() else it }, + type = (substitutedType ?: type.eraseTypeParameters()).let { if (forceArgumentBoxing) it.makeNullable() else it }, + // Currently there are no special bridge methods with vararg parameters, so we don't track substituted vararg element types. varargElementType = varargElementType?.eraseTypeParameters() ) @@ -492,10 +563,10 @@ private class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, to: IrType): IrExpression = if (expression.type == to || to.isAny() || to.isNullableAny()) expression else irImplicitCast(expression, to) - private val signatureCache = mutableMapOf() + private val signatureCache = mutableMapOf() private val IrFunction.jvmMethod: Method - get() = signatureCache.getOrPut(this) { context.methodSignatureMapper.mapAsmMethod(this) } + get() = signatureCache.getOrPut(symbol) { context.methodSignatureMapper.mapAsmMethod(this) } } private fun IrDeclaration.comesFromJava() = parentAsClass.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB diff --git a/compiler/testData/codegen/box/jvm8/treeMapBridge.kt b/compiler/testData/codegen/box/jvm8/treeMapBridge.kt index 852cfb7774c..215b930c53b 100644 --- a/compiler/testData/codegen/box/jvm8/treeMapBridge.kt +++ b/compiler/testData/codegen/box/jvm8/treeMapBridge.kt @@ -1,4 +1,5 @@ // TARGET_BACKEND: JVM +// IGNORE_BACKEND_FIR: JVM_IR // java.lang.NoSuchMethodError: java.util.TreeMap.remove // IGNORE_BACKEND: ANDROID diff --git a/compiler/testData/codegen/bytecodeListing/immutableCollection.kt b/compiler/testData/codegen/bytecodeListing/immutableCollection.kt index c83250effbb..f5905464a97 100644 --- a/compiler/testData/codegen/bytecodeListing/immutableCollection.kt +++ b/compiler/testData/codegen/bytecodeListing/immutableCollection.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR interface ImmutableCollection : Collection { fun add(element: @UnsafeVariance E): ImmutableCollection fun addAll(elements: Collection<@UnsafeVariance E>): ImmutableCollection diff --git a/compiler/testData/codegen/bytecodeListing/specialBridges/removeAtTwoSpecialBridges.kt b/compiler/testData/codegen/bytecodeListing/specialBridges/removeAtTwoSpecialBridges.kt index 22bf0dbbddc..89daa48784d 100644 --- a/compiler/testData/codegen/bytecodeListing/specialBridges/removeAtTwoSpecialBridges.kt +++ b/compiler/testData/codegen/bytecodeListing/specialBridges/removeAtTwoSpecialBridges.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR open class A0 : MutableList { override fun add(element: E): Boolean { throw UnsupportedOperationException() diff --git a/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/genericClass.kt b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/genericClass.kt index bd5614c012f..1eb2dbc6c7b 100644 --- a/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/genericClass.kt +++ b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/genericClass.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // WITH_SIGNATURES class GenericMap : MutableMap by HashMap() diff --git a/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/nonGenericClass.kt b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/nonGenericClass.kt index a1851b0711c..f3e9b359f42 100644 --- a/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/nonGenericClass.kt +++ b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/nonGenericClass.kt @@ -1,5 +1,5 @@ -// IGNORE_BACKEND: JVM_IR // WITH_SIGNATURES +// Test expectations differ between JVM and JVM IR backends, because of KT-40277. This should be revisited once KT-40277 is resolved. class StringStringMap : MutableMap by HashMap() diff --git a/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/nonGenericClass_ir.txt b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/nonGenericClass_ir.txt new file mode 100644 index 00000000000..753dd39ae65 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/nonGenericClass_ir.txt @@ -0,0 +1,57 @@ +@kotlin.Metadata +public abstract class;Lkotlin/jvm/internal/markers/KMutableMap;> AbstractStringStringMap { + public @org.jetbrains.annotations.NotNull <()Ljava/util/Collection;> method getValues(): java.util.Collection + public bridge final <()Ljava/util/Collection;> method values(): java.util.Collection + public @org.jetbrains.annotations.NotNull <()Ljava/util/Set;> method getKeys(): java.util.Set + public bridge final <()Ljava/util/Set;> method keySet(): java.util.Set + public bridge final <()Ljava/util/Set;>;> method entrySet(): java.util.Set + public @org.jetbrains.annotations.NotNull <()Ljava/util/Set;>;> method getEntries(): java.util.Set + public <(Ljava/util/Map<+Ljava/lang/String;+Ljava/lang/String;>;)V> method putAll(@org.jetbrains.annotations.NotNull p0: java.util.Map): void + public method (): void + public method clear(): void + public method containsKey(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean + public bridge final method containsKey(p0: java.lang.Object): boolean + public method containsValue(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean + public bridge final method containsValue(p0: java.lang.Object): boolean + public @org.jetbrains.annotations.Nullable method get(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String + public synthetic bridge method get(p0: java.lang.Object): java.lang.Object + public bridge final method get(p0: java.lang.Object): java.lang.String + public method getSize(): int + public method isEmpty(): boolean + public @org.jetbrains.annotations.Nullable method put(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: java.lang.String): java.lang.String + public synthetic bridge method put(p0: java.lang.Object, p1: java.lang.Object): java.lang.Object + public @org.jetbrains.annotations.Nullable method remove(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String + public synthetic bridge method remove(p0: java.lang.Object): java.lang.Object + public bridge final method remove(p0: java.lang.Object): java.lang.String + public bridge final method size(): int + private synthetic final field ;> $$delegate_0: java.util.HashMap +} + +@kotlin.Metadata +public final class;Lkotlin/jvm/internal/markers/KMutableMap;> StringStringMap { + public @org.jetbrains.annotations.NotNull <()Ljava/util/Collection;> method getValues(): java.util.Collection + public bridge final <()Ljava/util/Collection;> method values(): java.util.Collection + public @org.jetbrains.annotations.NotNull <()Ljava/util/Set;> method getKeys(): java.util.Set + public bridge final <()Ljava/util/Set;> method keySet(): java.util.Set + public bridge final <()Ljava/util/Set;>;> method entrySet(): java.util.Set + public @org.jetbrains.annotations.NotNull <()Ljava/util/Set;>;> method getEntries(): java.util.Set + public <(Ljava/util/Map<+Ljava/lang/String;+Ljava/lang/String;>;)V> method putAll(@org.jetbrains.annotations.NotNull p0: java.util.Map): void + public method (): void + public method clear(): void + public method containsKey(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean + public bridge final method containsKey(p0: java.lang.Object): boolean + public method containsValue(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean + public bridge final method containsValue(p0: java.lang.Object): boolean + public @org.jetbrains.annotations.Nullable method get(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String + public synthetic bridge method get(p0: java.lang.Object): java.lang.Object + public bridge final method get(p0: java.lang.Object): java.lang.String + public method getSize(): int + public method isEmpty(): boolean + public @org.jetbrains.annotations.Nullable method put(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: java.lang.String): java.lang.String + public synthetic bridge method put(p0: java.lang.Object, p1: java.lang.Object): java.lang.Object + public @org.jetbrains.annotations.Nullable method remove(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String + public synthetic bridge method remove(p0: java.lang.Object): java.lang.Object + public bridge final method remove(p0: java.lang.Object): java.lang.String + public bridge final method size(): int + private synthetic final field ;> $$delegate_0: java.util.HashMap +} diff --git a/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass.kt b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass.kt index de8de6cde65..bf66888a0d1 100644 --- a/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass.kt +++ b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass.kt @@ -1,5 +1,5 @@ -// IGNORE_BACKEND: JVM_IR // WITH_SIGNATURES +// Test expectations differ between JVM and JVM IR backends, because of KT-40277. This should be revisited once KT-40277 is resolved. class StringMap : MutableMap by HashMap() diff --git a/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass_ir.txt b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass_ir.txt new file mode 100644 index 00000000000..ac1b3d773e2 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/specialBridges/signatures/partiallySpecializedClass_ir.txt @@ -0,0 +1,51 @@ +@kotlin.Metadata +public abstract class<Ljava/lang/Object;Ljava/util/Map;Lkotlin/jvm/internal/markers/KMutableMap;> AbstractStringMap { + public @org.jetbrains.annotations.NotNull <()Ljava/util/Collection;> method getValues(): java.util.Collection + public bridge final <()Ljava/util/Collection;> method values(): java.util.Collection + public @org.jetbrains.annotations.NotNull <()Ljava/util/Set;> method getKeys(): java.util.Set + public bridge final <()Ljava/util/Set;> method keySet(): java.util.Set + public bridge final <()Ljava/util/Set;>;> method entrySet(): java.util.Set + public @org.jetbrains.annotations.NotNull <()Ljava/util/Set;>;> method getEntries(): java.util.Set + public @org.jetbrains.annotations.Nullable <(Ljava/lang/Object;)TV;> method get(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.Object + public bridge final <(Ljava/lang/Object;)TV;> method get(p0: java.lang.Object): java.lang.Object + public @org.jetbrains.annotations.Nullable <(Ljava/lang/Object;)TV;> method remove(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.Object + public bridge final <(Ljava/lang/Object;)TV;> method remove(p0: java.lang.Object): java.lang.Object + public @org.jetbrains.annotations.Nullable <(Ljava/lang/String;TV;)TV;> method put(@org.jetbrains.annotations.NotNull p0: java.lang.String, p1: java.lang.Object): java.lang.Object + public <(Ljava/util/Map<+Ljava/lang/String;+TV;>;)V> method putAll(@org.jetbrains.annotations.NotNull p0: java.util.Map): void + public method (): void + public method clear(): void + public method containsKey(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean + public bridge final method containsKey(p0: java.lang.Object): boolean + public method containsValue(p0: java.lang.Object): boolean + public method getSize(): int + public method isEmpty(): boolean + public synthetic bridge method put(p0: java.lang.Object, p1: java.lang.Object): java.lang.Object + public bridge final method size(): int + private synthetic final field ;> $$delegate_0: java.util.HashMap +} + +@kotlin.Metadata +public final class<Ljava/lang/Object;Ljava/util/Map;Lkotlin/jvm/internal/markers/KMutableMap;> StringMap { + public @org.jetbrains.annotations.NotNull <()Ljava/util/Collection;> method getValues(): java.util.Collection + public bridge final <()Ljava/util/Collection;> method values(): java.util.Collection + public @org.jetbrains.annotations.NotNull <()Ljava/util/Set;> method getKeys(): java.util.Set + public bridge final <()Ljava/util/Set;> method keySet(): java.util.Set + public bridge final <()Ljava/util/Set;>;> method entrySet(): java.util.Set + public @org.jetbrains.annotations.NotNull <()Ljava/util/Set;>;> method getEntries(): java.util.Set + public @org.jetbrains.annotations.Nullable <(Ljava/lang/Object;)TV;> method get(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.Object + public bridge final <(Ljava/lang/Object;)TV;> method get(p0: java.lang.Object): java.lang.Object + public @org.jetbrains.annotations.Nullable <(Ljava/lang/Object;)TV;> method remove(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.Object + public bridge final <(Ljava/lang/Object;)TV;> method remove(p0: java.lang.Object): java.lang.Object + public @org.jetbrains.annotations.Nullable <(Ljava/lang/String;TV;)TV;> method put(@org.jetbrains.annotations.NotNull p0: java.lang.String, p1: java.lang.Object): java.lang.Object + public <(Ljava/util/Map<+Ljava/lang/String;+TV;>;)V> method putAll(@org.jetbrains.annotations.NotNull p0: java.util.Map): void + public method (): void + public method clear(): void + public method containsKey(@org.jetbrains.annotations.NotNull p0: java.lang.String): boolean + public bridge final method containsKey(p0: java.lang.Object): boolean + public method containsValue(p0: java.lang.Object): boolean + public method getSize(): int + public method isEmpty(): boolean + public synthetic bridge method put(p0: java.lang.Object, p1: java.lang.Object): java.lang.Object + public bridge final method size(): int + private synthetic final field ;> $$delegate_0: java.util.HashMap +} diff --git a/compiler/testData/codegen/bytecodeText/builtinFunctions/removeAt.kt b/compiler/testData/codegen/bytecodeText/builtinFunctions/removeAt.kt index 849e6cb2022..d084bbd4046 100644 --- a/compiler/testData/codegen/bytecodeText/builtinFunctions/removeAt.kt +++ b/compiler/testData/codegen/bytecodeText/builtinFunctions/removeAt.kt @@ -90,18 +90,33 @@ fun box( } /* -16 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) -> calls in bridges with signature `public final bridge remove\(I\)` + 7 calls from `public synthetic bridge remove\(I\)Ljava/lang/Object;` -9 INVOKEVIRTUAL A[0-9]+\.remove \(I\) -> calls to A1-A9.removeAt 1 INVOKEINTERFACE A9\.remove \(I\) -> call A9.removeAt 1 INVOKEINTERFACE A9\.remove \(Ljava/lang/Object;\) -> call A9.remove + +On the JVM backend we have: +16 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) -> calls in bridges with signature `public final bridge remove\(I\)` + 7 calls from `public synthetic bridge remove\(I\)Ljava/lang/Object;` +9 INVOKEVIRTUAL A[0-9]+\.remove \(I\) -> calls to A1-A9.removeAt 1 INVOKEVIRTUAL A10\.remove \(I\) -> one call in 'box' function + +On the JVM IR backend we have: +9 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) -> calls in non-synthetic bridges with signature `public final bridge remove(I)` +17 INVOKEVIRTUAL A[0-9]+\.remove \(I\) -> calls to A1-A9.removeAt + calls in synthetic bridges with signature `public synthetic bridge remove(I)Ljava/lang/Object;` +2 INVOKEVIRTUAL A10\.remove \(I\) -> one call in 'box' function + call from synthetic `remove(I)` bridge + +This currently differs because of KT-40277, and the test expectations should be revised once KT-40277 is resolved. */ -// 16 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) -// 9 INVOKEVIRTUAL A[0-9]+\.remove \(I\) // 1 INVOKEINTERFACE A9\.remove \(I\) // 1 INVOKEINTERFACE A9\.remove \(Ljava/lang/Object;\) -// 1 INVOKEVIRTUAL A10\.remove \(I\) // 2 INVOKEINTERFACE java\/util\/List.remove \(I\) // 2 INVOKEINTERFACE java\/util\/List.remove \(Ljava/lang/Object;\) +// JVM_TEMPLATES: +// 16 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) +// 9 INVOKEVIRTUAL A[0-9]+\.remove \(I\) +// 1 INVOKEVIRTUAL A10\.remove \(I\) + +// JVM_IR_TEMPLATES: +// 9 INVOKEVIRTUAL A[0-9]+.removeAt \(I\) +// 17 INVOKEVIRTUAL A[0-9]+\.remove \(I\) +// 2 INVOKEVIRTUAL A10\.remove \(I\) diff --git a/compiler/testData/codegen/bytecodeText/mapGetOrDefault.kt b/compiler/testData/codegen/bytecodeText/mapGetOrDefault.kt index 8df6d39b2cb..0af315b6e79 100644 --- a/compiler/testData/codegen/bytecodeText/mapGetOrDefault.kt +++ b/compiler/testData/codegen/bytecodeText/mapGetOrDefault.kt @@ -27,4 +27,14 @@ public class TestMap implements Map { class MyMap: TestMap() +// The Kotlin version of getOrDefault, which redirects to the default implementation in java.util.Map +// 1 public bridge getOrDefault\(Ljava/lang/String;Ljava/lang/String;\)Ljava/lang/String; + +// Test expectations differ between JVM and JVM IR backends, because of KT-40277. This should be revisited once KT-40277 is resolved. + +// JVM_TEMPLATES: // 1 public final bridge getOrDefault\(Ljava/lang/Object;Ljava/lang/Object;\)Ljava/lang/Object; + +// JVM_IR_TEMPLATES: +// 1 public final bridge getOrDefault\(Ljava/lang/Object;Ljava/lang/String;\)Ljava/lang/String; +// 1 public synthetic bridge getOrDefault\(Ljava/lang/Object;Ljava/lang/Object;\)Ljava/lang/Object; \ No newline at end of file