JVM: Do not use equals-impl0 methods generated by older compiler versions
This commit is contained in:
committed by
Alexander Udalov
parent
b85b2d9af8
commit
2e53e36fd5
@@ -3513,10 +3513,13 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> 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<StackValue, StackValue> 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<StackValue, StackValue> 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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-7
@@ -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
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user