diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxedBasicValue.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxedBasicValue.kt index 1c5c3ffb29d..cc5146dd65d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxedBasicValue.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxedBasicValue.kt @@ -66,7 +66,7 @@ class BoxedValueDescriptor( var isSafeToRemove = true; private set val unboxedType: Type = getUnboxedType(boxedType) - fun getAssociatedInsns() = associatedInsns.toReadOnlyList() + fun getAssociatedInsns() = associatedInsns.toList() fun addInsn(insnNode: AbstractInsnNode) { associatedInsns.add(insnNode) 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 650d7a1bee0..97f390fc1c5 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 @@ -71,6 +71,10 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic ?: throw AssertionError("firstArg should be progression iterator") createNewBoxing(insn, AsmUtil.boxType(progressionIterator.valuesPrimitiveType), progressionIterator) } + insn.isAreEqualIntrinsicForSameTypedBoxedValues(values) && canValuesBeUnboxedForAreEqual(values) -> { + onAreEqual(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, @@ -122,6 +126,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 onMethodCallWithBoxedValue(value: BoxedBasicValue) {} protected open fun onMergeFail(value: BoxedBasicValue) {} protected open fun onMergeSuccess(v: BoxedBasicValue, w: BoxedBasicValue) {} @@ -203,3 +208,28 @@ fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List) fun isProgressionClass(type: Type) = RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(type.internalName)) + +fun AbstractInsnNode.isAreEqualIntrinsicForSameTypedBoxedValues(values: List) = + isAreEqualIntrinsic() && run { + if (values.size != 2) return false + + 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 AbstractInsnNode.isAreEqualIntrinsic() = + isMethodInsnWith(Opcodes.INVOKESTATIC) { + name == "areEqual" && + owner == "kotlin/jvm/internal/Intrinsics" && + desc == "(Ljava/lang/Object;Ljava/lang/Object;)Z" + } + +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 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 1fe2de1a1ab..e76af128d0c 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 @@ -84,6 +84,13 @@ internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterprete } } + override fun onAreEqual(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) } 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 afff36f18a0..21d15c6bcf7 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 @@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue; import org.jetbrains.kotlin.codegen.optimization.common.UtilKt; import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer; +import org.jetbrains.org.objectweb.asm.Label; import org.jetbrains.org.objectweb.asm.Opcodes; import org.jetbrains.org.objectweb.asm.Type; import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; @@ -327,9 +328,68 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer { ); node.instructions.set(insn, new InsnNode(Opcodes.ICONST_1)); break; + case Opcodes.INVOKESTATIC: + if (BoxingInterpreterKt.isAreEqualIntrinsic(insn)) { + adaptAreEqualIntrinsic(node, insn, value); + break; + } + else { + // fall-through to default + } default: // CHECKCAST or unboxing-method call node.instructions.remove(insn); } } + + private static void adaptAreEqualIntrinsic(@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: + adaptAreEqualIntrinsicForInt(node, insn); + break; + case Type.LONG: + adaptAreEqualIntrinsicForLong(node, insn); + break; + case Type.OBJECT: + break; + default: + throw new AssertionError("Unexpected unboxed type kind: " + unboxedType); + } + } + + 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); + } + + 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); + } + + private static void ifEqual1Else0(@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.IFNE, 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); + } } diff --git a/compiler/testData/codegen/box/boxingOptimization/explicitEqualsOnDouble.kt b/compiler/testData/codegen/box/boxingOptimization/explicitEqualsOnDouble.kt new file mode 100644 index 00000000000..ea864337add --- /dev/null +++ b/compiler/testData/codegen/box/boxingOptimization/explicitEqualsOnDouble.kt @@ -0,0 +1,8 @@ +// IGNORE_BACKEND: JS + +fun equals1(a: Double, b: Double) = a.equals(b) + +fun box(): String { + if ((-0.0).equals(0.0)) return "fail 0" + return "OK" +} diff --git a/compiler/testData/codegen/box/boxingOptimization/kt15871.kt b/compiler/testData/codegen/box/boxingOptimization/kt15871.kt new file mode 100644 index 00000000000..f41df27c983 --- /dev/null +++ b/compiler/testData/codegen/box/boxingOptimization/kt15871.kt @@ -0,0 +1,5 @@ +fun box() = + if (getAndCheck({ 42 }, { 42 })) "OK" else "fail" + +inline fun getAndCheck(getFirst: () -> T, getSecond: () -> T) = + getFirst() == getSecond() \ No newline at end of file diff --git a/compiler/testData/codegen/box/ieee754/safeCall.kt b/compiler/testData/codegen/box/ieee754/safeCall.kt index 06160ead6c5..5133ddaef43 100644 --- a/compiler/testData/codegen/box/ieee754/safeCall.kt +++ b/compiler/testData/codegen/box/ieee754/safeCall.kt @@ -2,6 +2,9 @@ fun box(): String { val plusZero: Double? = 0.0 val minusZero: Double = -0.0 + + useBoxed(plusZero) + if (plusZero?.equals(minusZero) ?: null!!) { return "fail 1" } @@ -11,4 +14,6 @@ fun box(): String { } return "OK" -} \ No newline at end of file +} + +fun useBoxed(a: Any?) {} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/boxingOptimization/boxingAndEquals.kt b/compiler/testData/codegen/bytecodeText/boxingOptimization/boxingAndEquals.kt new file mode 100644 index 00000000000..287598fc8dd --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/boxingOptimization/boxingAndEquals.kt @@ -0,0 +1,17 @@ +// https://youtrack.jetbrains.com/issue/KT-15871 + +// FILE: Test.kt + +fun getAndCheckInt(a: Int, b: Int) = + getAndCheck({ a }, { b }) + +// @TestKt.class: +// 0 valueOf +// 0 Value +// 0 areEqual + +// FILE: Inline.kt +inline fun getAndCheck(getFirst: () -> T, getSecond: () -> T) = + getFirst() == getSecond() + + diff --git a/compiler/testData/codegen/light-analysis/boxingOptimization/explicitEqualsOnDouble.txt b/compiler/testData/codegen/light-analysis/boxingOptimization/explicitEqualsOnDouble.txt new file mode 100644 index 00000000000..2219c1b2644 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/boxingOptimization/explicitEqualsOnDouble.txt @@ -0,0 +1,5 @@ +@kotlin.Metadata +public final class ExplicitEqualsOnDoubleKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static method equals1(p0: double, p1: double): boolean +} diff --git a/compiler/testData/codegen/light-analysis/boxingOptimization/kt15871.txt b/compiler/testData/codegen/light-analysis/boxingOptimization/kt15871.txt new file mode 100644 index 00000000000..6839236aca8 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/boxingOptimization/kt15871.txt @@ -0,0 +1,5 @@ +@kotlin.Metadata +public final class Kt15871Kt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static method getAndCheck(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function0, @org.jetbrains.annotations.NotNull p1: kotlin.jvm.functions.Function0): boolean +} diff --git a/compiler/testData/codegen/light-analysis/ieee754/safeCall.txt b/compiler/testData/codegen/light-analysis/ieee754/safeCall.txt index cde5626f92d..18ae83bac32 100644 --- a/compiler/testData/codegen/light-analysis/ieee754/safeCall.txt +++ b/compiler/testData/codegen/light-analysis/ieee754/safeCall.txt @@ -1,4 +1,5 @@ @kotlin.Metadata public final class SafeCallKt { public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static method useBoxed(@org.jetbrains.annotations.Nullable p0: java.lang.Object): void } 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 ed072d53368..c803c8c2a0c 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 @@ -884,6 +884,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("explicitEqualsOnDouble.kt") + public void testExplicitEqualsOnDouble() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/explicitEqualsOnDouble.kt"); + doTest(fileName); + } + @TestMetadata("fold.kt") public void testFold() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/fold.kt"); @@ -896,6 +902,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("kt15871.kt") + public void testKt15871() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt15871.kt"); + doTest(fileName); + } + @TestMetadata("kt5493.kt") public void testKt5493() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt5493.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 6efe697517e..d5d5aba1662 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -884,6 +884,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("explicitEqualsOnDouble.kt") + public void testExplicitEqualsOnDouble() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/explicitEqualsOnDouble.kt"); + doTest(fileName); + } + @TestMetadata("fold.kt") public void testFold() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/fold.kt"); @@ -896,6 +902,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("kt15871.kt") + public void testKt15871() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt15871.kt"); + doTest(fileName); + } + @TestMetadata("kt5493.kt") public void testKt5493() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt5493.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 2a229373c54..d52359ca62c 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -455,6 +455,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/boxingOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("boxingAndEquals.kt") + public void testBoxingAndEquals() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/boxingAndEquals.kt"); + doTest(fileName); + } + @TestMetadata("casts.kt") public void testCasts() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/casts.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 6d5ac301d8a..c0b56fbefb7 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 @@ -1094,6 +1094,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); } + @TestMetadata("explicitEqualsOnDouble.kt") + public void testExplicitEqualsOnDouble() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/explicitEqualsOnDouble.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("fold.kt") public void testFold() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/fold.kt"); @@ -1106,6 +1118,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } + @TestMetadata("kt15871.kt") + public void testKt15871() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt15871.kt"); + doTest(fileName); + } + @TestMetadata("kt5493.kt") public void testKt5493() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/kt5493.kt");