From e1b41eee15a22c2eaae615a0a14574956d4c93fc Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Fri, 19 May 2017 10:34:15 +0300 Subject: [PATCH] Specialize Comparable#compareTo for boxed primitives Support Comparable#compareTo for boxed primitive in redundant boxing/unboxing analysis, along with CHECKCAST to java.lang.Comparable. Note that we can do that for Float and Double, too, because Float#compareTo(Float) and Double#compareTo(Double) are delegated to Float#compare(float, float) and Double#compare(double, double), respectively. Fuse specialized comparison for integers with conditional jumps if possible (both for Comparable#compareTo and Intrinsics#areEqual). #KT-11959 Fixed --- .../optimization/boxing/BoxingInterpreter.kt | 38 ++-- .../boxing/RedundantBoxingInterpreter.kt | 9 + .../RedundantBoxingMethodTransformer.java | 166 ++++++++++++++++-- .../boxingOptimization/boxedIntegersCmp.kt | 46 +++++ .../boxedPrimitivesAreEqual.kt | 21 +++ .../box/boxingOptimization/boxedRealsCmp.kt | 82 +++++++++ .../box/boxingOptimization/intCompareTo.kt | 8 + .../box/boxingOptimization/maxMinBy.kt | 20 +++ .../boxingOptimization/maxMinBy.kt | 34 ++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 30 ++++ .../codegen/BlackBoxCodegenTestGenerated.java | 30 ++++ .../codegen/BytecodeTextTestGenerated.java | 6 + .../LightAnalysisModeTestGenerated.java | 30 ++++ .../semantics/JsCodegenBoxTestGenerated.java | 36 ++++ 14 files changed, 526 insertions(+), 30 deletions(-) create mode 100644 compiler/testData/codegen/box/boxingOptimization/boxedIntegersCmp.kt create mode 100644 compiler/testData/codegen/box/boxingOptimization/boxedPrimitivesAreEqual.kt create mode 100644 compiler/testData/codegen/box/boxingOptimization/boxedRealsCmp.kt create mode 100644 compiler/testData/codegen/box/boxingOptimization/intCompareTo.kt create mode 100644 compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt create mode 100644 compiler/testData/codegen/bytecodeText/boxingOptimization/maxMinBy.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt index 97f390fc1c5..b67f38266a0 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt @@ -75,6 +75,10 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic onAreEqual(insn, values[0] as BoxedBasicValue, values[1] as BoxedBasicValue) value } + insn.isJavaLangComparableCompareToForSameTypedBoxedValues(values) -> { + onCompareTo(insn, values[0] as BoxedBasicValue, values[1] as BoxedBasicValue) + value + } else -> { // N-ary operation should be a method call or multinewarray. // Arguments for multinewarray could be only numeric, @@ -127,6 +131,7 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic protected open fun onNewBoxedValue(value: BoxedBasicValue) {} protected open fun onUnboxing(insn: AbstractInsnNode, value: BoxedBasicValue, resultType: Type) {} protected open fun onAreEqual(insn: AbstractInsnNode, value1: BoxedBasicValue, value2: BoxedBasicValue) {} + protected open fun onCompareTo(insn: AbstractInsnNode, value1: BoxedBasicValue, value2: BoxedBasicValue) {} protected open fun onMethodCallWithBoxedValue(value: BoxedBasicValue) {} protected open fun onMergeFail(value: BoxedBasicValue) {} protected open fun onMergeSuccess(v: BoxedBasicValue, w: BoxedBasicValue) {} @@ -150,7 +155,7 @@ fun AbstractInsnNode.isPrimitiveUnboxing() = isWrapperClassNameOrNumber(owner) && isUnboxingMethodName(name) } -private fun AbstractInsnNode.isJavaLangClassUnboxing() = +fun AbstractInsnNode.isJavaLangClassUnboxing() = isMethodInsnWith(Opcodes.INVOKESTATIC) { owner == "kotlin/jvm/JvmClassMappingKt" && name == "getJavaClass" && @@ -185,7 +190,7 @@ private fun MethodInsnNode.isBoxingMethodDescriptor(): Boolean { return desc == Type.getMethodDescriptor(ownerType, AsmUtil.unboxType(ownerType)) } -private fun AbstractInsnNode.isJavaLangClassBoxing() = +fun AbstractInsnNode.isJavaLangClassBoxing() = isMethodInsnWith(Opcodes.INVOKESTATIC) { owner == AsmTypes.REFLECTION && name == "getOrCreateKotlinClass" && @@ -210,16 +215,15 @@ fun isProgressionClass(type: Type) = RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(type.internalName)) fun AbstractInsnNode.isAreEqualIntrinsicForSameTypedBoxedValues(values: List) = - isAreEqualIntrinsic() && run { - if (values.size != 2) return false + isAreEqualIntrinsic() && areSameTypedBoxedValues(values) - val (v1, v2) = values - if (v1 !is BoxedBasicValue || v2 !is BoxedBasicValue) return false - - val d1 = v1.descriptor - val d2 = v2.descriptor - d1.unboxedType == d2.unboxedType - } +fun areSameTypedBoxedValues(values: List): Boolean { + if (values.size != 2) return false + val (v1, v2) = values + return v1 is BoxedBasicValue && + v2 is BoxedBasicValue && + v1.descriptor.unboxedType == v2.descriptor.unboxedType +} fun AbstractInsnNode.isAreEqualIntrinsic() = isMethodInsnWith(Opcodes.INVOKESTATIC) { @@ -232,4 +236,14 @@ fun canValuesBeUnboxedForAreEqual(values: List): Boolean = !values.any { val unboxedType = getUnboxedType(it.type) unboxedType == Type.DOUBLE_TYPE || unboxedType == Type.FLOAT_TYPE - } \ No newline at end of file + } + +fun AbstractInsnNode.isJavaLangComparableCompareToForSameTypedBoxedValues(values: List) = + isJavaLangComparableCompareTo() && areSameTypedBoxedValues(values) + +fun AbstractInsnNode.isJavaLangComparableCompareTo() = + isMethodInsnWith(Opcodes.INVOKEINTERFACE) { + name == "compareTo" && + owner == "java/lang/Comparable" && + desc == "(Ljava/lang/Object;)I" + } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingInterpreter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingInterpreter.kt index e76af128d0c..cefd6d6ac7c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingInterpreter.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingInterpreter.kt @@ -91,6 +91,13 @@ internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterprete descriptor1.addInsn(insn) } + override fun onCompareTo(insn: AbstractInsnNode, value1: BoxedBasicValue, value2: BoxedBasicValue) { + val descriptor1 = value1.descriptor + val descriptor2 = value2.descriptor + candidatesBoxedValues.merge(descriptor1, descriptor2) + descriptor1.addInsn(insn) + } + override fun onMethodCallWithBoxedValue(value: BoxedBasicValue) { markValueAsDirty(value) } @@ -133,6 +140,8 @@ internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterprete true Type.getInternalName(Number::class.java) -> PRIMITIVE_TYPES_SORTS_WITH_WRAPPER_EXTENDS_NUMBER.contains(value.descriptor.unboxedType.sort) + "java/lang/Comparable" -> + true else -> value.type.internalName == targetInternalName } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingMethodTransformer.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingMethodTransformer.java index d6ee81866be..fc7e0057739 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingMethodTransformer.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingMethodTransformer.java @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.optimization.boxing; import com.intellij.openapi.util.Pair; import kotlin.collections.CollectionsKt; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil; import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue; import org.jetbrains.kotlin.codegen.optimization.common.UtilKt; import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer; @@ -324,16 +325,40 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer { adaptAreEqualIntrinsic(node, insn, value); break; } - else { - // fall-through to default + else if (BoxingInterpreterKt.isJavaLangClassBoxing(insn) || BoxingInterpreterKt.isJavaLangClassUnboxing(insn)) { + node.instructions.remove(insn); + break; } - default: + else { + throwCannotAdaptInstruction(insn); + } + case Opcodes.INVOKEINTERFACE: + if (BoxingInterpreterKt.isJavaLangComparableCompareTo(insn)) { + adaptJavaLangComparableCompareTo(node, insn, value); + break; + } + else { + throwCannotAdaptInstruction(insn); + } + case Opcodes.CHECKCAST: + case Opcodes.INVOKEVIRTUAL: // CHECKCAST or unboxing-method call node.instructions.remove(insn); + break; + default: + throwCannotAdaptInstruction(insn); } } - private static void adaptAreEqualIntrinsic(@NotNull MethodNode node, @NotNull AbstractInsnNode insn, @NotNull BoxedValueDescriptor value) { + private static void throwCannotAdaptInstruction(@NotNull AbstractInsnNode insn) { + throw new AssertionError("Cannot adapt instruction: " + InlineCodegenUtil.getInsnText(insn)); + } + + private static void adaptAreEqualIntrinsic( + @NotNull MethodNode node, + @NotNull AbstractInsnNode insn, + @NotNull BoxedValueDescriptor value + ) { Type unboxedType = value.getUnboxedType(); switch (unboxedType.getSort()) { @@ -355,32 +380,137 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer { } private static void adaptAreEqualIntrinsicForInt(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { - LabelNode lNotEqual = new LabelNode(new Label()); - LabelNode lDone = new LabelNode(new Label()); - node.instructions.insertBefore(insn, new JumpInsnNode(Opcodes.IF_ICMPNE, lNotEqual)); - node.instructions.insertBefore(insn, new InsnNode(Opcodes.ICONST_1)); - node.instructions.insertBefore(insn, new JumpInsnNode(Opcodes.GOTO, lDone)); - node.instructions.insertBefore(insn, lNotEqual); - node.instructions.insertBefore(insn, new InsnNode(Opcodes.ICONST_0)); - node.instructions.insertBefore(insn, lDone); - - node.instructions.remove(insn); + AbstractInsnNode next = insn.getNext(); + if (next != null && (next.getOpcode() == Opcodes.IFEQ || next.getOpcode() == Opcodes.IFNE)) { + fuseAreEqualWithBranch(node, insn, Opcodes.IF_ICMPNE, Opcodes.IF_ICMPEQ); + node.instructions.remove(insn); + node.instructions.remove(next); + } + else { + ifEqual1Else0(node, insn, Opcodes.IF_ICMPNE); + node.instructions.remove(insn); + } } private static void adaptAreEqualIntrinsicForLong(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { node.instructions.insertBefore(insn, new InsnNode(Opcodes.LCMP)); - ifEqual1Else0(node, insn); - node.instructions.remove(insn); + AbstractInsnNode next = insn.getNext(); + if (next != null && (next.getOpcode() == Opcodes.IFEQ || next.getOpcode() == Opcodes.IFNE)) { + fuseAreEqualWithBranch(node, insn, Opcodes.IFNE, Opcodes.IFEQ); + node.instructions.remove(insn); + node.instructions.remove(next); + } + else { + ifEqual1Else0(node, insn, Opcodes.IFNE); + node.instructions.remove(insn); + } } - private static void ifEqual1Else0(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { + private static void fuseAreEqualWithBranch( + @NotNull MethodNode node, + @NotNull AbstractInsnNode insn, + int ifEqualOpcode, + int ifNotEqualOpcode + ) { + AbstractInsnNode next = insn.getNext(); + assert next instanceof JumpInsnNode : "JumpInsnNode expected: " + next; + LabelNode nextLabel = ((JumpInsnNode) next).label; + if (next.getOpcode() == Opcodes.IFEQ) { + node.instructions.insertBefore(insn, new JumpInsnNode(ifEqualOpcode, nextLabel)); + } + else if (next.getOpcode() == Opcodes.IFNE) { + node.instructions.insertBefore(insn, new JumpInsnNode(ifNotEqualOpcode, nextLabel)); + } + else { + throw new AssertionError("IFEQ or IFNE expected: " + InlineCodegenUtil.getInsnOpcodeText(next)); + } + } + + private static void ifEqual1Else0(@NotNull MethodNode node, @NotNull AbstractInsnNode insn, int ifneOpcode) { LabelNode lNotEqual = new LabelNode(new Label()); LabelNode lDone = new LabelNode(new Label()); - node.instructions.insertBefore(insn, new JumpInsnNode(Opcodes.IFNE, lNotEqual)); + node.instructions.insertBefore(insn, new JumpInsnNode(ifneOpcode, lNotEqual)); node.instructions.insertBefore(insn, new InsnNode(Opcodes.ICONST_1)); node.instructions.insertBefore(insn, new JumpInsnNode(Opcodes.GOTO, lDone)); node.instructions.insertBefore(insn, lNotEqual); node.instructions.insertBefore(insn, new InsnNode(Opcodes.ICONST_0)); node.instructions.insertBefore(insn, lDone); } + + private static void adaptJavaLangComparableCompareTo( + @NotNull MethodNode node, + @NotNull AbstractInsnNode insn, + @NotNull BoxedValueDescriptor value + ) { + Type unboxedType = value.getUnboxedType(); + + switch (unboxedType.getSort()) { + case Type.BOOLEAN: + case Type.BYTE: + case Type.SHORT: + case Type.INT: + case Type.CHAR: + adaptJavaLangComparableCompareToForInt(node, insn); + break; + case Type.LONG: + adaptJavaLangComparableCompareToForLong(node, insn); + break; + case Type.FLOAT: + adaptJavaLangComparableCompareToForFloat(node, insn); + break; + case Type.DOUBLE: + adaptJavaLangComparableCompareToForDouble(node, insn); + break; + default: + throw new AssertionError("Unexpected unboxed type kind: " + unboxedType); + } + } + + private static void adaptJavaLangComparableCompareToForInt(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { + AbstractInsnNode next = insn.getNext(); + AbstractInsnNode next2 = next == null ? null : next.getNext(); + if (next != null && next2 != null && + next.getOpcode() == Opcodes.ICONST_0 && + next2.getOpcode() >= Opcodes.IF_ICMPEQ && next2.getOpcode() <= Opcodes.IF_ICMPLE) { + // Fuse: compareTo + ICONST_0 + IF_ICMPxx -> IF_ICMPxx + node.instructions.remove(insn); + node.instructions.remove(next); + } + else if (next != null && + next.getOpcode() >= Opcodes.IFEQ && next.getOpcode() <= Opcodes.IFLE) { + // Fuse: compareTo + IFxx -> IF_ICMPxx + LabelNode nextLabel = ((JumpInsnNode) next).label; + int ifCmpOpcode = next.getOpcode() - Opcodes.IFEQ + Opcodes.IF_ICMPEQ; + node.instructions.insertBefore(insn, new JumpInsnNode(ifCmpOpcode, nextLabel)); + node.instructions.remove(insn); + node.instructions.remove(next); + } + else { + // Can't fuse with branching instruction. + // Trick: convert I, I on stack to L, L and use LCMP. + // This is more compact than explicit branching. + // TODO Generate 'java.lang.Integer#compare(int, int)' in targets >= JVM 1.7 + + // Initial stack: I1 I2 + node.instructions.insertBefore(insn, new InsnNode(Opcodes.SWAP)); // I2 I1 + node.instructions.insertBefore(insn, new InsnNode(Opcodes.I2L)); // L2 I1 + node.instructions.insertBefore(insn, new InsnNode(Opcodes.DUP2_X1)); // L2 I1 L2 + node.instructions.insertBefore(insn, new InsnNode(Opcodes.POP2)); // I1 L2 + node.instructions.insertBefore(insn, new InsnNode(Opcodes.I2L)); // L1 L2 + node.instructions.insertBefore(insn, new InsnNode(Opcodes.LCMP)); // compare(L1, L2) + node.instructions.remove(insn); + } + } + + private static void adaptJavaLangComparableCompareToForLong(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { + node.instructions.set(insn, new InsnNode(Opcodes.LCMP)); + } + + private static void adaptJavaLangComparableCompareToForFloat(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { + node.instructions.set(insn, new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Float", "compare", "(FF)I", false)); + } + + private static void adaptJavaLangComparableCompareToForDouble(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { + node.instructions.set(insn, new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Double", "compare", "(DD)I", false)); + } } diff --git a/compiler/testData/codegen/box/boxingOptimization/boxedIntegersCmp.kt b/compiler/testData/codegen/box/boxingOptimization/boxedIntegersCmp.kt new file mode 100644 index 00000000000..1a181d9b391 --- /dev/null +++ b/compiler/testData/codegen/box/boxingOptimization/boxedIntegersCmp.kt @@ -0,0 +1,46 @@ +inline fun ltx(a: Comparable, b: Any) = a < b +inline fun lex(a: Comparable, b: Any) = a <= b +inline fun gex(a: Comparable, b: Any) = a >= b +inline fun gtx(a: Comparable, b: Any) = a > b + +inline fun lt(a: Any, b: Any) = ltx(a as Comparable, b) +inline fun le(a: Any, b: Any) = lex(a as Comparable, b) +inline fun ge(a: Any, b: Any) = gex(a as Comparable, b) +inline fun gt(a: Any, b: Any) = gtx(a as Comparable, b) + +val ONE = 1 +val ONEL = 1L + +fun box(): String { + return when { + !lt(ONE, 42) -> "Fail 1 LT" + lt(42, ONE) -> "Fail 2 LT" + + !le(ONE, 42) -> "Fail 1 LE" + le(42, ONE) -> "Fail 2 LE" + !le(1, ONE) -> "Fail 3 LE" + + !ge(42, ONE) -> "Fail 1 GE" + ge(ONE, 42) -> "Fail 2 GE" + !ge(1, ONE) -> "Fail 3 GE" + + gt(ONE, 42) -> "Fail 1 GT" + !gt(42, ONE) -> "Fail 2 GT" + + !lt(ONEL, 42L) -> "Fail 1 LT L" + lt(42L, ONEL) -> "Fail 2 LT L" + + !le(ONEL, 42L) -> "Fail 1 LE L" + le(42L, ONEL) -> "Fail 2 LE L" + !le(ONEL, 1L) -> "Fail 3 LE L" + + !ge(42L, ONEL) -> "Fail 1 GE L" + ge(ONEL, 42L) -> "Fail 2 GE L" + !ge(ONEL, 1L) -> "Fail 3 GE L" + + gt(ONEL, 42L) -> "Fail 1 GT L" + !gt(42L, ONEL) -> "Fail 2 GT L" + + else -> "OK" + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/boxingOptimization/boxedPrimitivesAreEqual.kt b/compiler/testData/codegen/box/boxingOptimization/boxedPrimitivesAreEqual.kt new file mode 100644 index 00000000000..2bd8eda1ddd --- /dev/null +++ b/compiler/testData/codegen/box/boxingOptimization/boxedPrimitivesAreEqual.kt @@ -0,0 +1,21 @@ +inline fun eq(a: Any, b: Any) = a == b +inline fun ne(a: Any, b: Any) = a != b + +val ONE = 1 +val ONEL = 1L + +fun box(): String { + return when { + eq(ONE, 2) -> "Fail 1" + !eq(ONE, 1) -> "Fail 2" + !ne(ONE, 2) -> "Fail 3" + ne(ONE, 1) -> "Fail 4" + + eq(ONEL, 42L) -> "Fail 1L" + !eq(ONEL, 1L) -> "Fail 2L" + !ne(ONEL, 42L) -> "Fail 3L" + ne(ONEL, 1L) -> "Fail 4L" + + else -> "OK" + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/boxingOptimization/boxedRealsCmp.kt b/compiler/testData/codegen/box/boxingOptimization/boxedRealsCmp.kt new file mode 100644 index 00000000000..20db67feeb0 --- /dev/null +++ b/compiler/testData/codegen/box/boxingOptimization/boxedRealsCmp.kt @@ -0,0 +1,82 @@ +// IGNORE_BACKEND: JS, NATIVE + +inline fun ltx(a: Comparable, b: Any) = a < b +inline fun lex(a: Comparable, b: Any) = a <= b +inline fun gex(a: Comparable, b: Any) = a >= b +inline fun gtx(a: Comparable, b: Any) = a > b + +inline fun lt(a: Any, b: Any) = ltx(a as Comparable, b) +inline fun le(a: Any, b: Any) = lex(a as Comparable, b) +inline fun ge(a: Any, b: Any) = gex(a as Comparable, b) +inline fun gt(a: Any, b: Any) = gtx(a as Comparable, b) + +val PLUS0F = 0.0F +val MINUS0F = -0.0F +val PLUS0D = 0.0 +val MINUS0D = -0.0 + +fun box(): String { + return when { + !lt(1.0F, 42.0F) -> "Fail 1 LT F" + lt(42.0F, 1.0F) -> "Fail 2 LT F" + + !le(1.0F, 42.0F) -> "Fail 1 LE F" + le(42.0F, 1.0F) -> "Fail 2 LE F" + !le(1.0F, 1.0F) -> "Fail 3 LE F" + + !ge(42.0F, 1.0F) -> "Fail 1 GE F" + ge(1.0F, 42.0F) -> "Fail 2 GE F" + !ge(1.0F, 1.0F) -> "Fail 3 GE F" + + gt(1.0F, 42.0F) -> "Fail 1 GT F" + !gt(42.0F, 1.0F) -> "Fail 2 GT F" + + !lt(1.0, 42.0) -> "Fail 1 LT D" + lt(42.0, 1.0) -> "Fail 2 LT D" + + !le(1.0, 42.0) -> "Fail 1 LE D" + le(42.0, 1.0) -> "Fail 2 LE D" + !le(1.0, 1.0) -> "Fail 3 LE D" + + !ge(42.0, 1.0) -> "Fail 1 GE D" + ge(1.0, 42.0) -> "Fail 2 GE D" + !ge(1.0, 1.0) -> "Fail 3 GE D" + + gt(1.0, 42.0) -> "Fail 1 GT D" + !gt(42.0, 1.0) -> "Fail 2 GT D" + + !lt(MINUS0F, PLUS0F) -> "Fail 1 LT +-0 F" + lt(PLUS0F, MINUS0F) -> "Fail 2 LT +-0 F" + + !le(MINUS0F, PLUS0F) -> "Fail 1 LE +-0 F" + le(PLUS0F, MINUS0F) -> "Fail 2 LE +-0 F" + !le(MINUS0F, MINUS0F) -> "Fail 3 LE +-0 F" + !le(PLUS0F, PLUS0F) -> "Fail 3 LE +-0 F" + + ge(MINUS0F, PLUS0F) -> "Fail 1 GE +-0 F" + !ge(PLUS0F, MINUS0F) -> "Fail 2 GE +-0 F" + !ge(MINUS0F, MINUS0F) -> "Fail 3 GE +-0 F" + !ge(PLUS0F, PLUS0F) -> "Fail 3 GE +-0 F" + + gt(MINUS0F, PLUS0F) -> "Fail 1 GT +-0 F" + !gt(PLUS0F, MINUS0F) -> "Fail 2 GT +-0 F" + + !lt(MINUS0D, PLUS0D) -> "Fail 1 LT +-0 D" + lt(PLUS0D, MINUS0D) -> "Fail 2 LT +-0 D" + + !le(MINUS0D, PLUS0D) -> "Fail 1 LE +-0 D" + le(PLUS0D, MINUS0D) -> "Fail 2 LE +-0 D" + !le(MINUS0D, MINUS0D) -> "Fail 3 LE +-0 D" + !le(PLUS0D, PLUS0D) -> "Fail 3 LE +-0 D" + + ge(MINUS0D, PLUS0D) -> "Fail 1 GE +-0 D" + !ge(PLUS0D, MINUS0D) -> "Fail 2 GE +-0 D" + !ge(MINUS0D, MINUS0D) -> "Fail 3 GE +-0 D" + !ge(PLUS0D, PLUS0D) -> "Fail 3 GE +-0 D" + + gt(MINUS0D, PLUS0D) -> "Fail 1 GT +-0 D" + !gt(PLUS0D, MINUS0D) -> "Fail 2 GT +-0 D" + + else -> "OK" + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/boxingOptimization/intCompareTo.kt b/compiler/testData/codegen/box/boxingOptimization/intCompareTo.kt new file mode 100644 index 00000000000..e3016ff7bbe --- /dev/null +++ b/compiler/testData/codegen/box/boxingOptimization/intCompareTo.kt @@ -0,0 +1,8 @@ +fun box(): String { + val a: Any = 1 + val b: Any = 42 + val test = (a as Comparable).compareTo(b) + if (test != -1) return "Fail: $test" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt b/compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt new file mode 100644 index 00000000000..00adbc52f4c --- /dev/null +++ b/compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt @@ -0,0 +1,20 @@ +// WITH_RUNTIME + +fun box(): String { + val intList = listOf(1, 2, 3) + val longList = listOf(1L, 2L, 3L) + + val intListMin = intList.minBy { it } + if (intListMin != 1) return "Fail intListMin=$intListMin" + + val intListMax = intList.maxBy { it } + if (intListMax != 3) return "Fail intListMax=$intListMax" + + val longListMin = longList.minBy { it } + if (longListMin != 1L) return "Fail longListMin=$longListMin" + + val longListMax = longList.maxBy { it } + if (longListMax != 3L) return "Fail longListMax=$longListMax" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/maxMinBy.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/maxMinBy.kt new file mode 100644 index 00000000000..adc07399f5a --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/maxMinBy.kt @@ -0,0 +1,34 @@ +// WITH_RUNTIME +// FILE: list.kt +val intList = listOf(1, 2, 3) +val longList = listOf(1L, 2L, 3L) + +// FILE: box.kt +fun box(): String { + val intListMin = intList.minBy { it } ?: -1 + if (intListMin != 1) return "Fail intListMin=$intListMin" + + val intListMax = intList.maxBy { it } ?: -1 + if (intListMax != 3) return "Fail intListMax=$intListMax" + + val longListMin = longList.minBy { it } ?: -1 + if (longListMin != 1L) return "Fail longListMin=$longListMin" + + val longListMax = longList.maxBy { it } ?: -1 + if (longListMax != 3L) return "Fail longListMax=$longListMax" + + return "OK" +} + +// @BoxKt.class: +// -- no boxing +// 0 valueOf +// -- no compareTo +// 0 compareTo +// -- comparisons are properly fused with conditional jumps +// 0 ICONST_0 +// 1 IF_ICMPGE +// 1 IF_ICMPLE +// 4 LCMP +// 1 IFGE +// 1 IFLE diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index c956fb28853..d1b1bb49fe5 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -896,6 +896,24 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/boxingOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("boxedIntegersCmp.kt") + public void testBoxedIntegersCmp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedIntegersCmp.kt"); + doTest(fileName); + } + + @TestMetadata("boxedPrimitivesAreEqual.kt") + public void testBoxedPrimitivesAreEqual() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedPrimitivesAreEqual.kt"); + doTest(fileName); + } + + @TestMetadata("boxedRealsCmp.kt") + public void testBoxedRealsCmp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedRealsCmp.kt"); + doTest(fileName); + } + @TestMetadata("casts.kt") public void testCasts() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/casts.kt"); @@ -926,6 +944,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("intCompareTo.kt") + public void testIntCompareTo() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/intCompareTo.kt"); + doTest(fileName); + } + @TestMetadata("kt15871.kt") public void testKt15871() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt15871.kt"); @@ -962,6 +986,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("maxMinBy.kt") + public void testMaxMinBy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt"); + doTest(fileName); + } + @TestMetadata("nullCheck.kt") public void testNullCheck() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/nullCheck.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 7ce1e888df6..d34206de433 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -896,6 +896,24 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/boxingOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("boxedIntegersCmp.kt") + public void testBoxedIntegersCmp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedIntegersCmp.kt"); + doTest(fileName); + } + + @TestMetadata("boxedPrimitivesAreEqual.kt") + public void testBoxedPrimitivesAreEqual() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedPrimitivesAreEqual.kt"); + doTest(fileName); + } + + @TestMetadata("boxedRealsCmp.kt") + public void testBoxedRealsCmp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedRealsCmp.kt"); + doTest(fileName); + } + @TestMetadata("casts.kt") public void testCasts() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/casts.kt"); @@ -926,6 +944,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("intCompareTo.kt") + public void testIntCompareTo() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/intCompareTo.kt"); + doTest(fileName); + } + @TestMetadata("kt15871.kt") public void testKt15871() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt15871.kt"); @@ -962,6 +986,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("maxMinBy.kt") + public void testMaxMinBy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt"); + doTest(fileName); + } + @TestMetadata("nullCheck.kt") public void testNullCheck() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/nullCheck.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 3f4ccb5e83a..03573ed329b 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -515,6 +515,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { doTest(fileName); } + @TestMetadata("maxMinBy.kt") + public void testMaxMinBy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/maxMinBy.kt"); + doTest(fileName); + } + @TestMetadata("nullCheck.kt") public void testNullCheck() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/nullCheck.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 39c706a3451..0a95024fed4 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -896,6 +896,24 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/boxingOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("boxedIntegersCmp.kt") + public void testBoxedIntegersCmp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedIntegersCmp.kt"); + doTest(fileName); + } + + @TestMetadata("boxedPrimitivesAreEqual.kt") + public void testBoxedPrimitivesAreEqual() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedPrimitivesAreEqual.kt"); + doTest(fileName); + } + + @TestMetadata("boxedRealsCmp.kt") + public void testBoxedRealsCmp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedRealsCmp.kt"); + doTest(fileName); + } + @TestMetadata("casts.kt") public void testCasts() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/casts.kt"); @@ -926,6 +944,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("intCompareTo.kt") + public void testIntCompareTo() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/intCompareTo.kt"); + doTest(fileName); + } + @TestMetadata("kt15871.kt") public void testKt15871() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt15871.kt"); @@ -962,6 +986,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("maxMinBy.kt") + public void testMaxMinBy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt"); + doTest(fileName); + } + @TestMetadata("nullCheck.kt") public void testNullCheck() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/nullCheck.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 5b0cbc9f66a..b535f4be17a 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -1076,6 +1076,30 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/boxingOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); } + @TestMetadata("boxedIntegersCmp.kt") + public void testBoxedIntegersCmp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedIntegersCmp.kt"); + doTest(fileName); + } + + @TestMetadata("boxedPrimitivesAreEqual.kt") + public void testBoxedPrimitivesAreEqual() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedPrimitivesAreEqual.kt"); + doTest(fileName); + } + + @TestMetadata("boxedRealsCmp.kt") + public void testBoxedRealsCmp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/boxedRealsCmp.kt"); + try { + doTest(fileName); + } + catch (Throwable ignore) { + return; + } + throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + } + @TestMetadata("casts.kt") public void testCasts() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/casts.kt"); @@ -1118,6 +1142,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("intCompareTo.kt") + public void testIntCompareTo() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/intCompareTo.kt"); + doTest(fileName); + } + @TestMetadata("kt15871.kt") public void testKt15871() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt15871.kt"); @@ -1160,6 +1190,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("maxMinBy.kt") + public void testMaxMinBy() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/maxMinBy.kt"); + doTest(fileName); + } + @TestMetadata("nullCheck.kt") public void testNullCheck() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/nullCheck.kt");