diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 2cfe38aeea5..858b6bec5c0 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -3513,10 +3513,13 @@ public class ExpressionCodegen extends KtVisitor impleme Label endLabel = new Label(); boolean flipComparison = opToken == KtTokens.EXCLEQ || opToken == KtTokens.EXCLEQEQEQ; + // Don't call equals-impl0 if the class file version is too low to support it. + boolean useUnboxedEquals = leftIsUnboxed && JvmCodegenUtil.typeHasSpecializedInlineClassEquality(leftKotlinType, state); + leftValue.put(leftType, leftKotlinType, v); //noinspection SuspiciousNameCombination Type afterTopType = leftType; - if (!leftIsUnboxed) { + if (!useUnboxedEquals) { StackValue.coerce(leftType, leftKotlinType, OBJECT_TYPE, nullableAnyType, v); afterTopType = OBJECT_TYPE; } @@ -3524,12 +3527,12 @@ public class ExpressionCodegen extends KtVisitor impleme rightValue.put(rightType, rightKotlinType, v); //noinspection SuspiciousNameCombination Type topType = rightType; - if (!leftIsUnboxed || !rightIsUnboxed) { + if (!useUnboxedEquals || !rightIsUnboxed) { StackValue.coerce(rightType, rightKotlinType, OBJECT_TYPE, nullableAnyType, v); topType = OBJECT_TYPE; } - if (leftIsUnboxed) { + if (useUnboxedEquals) { String className = typeMapper.mapTypeAsDeclaration(leftKotlinType).getInternalName(); // Nullable inline class wrappers around non-nullable types are unboxed, yet // equals-impl expects a non-nullable first argument and equals-impl0 expects @@ -3568,12 +3571,12 @@ public class ExpressionCodegen extends KtVisitor impleme v.goTo(endLabel); v.visitLabel(nonNullLabel); } - String descriptor = Type.getMethodType(Type.BOOLEAN_TYPE, leftType, leftType).toString(); + String descriptor = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, leftType, leftType); v.invokestatic(className, InlineClassDescriptorResolver.SPECIALIZED_EQUALS_NAME.asString(), descriptor, false); } else { // equals-impl expects a non-nullable first argument, yet `left` may be unboxed even if // it is nullable when it is a wrapper around a non-nullable reference type. - String descriptor = Type.getMethodType(Type.BOOLEAN_TYPE, leftType, OBJECT_TYPE).toString(); + String descriptor = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, leftType, OBJECT_TYPE); v.invokestatic(className, "equals-impl", descriptor, false); } } else { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java index 580345cbe93..9bb118a3aa4 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.codegen.context.MethodContext; import org.jetbrains.kotlin.codegen.context.RootContext; import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; +import org.jetbrains.kotlin.config.ApiVersion; import org.jetbrains.kotlin.config.JvmAnalysisFlags; import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.descriptors.*; @@ -42,6 +43,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver; import org.jetbrains.kotlin.resolve.source.PsiSourceElement; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor; +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.util.OperatorNameConventions; @@ -400,4 +402,24 @@ public class JvmCodegenUtil { return name; } + + // Before metadata version 1.1.16 we did not generate equals-impl0 methods correctly. + // The method is still present on all inline classes, but the implementation always throws + // a NullPointerException. + public static boolean typeHasSpecializedInlineClassEquality(@NotNull KotlinType type, @NotNull GenerationState state) { + ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor(); + if (!(descriptor instanceof DeserializedClassDescriptor)) + return true; + + DeserializedClassDescriptor classDescriptor = (DeserializedClassDescriptor) descriptor; + + // The Result class is the only inline class in the standard library without special rules for equality. + // We only call Result.equals-impl0 if we are compiling for Kotlin 1.4 or later. Otherwise, the code + // might well be running against an older version of the standard library. + if (DescriptorUtils.getFqNameSafe(classDescriptor).equals(DescriptorUtils.RESULT_FQ_NAME)) { + return state.getLanguageVersionSettings().getApiVersion().compareTo(ApiVersion.KOTLIN_1_4) >= 0; + } else { + return ((DeserializedClassDescriptor) descriptor).getMetadataVersion().isAtLeast(1, 1, 16); + } + } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt index e541e7e63d1..537198a2211 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt @@ -15,6 +15,7 @@ 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.lower.inlineclasses.* +import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET @@ -33,6 +34,7 @@ import org.jetbrains.kotlin.ir.types.classOrNull import org.jetbrains.kotlin.ir.types.isNullable import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.utils.addToStdlib.safeAs val jvmInlineClassPhase = makeIrFilePhase( @@ -257,7 +259,7 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F } private fun IrBuilderWithScope.specializeEqualsCall(left: IrExpression, right: IrExpression): IrExpression? { - // There's already special handling for null-comparisons in Equals.kt. + // There's already special handling for null-comparisons in the Equals intrinsic. // We cannot specialize calls for which the first argument is already boxed. if (left.isNullConst() || right.isNullConst() || left.type.unboxInlineClass() == left.type) return null @@ -311,8 +313,8 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F val arg = expression.dispatchReceiver!!.transform(this, null) coerceInlineClasses(arg, expression.symbol.owner.dispatchReceiverParameter!!.type, expression.type) } - // Specialize calls to equals with at least one inline class argument to avoid boxing. - expression.isInlineClassEqEq -> { + // Specialize calls to equals when the left argument is a value of inline class type. + expression.isSpecializedInlineClassEqEq -> { expression.transformChildrenVoid() context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol, expression.startOffset, expression.endOffset) .specializeEqualsCall(expression.getValueArgument(0)!!, expression.getValueArgument(1)!!) @@ -322,10 +324,20 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F super.visitCall(expression) } - private val IrCall.isInlineClassEqEq: Boolean - get() = symbol == context.irBuiltIns.eqeqSymbol && - (getValueArgument(0)?.type?.classOrNull?.owner?.isInline == true || - getValueArgument(1)?.type?.classOrNull?.owner?.isInline == true) + private val IrCall.isSpecializedInlineClassEqEq: Boolean + get() { + // Note that reference equality (x === y) is not allowed on values of inline class type, + // so it is enough to check for eqeq. + if (symbol != context.irBuiltIns.eqeqSymbol) + return false + + val leftClass = getValueArgument(0)?.type?.classOrNull?.owner?.takeIf { it.isInline } + ?: return false + + // Before version 1.4, we cannot rely on the Result.equals-impl0 method + return (leftClass.fqNameWhenAvailable != DescriptorUtils.RESULT_FQ_NAME) || + context.state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4 + } override fun visitGetField(expression: IrGetField): IrExpression { val field = expression.symbol.owner diff --git a/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmMetadataVersion.kt b/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmMetadataVersion.kt index 89072951ad4..4bcab8bfe81 100644 --- a/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmMetadataVersion.kt +++ b/core/metadata.jvm/src/org/jetbrains/kotlin/metadata/jvm/deserialization/JvmMetadataVersion.kt @@ -27,7 +27,7 @@ class JvmMetadataVersion(versionArray: IntArray, val isStrictSemantics: Boolean) companion object { @JvmField - val INSTANCE = JvmMetadataVersion(1, 1, 15) + val INSTANCE = JvmMetadataVersion(1, 1, 16) @JvmField val INVALID_VERSION = JvmMetadataVersion()