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) {
|
public static void pop2(@NotNull MethodVisitor v, @NotNull Type type) {
|
||||||
if (type.getSize() == 2) {
|
if (type.getSize() == 2) {
|
||||||
v.visitInsn(Opcodes.POP2);
|
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) {
|
public static void dup(@NotNull InstructionAdapter v, @NotNull Type topOfStack, @NotNull Type afterTop) {
|
||||||
if (topOfStack.getSize() == 0 && afterTop.getSize() == 0) {
|
if (topOfStack.getSize() == 0 && afterTop.getSize() == 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3390,15 +3390,10 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
Type leftType = pregeneratedSubject != null ? pregeneratedSubject.type : expressionType(left);
|
Type leftType = pregeneratedSubject != null ? pregeneratedSubject.type : expressionType(left);
|
||||||
Type rightType = expressionType(right);
|
Type rightType = expressionType(right);
|
||||||
|
|
||||||
boolean leftIsInlineClassWithPrimitiveEquality = leftIsInlineClass && isInlineClassTypeWithPrimitiveEquality(leftKotlinType);
|
boolean leftHasPrimitiveEquality =
|
||||||
boolean rightIsInlineClassWithPrimitiveEquality = rightIsInlineClass && isInlineClassTypeWithPrimitiveEquality(rightKotlinType);
|
isPrimitive(leftType) && (!leftIsInlineClass || isInlineClassTypeWithPrimitiveEquality(leftKotlinType));
|
||||||
|
boolean rightHasPrimitiveEquality =
|
||||||
boolean leftHasPrimitiveEquality = isPrimitive(leftType) && (!leftIsInlineClass || leftIsInlineClassWithPrimitiveEquality);
|
isPrimitive(rightType) && (!rightIsInlineClass || isInlineClassTypeWithPrimitiveEquality(rightKotlinType));
|
||||||
boolean rightHasPrimitiveEquality = isPrimitive(rightType) && (!rightIsInlineClass || rightIsInlineClassWithPrimitiveEquality);
|
|
||||||
|
|
||||||
if (leftIsInlineClass && !leftIsInlineClassWithPrimitiveEquality || rightIsInlineClass && !rightIsInlineClassWithPrimitiveEquality) {
|
|
||||||
return generateEqualityWithInlineClassesBoxing(left, leftType, right, rightType, opToken, pregeneratedSubject);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (KtPsiUtil.isNullConstant(left)) {
|
if (KtPsiUtil.isNullConstant(left)) {
|
||||||
return genCmpWithNull(right, opToken, null);
|
return genCmpWithNull(right, opToken, null);
|
||||||
@@ -3408,6 +3403,17 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
return genCmpWithNull(left, opToken, pregeneratedSubject);
|
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)) {
|
if (isIntZero(left, leftType) && rightHasPrimitiveEquality && isIntPrimitive(rightType)) {
|
||||||
return genCmpWithZero(right, opToken, null);
|
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);
|
return genCmpPrimitiveToSafeCall(left, leftType, (KtSafeQualifiedExpression) right, opToken, pregeneratedSubject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!leftIsInlineClass && !rightIsInlineClass) {
|
if (BoxedToPrimitiveEquality.isApplicable(opToken, leftType, rightType)) {
|
||||||
if (BoxedToPrimitiveEquality.isApplicable(opToken, leftType, rightType)) {
|
return BoxedToPrimitiveEquality.create(
|
||||||
return BoxedToPrimitiveEquality.create(
|
opToken,
|
||||||
opToken,
|
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
genLazy(right, rightType), rightType,
|
||||||
genLazy(right, rightType), rightType,
|
myFrameMap
|
||||||
myFrameMap
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (PrimitiveToBoxedEquality.isApplicable(opToken, leftType, rightType)) {
|
if (PrimitiveToBoxedEquality.isApplicable(opToken, leftType, rightType)) {
|
||||||
return PrimitiveToBoxedEquality.create(
|
return PrimitiveToBoxedEquality.create(
|
||||||
opToken,
|
opToken,
|
||||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||||
genLazy(right, rightType), rightType
|
genLazy(right, rightType), rightType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PrimitiveToObjectEquality.isApplicable(opToken, leftType, rightType)) {
|
if (PrimitiveToObjectEquality.isApplicable(opToken, leftType, rightType)) {
|
||||||
return PrimitiveToObjectEquality.create(
|
return PrimitiveToObjectEquality.create(
|
||||||
opToken,
|
opToken,
|
||||||
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
genLazyUnlessProvided(pregeneratedSubject, left, leftType), leftType,
|
||||||
genLazy(right, rightType), rightType
|
genLazy(right, rightType), rightType
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!leftIsInlineClass && !rightIsInlineClass && isPrimitive(leftType) != isPrimitive(rightType)) {
|
if (isPrimitive(leftType) != isPrimitive(rightType)) {
|
||||||
leftType = boxType(leftType);
|
leftType = boxType(leftType);
|
||||||
rightType = boxType(rightType);
|
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 KtExpression left,
|
||||||
@NotNull Type leftType,
|
|
||||||
@NotNull KtExpression right,
|
@NotNull KtExpression right,
|
||||||
@NotNull Type rightType,
|
|
||||||
@NotNull IElementType opToken,
|
@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 leftValue = genLazyUnlessProvided(pregeneratedSubject, left, leftType, leftKotlinType);
|
||||||
StackValue rightValue = genLazy(right, rightType, rightKotlinType);
|
StackValue rightValue = genLazy(right, rightType, rightKotlinType);
|
||||||
|
|
||||||
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
|
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
|
||||||
KotlinType nullableAnyType = state.getModule().getBuiltIns().getNullableAnyType();
|
KotlinType nullableAnyType = state.getModule().getBuiltIns().getNullableAnyType();
|
||||||
|
Label endLabel = new Label();
|
||||||
|
boolean flipComparison = opToken == KtTokens.EXCLEQ || opToken == KtTokens.EXCLEQEQEQ;
|
||||||
|
|
||||||
leftValue.put(leftType, leftKotlinType, v);
|
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);
|
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);
|
genInvertBoolean(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.visitLabel(endLabel);
|
||||||
return Unit.INSTANCE;
|
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) {
|
private StackValue genCmpWithNull(KtExpression exp, IElementType opToken, @Nullable StackValue pregeneratedExpr) {
|
||||||
return StackValue.compareWithNull(
|
KotlinType kotlinType = kotlinType(exp);
|
||||||
pregeneratedExpr != null ? pregeneratedExpr : gen(exp),
|
Type type = expressionType(exp);
|
||||||
(KtTokens.EQEQ == opToken || KtTokens.EQEQEQ == opToken) ? IFNONNULL : IFNULL
|
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) {
|
private StackValue generateElvis(@NotNull KtBinaryExpression expression) {
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ public abstract class StackValue {
|
|||||||
return false;
|
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);
|
return KotlinTypeMapper.mapUnderlyingTypeOfInlineClassType(kotlinType).equals(actualType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ object InlineClassDescriptorResolver {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val UNBOX_METHOD_NAME = Name.identifier("unbox")
|
val UNBOX_METHOD_NAME = Name.identifier("unbox")
|
||||||
|
|
||||||
|
@JvmField
|
||||||
val SPECIALIZED_EQUALS_NAME = Name.identifier("equals-impl0")
|
val SPECIALIZED_EQUALS_NAME = Name.identifier("equals-impl0")
|
||||||
|
|
||||||
val BOXING_VALUE_PARAMETER_NAME = Name.identifier("v")
|
val BOXING_VALUE_PARAMETER_NAME = Name.identifier("v")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// IGNORE_BACKEND: JVM
|
||||||
// IGNORE_BACKEND: JVM_IR
|
// IGNORE_BACKEND: JVM_IR
|
||||||
// !LANGUAGE: +InlineClasses
|
// !LANGUAGE: +InlineClasses
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -35,4 +35,4 @@ fun test() {
|
|||||||
// 0 INVOKESTATIC Result.box-impl
|
// 0 INVOKESTATIC Result.box-impl
|
||||||
// 0 INVOKESTATIC Result.unbox-impl
|
// 0 INVOKESTATIC Result.unbox-impl
|
||||||
// 0 Result\$Failure
|
// 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");
|
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")
|
@TestMetadata("simpleSecondaryConstructor.kt")
|
||||||
public void ignoreSimpleSecondaryConstructor() throws Exception {
|
public void ignoreSimpleSecondaryConstructor() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/inlineClasses/simpleSecondaryConstructor.kt");
|
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");
|
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")
|
@TestMetadata("inlineClassWithDefaultFunctionsFromAny.kt")
|
||||||
public void testInlineClassWithDefaultFunctionsFromAny() throws Exception {
|
public void testInlineClassWithDefaultFunctionsFromAny() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithDefaultFunctionsFromAny.kt");
|
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithDefaultFunctionsFromAny.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user