JVM: Avoid boxing in inline class equality
This commit is contained in:
committed by
Alexander Udalov
parent
f53b28e8a3
commit
cdc5e1347b
@@ -1164,6 +1164,15 @@ public class AsmUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static void pop2(@NotNull MethodVisitor v, @NotNull Type topOfStack, @NotNull Type afterTop) {
|
||||
if (topOfStack.getSize() == 1 && afterTop.getSize() == 1) {
|
||||
v.visitInsn(POP2);
|
||||
} else {
|
||||
pop(v, topOfStack);
|
||||
pop(v, afterTop);
|
||||
}
|
||||
}
|
||||
|
||||
public static void pop2(@NotNull MethodVisitor v, @NotNull Type type) {
|
||||
if (type.getSize() == 2) {
|
||||
v.visitInsn(Opcodes.POP2);
|
||||
@@ -1206,6 +1215,29 @@ public class AsmUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicate the element afterTop and push it on the top of the stack.
|
||||
public static void dupSecond(@NotNull InstructionAdapter v, @NotNull Type topOfStack, @NotNull Type afterTop) {
|
||||
if (afterTop.getSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (topOfStack.getSize() == 0) {
|
||||
dup(v, afterTop);
|
||||
} else if (topOfStack.getSize() == 1 && afterTop.getSize() == 1) {
|
||||
v.dup2();
|
||||
v.pop();
|
||||
} else {
|
||||
swap(v, topOfStack, afterTop);
|
||||
if (topOfStack.getSize() == 1 && afterTop.getSize() == 2) {
|
||||
v.dup2X1();
|
||||
} else if (topOfStack.getSize() == 2 && afterTop.getSize() == 1) {
|
||||
v.dupX2();
|
||||
} else /* top = 2, after top = 2 */ {
|
||||
v.dup2X2();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void dup(@NotNull InstructionAdapter v, @NotNull Type topOfStack, @NotNull Type afterTop) {
|
||||
if (topOfStack.getSize() == 0 && afterTop.getSize() == 0) {
|
||||
return;
|
||||
|
||||
@@ -3390,15 +3390,10 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
Type leftType = pregeneratedSubject != null ? pregeneratedSubject.type : expressionType(left);
|
||||
Type rightType = expressionType(right);
|
||||
|
||||
boolean leftIsInlineClassWithPrimitiveEquality = leftIsInlineClass && isInlineClassTypeWithPrimitiveEquality(leftKotlinType);
|
||||
boolean rightIsInlineClassWithPrimitiveEquality = rightIsInlineClass && isInlineClassTypeWithPrimitiveEquality(rightKotlinType);
|
||||
|
||||
boolean leftHasPrimitiveEquality = isPrimitive(leftType) && (!leftIsInlineClass || leftIsInlineClassWithPrimitiveEquality);
|
||||
boolean rightHasPrimitiveEquality = isPrimitive(rightType) && (!rightIsInlineClass || rightIsInlineClassWithPrimitiveEquality);
|
||||
|
||||
if (leftIsInlineClass && !leftIsInlineClassWithPrimitiveEquality || rightIsInlineClass && !rightIsInlineClassWithPrimitiveEquality) {
|
||||
return generateEqualityWithInlineClassesBoxing(left, leftType, right, rightType, opToken, pregeneratedSubject);
|
||||
}
|
||||
boolean leftHasPrimitiveEquality =
|
||||
isPrimitive(leftType) && (!leftIsInlineClass || isInlineClassTypeWithPrimitiveEquality(leftKotlinType));
|
||||
boolean rightHasPrimitiveEquality =
|
||||
isPrimitive(rightType) && (!rightIsInlineClass || isInlineClassTypeWithPrimitiveEquality(rightKotlinType));
|
||||
|
||||
if (KtPsiUtil.isNullConstant(left)) {
|
||||
return genCmpWithNull(right, opToken, null);
|
||||
@@ -3408,6 +3403,17 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
return genCmpWithNull(left, opToken, pregeneratedSubject);
|
||||
}
|
||||
|
||||
if (leftIsInlineClass || rightIsInlineClass) {
|
||||
boolean leftIsUnboxed = leftIsInlineClass && StackValue.isUnboxedInlineClass(leftKotlinType, leftType);
|
||||
boolean rightIsUnboxed = rightIsInlineClass && StackValue.isUnboxedInlineClass(rightKotlinType, rightType);
|
||||
if (!leftIsUnboxed || !rightIsUnboxed || !leftHasPrimitiveEquality || !rightHasPrimitiveEquality) {
|
||||
return genEqualsForInlineClasses(
|
||||
left, right, opToken, pregeneratedSubject, leftKotlinType, rightKotlinType,
|
||||
leftType, rightType, leftIsUnboxed, rightIsUnboxed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isIntZero(left, leftType) && rightHasPrimitiveEquality && isIntPrimitive(rightType)) {
|
||||
return genCmpWithZero(right, opToken, null);
|
||||
}
|
||||
@@ -3428,35 +3434,33 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
return genCmpPrimitiveToSafeCall(left, leftType, (KtSafeQualifiedExpression) right, opToken, pregeneratedSubject);
|
||||
}
|
||||
|
||||
if (!leftIsInlineClass && !rightIsInlineClass) {
|
||||
if (BoxedToPrimitiveEquality.isApplicable(opToken, leftType, rightType)) {
|
||||
return BoxedToPrimitiveEquality.create(
|
||||
opToken,
|
||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||
genLazy(right, rightType), rightType,
|
||||
myFrameMap
|
||||
);
|
||||
}
|
||||
if (BoxedToPrimitiveEquality.isApplicable(opToken, leftType, rightType)) {
|
||||
return BoxedToPrimitiveEquality.create(
|
||||
opToken,
|
||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||
genLazy(right, rightType), rightType,
|
||||
myFrameMap
|
||||
);
|
||||
}
|
||||
|
||||
if (PrimitiveToBoxedEquality.isApplicable(opToken, leftType, rightType)) {
|
||||
return PrimitiveToBoxedEquality.create(
|
||||
opToken,
|
||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||
genLazy(right, rightType), rightType
|
||||
);
|
||||
}
|
||||
if (PrimitiveToBoxedEquality.isApplicable(opToken, leftType, rightType)) {
|
||||
return PrimitiveToBoxedEquality.create(
|
||||
opToken,
|
||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||
genLazy(right, rightType), rightType
|
||||
);
|
||||
}
|
||||
|
||||
if (PrimitiveToObjectEquality.isApplicable(opToken, leftType, rightType)) {
|
||||
return PrimitiveToObjectEquality.create(
|
||||
opToken,
|
||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||
genLazy(right, rightType), rightType
|
||||
);
|
||||
}
|
||||
if (PrimitiveToObjectEquality.isApplicable(opToken, leftType, rightType)) {
|
||||
return PrimitiveToObjectEquality.create(
|
||||
opToken,
|
||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||
genLazy(right, rightType), rightType
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (!leftIsInlineClass && !rightIsInlineClass && isPrimitive(leftType) != isPrimitive(rightType)) {
|
||||
if (isPrimitive(leftType) != isPrimitive(rightType)) {
|
||||
leftType = boxType(leftType);
|
||||
rightType = boxType(rightType);
|
||||
}
|
||||
@@ -3488,34 +3492,100 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
);
|
||||
}
|
||||
|
||||
private StackValue generateEqualityWithInlineClassesBoxing(
|
||||
@NotNull
|
||||
private StackValue genEqualsForInlineClasses(
|
||||
@NotNull KtExpression left,
|
||||
@NotNull Type leftType,
|
||||
@NotNull KtExpression right,
|
||||
@NotNull Type rightType,
|
||||
@NotNull IElementType opToken,
|
||||
@Nullable StackValue pregeneratedSubject
|
||||
@Nullable StackValue pregeneratedSubject,
|
||||
@NotNull KotlinType leftKotlinType,
|
||||
@NotNull KotlinType rightKotlinType,
|
||||
@NotNull Type leftType,
|
||||
@NotNull Type rightType,
|
||||
boolean leftIsUnboxed,
|
||||
boolean rightIsUnboxed
|
||||
) {
|
||||
KotlinType leftKotlinType = kotlinType(left);
|
||||
KotlinType rightKotlinType = kotlinType(right);
|
||||
|
||||
StackValue leftValue = genLazyUnlessProvided(pregeneratedSubject, left, leftType, leftKotlinType);
|
||||
StackValue rightValue = genLazy(right, rightType, rightKotlinType);
|
||||
|
||||
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
|
||||
KotlinType nullableAnyType = state.getModule().getBuiltIns().getNullableAnyType();
|
||||
Label endLabel = new Label();
|
||||
boolean flipComparison = opToken == KtTokens.EXCLEQ || opToken == KtTokens.EXCLEQEQEQ;
|
||||
|
||||
leftValue.put(leftType, leftKotlinType, v);
|
||||
StackValue.coerce(leftType, leftKotlinType, AsmTypes.OBJECT_TYPE, nullableAnyType, v);
|
||||
//noinspection SuspiciousNameCombination
|
||||
Type afterTopType = leftType;
|
||||
if (!leftIsUnboxed) {
|
||||
StackValue.coerce(leftType, leftKotlinType, OBJECT_TYPE, nullableAnyType, v);
|
||||
afterTopType = OBJECT_TYPE;
|
||||
}
|
||||
|
||||
rightValue.put(rightType, rightKotlinType, v);
|
||||
StackValue.coerce(rightType, rightKotlinType, AsmTypes.OBJECT_TYPE, nullableAnyType, v);
|
||||
//noinspection SuspiciousNameCombination
|
||||
Type topType = rightType;
|
||||
if (!leftIsUnboxed || !rightIsUnboxed) {
|
||||
StackValue.coerce(rightType, rightKotlinType, OBJECT_TYPE, nullableAnyType, v);
|
||||
topType = OBJECT_TYPE;
|
||||
}
|
||||
|
||||
genAreEqualCall(v);
|
||||
if (leftIsUnboxed) {
|
||||
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
|
||||
// both arguments to be non-nullable.
|
||||
if (TypeUtils.isNullableType(leftKotlinType)) {
|
||||
AsmUtil.dupSecond(v, topType, afterTopType);
|
||||
Label nonNullLabel = new Label();
|
||||
v.ifnonnull(nonNullLabel);
|
||||
|
||||
if (opToken == KtTokens.EXCLEQ || opToken == KtTokens.EXCLEQEQEQ) {
|
||||
if (TypeUtils.isNullableType(rightKotlinType)) {
|
||||
Label branchJumpLabel = new Label();
|
||||
v.ifnonnull(branchJumpLabel);
|
||||
AsmUtil.pop(v, afterTopType);
|
||||
v.iconst(flipComparison ? 0 : 1);
|
||||
v.goTo(endLabel);
|
||||
v.visitLabel(branchJumpLabel);
|
||||
AsmUtil.pop(v, afterTopType);
|
||||
} else {
|
||||
AsmUtil.pop2(v, topType, afterTopType);
|
||||
}
|
||||
v.iconst(flipComparison ? 1 : 0);
|
||||
v.goTo(endLabel);
|
||||
|
||||
v.visitLabel(nonNullLabel);
|
||||
}
|
||||
|
||||
if (rightIsUnboxed) {
|
||||
// Call equals-impl0 if both arguments are unboxed
|
||||
if (TypeUtils.isNullableType(rightKotlinType)) {
|
||||
// left is already non-null, if right is null the comparison is false
|
||||
AsmUtil.dup(v, topType);
|
||||
Label nonNullLabel = new Label();
|
||||
v.ifnonnull(nonNullLabel);
|
||||
AsmUtil.pop2(v, topType, afterTopType);
|
||||
v.iconst(flipComparison ? 1 : 0);
|
||||
v.goTo(endLabel);
|
||||
v.visitLabel(nonNullLabel);
|
||||
}
|
||||
String descriptor = Type.getMethodType(Type.BOOLEAN_TYPE, leftType, leftType).toString();
|
||||
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();
|
||||
v.invokestatic(className, "equals-impl", descriptor, false);
|
||||
}
|
||||
} else {
|
||||
// Call Intrinsics.areEqual when both arguments are boxed
|
||||
genAreEqualCall(v);
|
||||
}
|
||||
|
||||
if (flipComparison) {
|
||||
genInvertBoolean(v);
|
||||
}
|
||||
|
||||
v.visitLabel(endLabel);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
@@ -3785,10 +3855,23 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
|
||||
private StackValue genCmpWithNull(KtExpression exp, IElementType opToken, @Nullable StackValue pregeneratedExpr) {
|
||||
return StackValue.compareWithNull(
|
||||
pregeneratedExpr != null ? pregeneratedExpr : gen(exp),
|
||||
(KtTokens.EQEQ == opToken || KtTokens.EQEQEQ == opToken) ? IFNONNULL : IFNULL
|
||||
);
|
||||
KotlinType kotlinType = kotlinType(exp);
|
||||
Type type = expressionType(exp);
|
||||
StackValue argument = pregeneratedExpr != null ? pregeneratedExpr : gen(exp);
|
||||
|
||||
if (kotlinType == null || TypeUtils.isNullableType(kotlinType)) {
|
||||
return StackValue.compareWithNull(argument, (KtTokens.EQEQ == opToken || KtTokens.EQEQEQ == opToken) ? IFNONNULL : IFNULL);
|
||||
} else {
|
||||
// If exp has a non-nullable type, the comparison is vacuous.
|
||||
// For inline classes we cannot necessarily compare with null at all.
|
||||
// In this case the code below is necessary for correctness, not just an optimization.
|
||||
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
|
||||
argument.put(type, kotlinType, v);
|
||||
AsmUtil.pop(v, type);
|
||||
v.iconst((opToken == KtTokens.EXCLEQ || opToken == KtTokens.EXCLEQEQEQ) ? 1 : 0);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private StackValue generateElvis(@NotNull KtBinaryExpression expression) {
|
||||
|
||||
@@ -621,7 +621,7 @@ public abstract class StackValue {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isUnboxedInlineClass(@NotNull KotlinType kotlinType, @NotNull Type actualType) {
|
||||
public static boolean isUnboxedInlineClass(@NotNull KotlinType kotlinType, @NotNull Type actualType) {
|
||||
return KotlinTypeMapper.mapUnderlyingTypeOfInlineClassType(kotlinType).equals(actualType);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ object InlineClassDescriptorResolver {
|
||||
@JvmField
|
||||
val UNBOX_METHOD_NAME = Name.identifier("unbox")
|
||||
|
||||
@JvmField
|
||||
val SPECIALIZED_EQUALS_NAME = Name.identifier("equals-impl0")
|
||||
|
||||
val BOXING_VALUE_PARAMETER_NAME = Name.identifier("v")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// IGNORE_BACKEND: JVM
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// !LANGUAGE: +InlineClasses
|
||||
|
||||
|
||||
+1
-1
@@ -35,4 +35,4 @@ fun test() {
|
||||
// 0 INVOKESTATIC Result.box-impl
|
||||
// 0 INVOKESTATIC Result.unbox-impl
|
||||
// 0 Result\$Failure
|
||||
// 52 Result
|
||||
// 46 Result
|
||||
+5
-5
@@ -12197,6 +12197,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/fieldNameClash.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineClassWithCustomEquals.kt")
|
||||
public void ignoreInlineClassWithCustomEquals() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithCustomEquals.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simpleSecondaryConstructor.kt")
|
||||
public void ignoreSimpleSecondaryConstructor() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/simpleSecondaryConstructor.kt");
|
||||
@@ -12455,11 +12460,6 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassValuesInsideStrings.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineClassWithCustomEquals.kt")
|
||||
public void testInlineClassWithCustomEquals() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithCustomEquals.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineClassWithDefaultFunctionsFromAny.kt")
|
||||
public void testInlineClassWithDefaultFunctionsFromAny() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithDefaultFunctionsFromAny.kt");
|
||||
|
||||
Reference in New Issue
Block a user