From c990bbb26cb4ad4f249bc6addee0823b34a3ff0e Mon Sep 17 00:00:00 2001 From: "vladislav.grechko" Date: Mon, 17 Oct 2022 16:42:18 +0200 Subject: [PATCH] Get rid of excess method in compiled inline class with custom equals ^KT-54501: Fixed --- .../jvm/lower/JvmInlineClassLowering.kt | 44 +++------- .../lower/JvmValueClassAbstractLowering.kt | 1 + .../jvm/MemoizedInlineClassReplacements.kt | 5 ++ .../org/jetbrains/kotlin/ir/util/IrUtils.kt | 10 ++- .../codegen/box/callableReference/kt49526.kt | 1 - .../inlineClassEqualsOverride.fir.txt | 88 +++++++++++++++++++ .../inlineClassEqualsOverride.kt | 1 + .../inlineClassEqualsOverride.txt | 88 +++++++++++++++++++ 8 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.fir.txt create mode 100644 compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.txt diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt index 8e70511d59d..5570a427b67 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.backend.jvm.lower +import org.jetbrains.kotlin.backend.common.lower.MethodsFromAnyGeneratorForLowerings.Companion.isEquals import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.lower.irBlockBody import org.jetbrains.kotlin.backend.common.lower.loops.forLoopsPhase @@ -28,8 +29,8 @@ import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver import org.jetbrains.kotlin.resolve.JVM_INLINE_ANNOTATION_FQ_NAME -import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS val jvmInlineClassPhase = makeIrFilePhase( ::JvmInlineClassLowering, @@ -118,7 +119,7 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass buildPrimaryInlineClassConstructor(declaration, irConstructor) buildBoxFunction(declaration) buildUnboxFunction(declaration) - buildSpecializedEqualsMethod(declaration) + buildSpecializedEqualsMethodIfNeeded(declaration) addJvmInlineAnnotation(declaration) } @@ -479,35 +480,29 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass irClass.declarations += function } - fun buildSpecializedEqualsMethod(valueClass: IrClass) { + fun buildSpecializedEqualsMethodIfNeeded(valueClass: IrClass) { val function = context.inlineClassReplacements.getSpecializedEqualsMethod(valueClass, context.irBuiltIns) + // Return if we have already built specialized equals as static replacement of typed equals + if (function.body != null) return val left = function.valueParameters[0] val right = function.valueParameters[1] val type = left.type.unboxInlineClass() - val typedEqualsStaticReplacement = findStaticReplacementForTypedEquals(valueClass) - val untypedEquals = valueClass.functions.single { it.overridesEqualsFromAny } + val untypedEquals = valueClass.functions.single { it.isEquals(context) } function.body = context.createIrBuilder(valueClass.symbol).run { irExprBody( - if (typedEqualsStaticReplacement == null) { - if (untypedEquals.origin == IrDeclarationOrigin.DEFINED) { - val boxFunction = this@JvmInlineClassLowering.context.inlineClassReplacements.getBoxFunction(valueClass) + if (untypedEquals.origin == IrDeclarationOrigin.DEFINED) { + val boxFunction = this@JvmInlineClassLowering.context.inlineClassReplacements.getBoxFunction(valueClass) - fun irBox(expr: IrExpression) = irCall(boxFunction).apply { putValueArgument(0, expr) } + fun irBox(expr: IrExpression) = irCall(boxFunction).apply { putValueArgument(0, expr) } - irCall(untypedEquals).apply { - dispatchReceiver = irBox(irGet(left)) - putValueArgument(0, irBox(irGet(right))) - } - } else { - irEquals(coerceInlineClasses(irGet(left), left.type, type), coerceInlineClasses(irGet(right), right.type, type)) + irCall(untypedEquals).apply { + dispatchReceiver = irBox(irGet(left)) + putValueArgument(0, irBox(irGet(right))) } } else { - irCall(typedEqualsStaticReplacement).apply { - putValueArgument(0, irGet(left)) - putValueArgument(1, irGet(right)) - } + irEquals(coerceInlineClasses(irGet(left), left.type, type), coerceInlineClasses(irGet(right), right.type, type)) } ) } @@ -515,15 +510,4 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass valueClass.declarations += function } - private fun findStaticReplacementForTypedEquals(valueClass: IrClass): IrFunction? { - fun isTypedEquals(irFunction: IrFunction): Boolean { - return irFunction.run { - name == EQUALS && returnType.isBoolean() && valueParameters.size == 1 - && (valueParameters[0].type.classFqName?.run { valueClass.hasEqualFqName(this) } ?: false) - && contextReceiverParametersCount == 0 && extensionReceiverParameter == null - } - } - return valueClass.functions - .singleOrNull { context.inlineClassReplacements.originalFunctionForStaticReplacement[it]?.run { isTypedEquals(this) } ?: false } - } } diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt index 7f7e6fb3324..ceb25a611b8 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt @@ -184,6 +184,7 @@ internal abstract class JvmValueClassAbstractLowering(val context: JvmBackendCon function, replacement, when { + function.isTypedEquals -> InlineClassAbi.mangledNameFor(function, mangleReturnTypes = false, useOldMangleRules = false) // If the original function has signature which need mangling we still need to replace it with a mangled version. (!function.isFakeOverride || function.findInterfaceImplementation(context.state.jvmDefaultMode) != null) && when (specificMangle) { SpecificMangle.Inline -> function.signatureRequiresMangling(includeInline = true, includeMFVC = false) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt index 0e5d339bbd9..bd9494b5656 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt @@ -64,6 +64,11 @@ class MemoizedInlineClassReplacements( // Mangle all functions in the body of an inline class it.parent.safeAs()?.isSingleFieldValueClass == true -> when { + it.isTypedEquals -> createStaticReplacement(it).also { + it.name = InlineClassDescriptorResolver.SPECIALIZED_EQUALS_NAME + specializedEqualsCache.computeIfAbsent(it.parentAsClass) { it } + } + it.isRemoveAtSpecialBuiltinStub() -> null it.isValueClassMemberFakeOverriddenFromJvmDefaultInterfaceMethod() || diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt index 7894a5ef20b..a8814209b99 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/IrUtils.kt @@ -1308,6 +1308,10 @@ fun IrBuiltIns.getKFunctionType(returnType: IrType, parameterTypes: List fun IdSignature?.isComposite(): Boolean = this is IdSignature.CompositeSignature -val IrFunction.overridesEqualsFromAny - get() = name == OperatorNameConventions.EQUALS && valueParameters.size == 1 && valueParameters[0].type.isNullableAny() - && contextReceiverParametersCount == 0 && extensionReceiverParameter == null \ No newline at end of file +val IrFunction.isTypedEquals: Boolean + get() { + val parentClass = parent as? IrClass ?: return false + return name == OperatorNameConventions.EQUALS && returnType.isBoolean() && valueParameters.size == 1 + && (valueParameters[0].type.classFqName?.run { parentClass.hasEqualFqName(this) } ?: false) + && contextReceiverParametersCount == 0 && extensionReceiverParameter == null && parentClass.isValue + } \ No newline at end of file diff --git a/compiler/testData/codegen/box/callableReference/kt49526.kt b/compiler/testData/codegen/box/callableReference/kt49526.kt index b470dd59701..b97bbc33a9e 100644 --- a/compiler/testData/codegen/box/callableReference/kt49526.kt +++ b/compiler/testData/codegen/box/callableReference/kt49526.kt @@ -1,7 +1,6 @@ // WITH_STDLIB // CHECK_BYTECODE_LISTING // FIR_IDENTICAL -// FIR_IDENTICAL inline fun useRef(value: T, f: (T) -> Boolean) = f(value) diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.fir.txt b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.fir.txt new file mode 100644 index 00000000000..0c6d8d4a1c0 --- /dev/null +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.fir.txt @@ -0,0 +1,88 @@ +@kotlin.Metadata +public interface I { + // source: 'inlineClassEqualsOverride.kt' + public abstract method equals-ACFGG4I(p0: int): boolean +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class IC1 { + // source: 'inlineClassEqualsOverride.kt' + private final field value: double + private synthetic method (p0: double): void + public synthetic final static method box-impl(p0: double): IC1 + public static method constructor-impl(p0: double): double + public method equals(p0: java.lang.Object): boolean + public static method equals-impl(p0: double, p1: java.lang.Object): boolean + public final static method equals-impl0(p0: double, p1: double): boolean + public final method getValue(): double + public method hashCode(): int + public static method hashCode-impl(p0: double): int + public method toString(): java.lang.String + public static method toString-impl(p0: double): java.lang.String + public synthetic final method unbox-impl(): double +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class IC2 { + // source: 'inlineClassEqualsOverride.kt' + private final field value: int + private synthetic method (p0: int): void + public synthetic final static method box-impl(p0: int): IC2 + public static method constructor-impl(p0: int): int + public method equals(p0: java.lang.Object): boolean + public final method equals-ACFGG4I(p0: int): boolean + public static method equals-impl(p0: int, p1: java.lang.Object): boolean + public final static method equals-impl0(p0: int, p1: int): boolean + public final method getValue(): int + public method hashCode(): int + public static method hashCode-impl(p0: int): int + public method toString(): java.lang.String + public static method toString-impl(p0: int): java.lang.String + public synthetic final method unbox-impl(): int +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class IC3 { + // source: 'inlineClassEqualsOverride.kt' + private final field value: int + private synthetic method (p0: int): void + public synthetic final static method box-impl(p0: int): IC3 + public static method constructor-impl(p0: int): int + public method equals(p0: java.lang.Object): boolean + public static method equals-impl(p0: int, p1: java.lang.Object): boolean + public final static method equals-impl0(p0: int, p1: int): boolean + public final method getValue(): int + public method hashCode(): int + public static method hashCode-impl(p0: int): int + public method toString(): java.lang.String + public static method toString-impl(p0: int): java.lang.String + public synthetic final method unbox-impl(): int +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class IC4 { + // source: 'inlineClassEqualsOverride.kt' + private final field value: int + private synthetic method (p0: int): void + public synthetic final static method box-impl(p0: int): IC4 + public static method constructor-impl(p0: int): int + public final @org.jetbrains.annotations.NotNull method equals(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Void + public final static @org.jetbrains.annotations.NotNull method equals-impl(p0: int, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Void + public final static method equals-impl0(p0: int, p1: int): boolean + public final method getValue(): int + public method hashCode(): int + public static method hashCode-impl(p0: int): int + public method toString(): java.lang.String + public static method toString-impl(p0: int): java.lang.String + public synthetic final method unbox-impl(): int +} + +@kotlin.Metadata +public final class InlineClassEqualsOverrideKt { + // source: 'inlineClassEqualsOverride.kt' + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String +} diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt index 3185efbd92f..0104dbc8c93 100644 --- a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.kt @@ -2,6 +2,7 @@ // WORKS_WHEN_VALUE_CLASS // LANGUAGE: +ValueClasses, +CustomEqualsInInlineClasses // TARGET_BACKEND: JVM_IR +// CHECK_BYTECODE_LISTING import kotlin.math.abs diff --git a/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.txt b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.txt new file mode 100644 index 00000000000..d500c81a878 --- /dev/null +++ b/compiler/testData/codegen/box/inlineClasses/inlineClassEqualsOverride.txt @@ -0,0 +1,88 @@ +@kotlin.Metadata +public interface I { + // source: 'inlineClassEqualsOverride.kt' + public abstract method equals-ACFGG4I(p0: int): boolean +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class IC1 { + // source: 'inlineClassEqualsOverride.kt' + private final field value: double + private synthetic method (p0: double): void + public synthetic final static method box-impl(p0: double): IC1 + public static method constructor-impl(p0: double): double + public method equals(p0: java.lang.Object): boolean + public static method equals-impl(p0: double, p1: java.lang.Object): boolean + public final static method equals-impl0(p0: double, p1: double): boolean + public final method getValue(): double + public method hashCode(): int + public static method hashCode-impl(p0: double): int + public method toString(): java.lang.String + public static method toString-impl(p0: double): java.lang.String + public synthetic final method unbox-impl(): double +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class IC2 { + // source: 'inlineClassEqualsOverride.kt' + private final field value: int + private synthetic method (p0: int): void + public synthetic final static method box-impl(p0: int): IC2 + public static method constructor-impl(p0: int): int + public method equals(p0: java.lang.Object): boolean + public method equals-ACFGG4I(p0: int): boolean + public static method equals-impl(p0: int, p1: java.lang.Object): boolean + public static method equals-impl0(p0: int, p1: int): boolean + public final method getValue(): int + public method hashCode(): int + public static method hashCode-impl(p0: int): int + public method toString(): java.lang.String + public static method toString-impl(p0: int): java.lang.String + public synthetic final method unbox-impl(): int +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class IC3 { + // source: 'inlineClassEqualsOverride.kt' + private final field value: int + private synthetic method (p0: int): void + public synthetic final static method box-impl(p0: int): IC3 + public static method constructor-impl(p0: int): int + public method equals(p0: java.lang.Object): boolean + public static method equals-impl(p0: int, p1: java.lang.Object): boolean + public final static method equals-impl0(p0: int, p1: int): boolean + public final method getValue(): int + public method hashCode(): int + public static method hashCode-impl(p0: int): int + public method toString(): java.lang.String + public static method toString-impl(p0: int): java.lang.String + public synthetic final method unbox-impl(): int +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class IC4 { + // source: 'inlineClassEqualsOverride.kt' + private final field value: int + private synthetic method (p0: int): void + public synthetic final static method box-impl(p0: int): IC4 + public static method constructor-impl(p0: int): int + public @org.jetbrains.annotations.NotNull method equals(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Void + public static @org.jetbrains.annotations.NotNull method equals-impl(p0: int, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Void + public final static method equals-impl0(p0: int, p1: int): boolean + public final method getValue(): int + public method hashCode(): int + public static method hashCode-impl(p0: int): int + public method toString(): java.lang.String + public static method toString-impl(p0: int): java.lang.String + public synthetic final method unbox-impl(): int +} + +@kotlin.Metadata +public final class InlineClassEqualsOverrideKt { + // source: 'inlineClassEqualsOverride.kt' + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String +}