JVM: Do not use equals-impl0 methods generated by older compiler versions

This commit is contained in:
Steven Schäfer
2019-09-03 16:32:35 +02:00
committed by Alexander Udalov
parent b85b2d9af8
commit 2e53e36fd5
4 changed files with 50 additions and 13 deletions
@@ -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);
}
}
}
@@ -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
@@ -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()