JVM: Avoid boxing in inline class equality

This commit is contained in:
Steven Schäfer
2019-08-22 13:22:32 +02:00
committed by Alexander Udalov
parent f53b28e8a3
commit cdc5e1347b
7 changed files with 172 additions and 55 deletions
@@ -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
@@ -35,4 +35,4 @@ fun test() {
// 0 INVOKESTATIC Result.box-impl
// 0 INVOKESTATIC Result.unbox-impl
// 0 Result\$Failure
// 52 Result
// 46 Result
@@ -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");