From 4b99d853222309390163e7d40e4876968d2c3221 Mon Sep 17 00:00:00 2001 From: Jiaxiang Chen Date: Tue, 2 Apr 2019 13:47:27 -0700 Subject: [PATCH] Optimize JVM byte code generation for conditional conjunction Implement an intrinsic method for boolean.and operation, and replace ANDAND condition with a call to such intrinsic method. --- .../kotlin/backend/jvm/intrinsics/AndAnd.kt | 34 +++++++++++++++++++ .../jvm/intrinsics/IrIntrinsicMethods.kt | 10 ++++++ .../lower/JvmBuiltinOptimizationLowering.kt | 26 +++++++++++--- .../box/intrinsics/nonShortCircuitAnd.kt | 18 ++++++++++ .../{conjuction.kt => conjunction.kt} | 1 - .../conditions/conjunctionInDoWhile.kt | 16 +++++++++ .../conditions/conjunctionInWhile.kt | 16 +++++++++ .../conditions/negatedConjuction.kt | 1 - .../lineNumber/custom/ifThenElseFalse.kt | 10 ++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 5 +++ .../codegen/BytecodeTextTestGenerated.java | 16 +++++++-- .../LightAnalysisModeTestGenerated.java | 5 +++ .../codegen/LineNumberTestGenerated.java | 5 +++ .../ir/IrBlackBoxCodegenTestGenerated.java | 5 +++ .../ir/IrBytecodeTextTestGenerated.java | 16 +++++++-- .../codegen/ir/IrLineNumberTestGenerated.java | 5 +++ .../IrJsCodegenBoxTestGenerated.java | 5 +++ .../semantics/JsCodegenBoxTestGenerated.java | 5 +++ 18 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/AndAnd.kt create mode 100644 compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt rename compiler/testData/codegen/bytecodeText/conditions/{conjuction.kt => conjunction.kt} (88%) create mode 100644 compiler/testData/codegen/bytecodeText/conditions/conjunctionInDoWhile.kt create mode 100644 compiler/testData/codegen/bytecodeText/conditions/conjunctionInWhile.kt create mode 100644 compiler/testData/lineNumber/custom/ifThenElseFalse.kt diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/AndAnd.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/AndAnd.kt new file mode 100644 index 00000000000..1e35328fab4 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/AndAnd.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.jvm.intrinsics + +import org.jetbrains.kotlin.backend.jvm.codegen.* +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression +import org.jetbrains.org.objectweb.asm.Label + +object AndAnd : IntrinsicMethod() { + + private class BooleanConjunction(val arg0: IrExpression, val arg1: IrExpression, val codegen: ExpressionCodegen, val data: BlockInfo) : BooleanValue(codegen.mv) { + + override fun jumpIfFalse(target: Label) { + arg0.accept(codegen, data).coerceToBoolean().jumpIfFalse(target) + arg1.accept(codegen, data).coerceToBoolean().jumpIfFalse(target) + } + + override fun jumpIfTrue(target: Label) { + val stayLabel = Label() + arg0.accept(codegen, data).coerceToBoolean().jumpIfFalse(stayLabel) + arg1.accept(codegen, data).coerceToBoolean().jumpIfTrue(target) + mv.visitLabel(stayLabel) + } + } + + override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue? { + val (left, right) = expression.receiverAndArgs() + return BooleanConjunction(left, right, codegen, data) + } +} \ No newline at end of file diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt index 04c2e06eb97..4898218f102 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt @@ -31,6 +31,10 @@ class IrIntrinsicMethods(irBuiltIns: IrBuiltIns) { val intrinsics = IntrinsicMethods() + val andandSymbol = irBuiltIns.run { defineOperator(OperatorNames.ANDAND, bool, listOf(bool, bool)) } + + private val andand = andandSymbol.descriptor + private val irMapping = hashMapOf() private fun createPrimitiveComparisonIntrinsics(typeToIrFun: Map, operator: KtSingleValueToken) { @@ -55,6 +59,8 @@ class IrIntrinsicMethods(irBuiltIns: IrBuiltIns) { irMapping[irBuiltIns.noWhenBranchMatchedException] = IrNoWhenBranchMatchedException() irMapping[irBuiltIns.illegalArgumentException] = IrIllegalArgumentException() irMapping[irBuiltIns.throwNpe] = ThrowNPE() + + irMapping[andand] = AndAnd } fun getIntrinsic(descriptor: CallableMemberDescriptor): IntrinsicMethod? { @@ -64,4 +70,8 @@ class IrIntrinsicMethods(irBuiltIns: IrBuiltIns) { } return irMapping[descriptor.original] } + + private object OperatorNames { + const val ANDAND = "ANDAND" + } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmBuiltinOptimizationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmBuiltinOptimizationLowering.kt index f3d54545c8a..518b2b74c33 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmBuiltinOptimizationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmBuiltinOptimizationLowering.kt @@ -15,13 +15,12 @@ import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrVariable import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.types.isBoolean import org.jetbrains.kotlin.ir.types.isPrimitiveType import org.jetbrains.kotlin.ir.types.toKotlinType -import org.jetbrains.kotlin.ir.util.coerceToUnitIfNeeded -import org.jetbrains.kotlin.ir.util.isFalseConst -import org.jetbrains.kotlin.ir.util.isNullConst -import org.jetbrains.kotlin.ir.util.isTrueConst +import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid @@ -102,6 +101,25 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower expression.branches.removeIf() { it.condition.isFalseConst() && isCompilerGenerated } + if (expression.origin == IrStatementOrigin.ANDAND) { + assert(expression.type.isBoolean() + && expression.branches.size == 2 + && expression.branches[1].condition.isTrueConst() + && expression.branches[1].result.isFalseConst()) { + "ANDAND condition should have an 'if true then false' body on its second branch. " + + "Failing expression: ${expression.dump()}" + } + // Replace conjunction condition with intrinsic "and" function call + return IrCallImpl( + expression.startOffset, + expression.endOffset, + context.irBuiltIns.booleanType, + context.irIntrinsics.andandSymbol + ).apply { + dispatchReceiver = expression.branches[0].condition + putValueArgument(0, expression.branches[0].result) + } + } // If the only condition that is left has a constant true condition remove the // when in favor of the result. If there are no conditions left, remove the when // entirely and replace it with an empty block. diff --git a/compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt b/compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt new file mode 100644 index 00000000000..ae59d18b804 --- /dev/null +++ b/compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt @@ -0,0 +1,18 @@ +var s = "" + +fun o(): Boolean { + s += "O" + return false +} + +fun k(): Boolean { + s += "K" + return true +} + +fun box(): String { + val b = o() and k() + if (b) + return "fail: b should be false" + return s +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/conditions/conjuction.kt b/compiler/testData/codegen/bytecodeText/conditions/conjunction.kt similarity index 88% rename from compiler/testData/codegen/bytecodeText/conditions/conjuction.kt rename to compiler/testData/codegen/bytecodeText/conditions/conjunction.kt index e8b7664b13e..c9310c66162 100644 --- a/compiler/testData/codegen/bytecodeText/conditions/conjuction.kt +++ b/compiler/testData/codegen/bytecodeText/conditions/conjunction.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR val a = false val b = false val c = false diff --git a/compiler/testData/codegen/bytecodeText/conditions/conjunctionInDoWhile.kt b/compiler/testData/codegen/bytecodeText/conditions/conjunctionInDoWhile.kt new file mode 100644 index 00000000000..0eea7efdcb3 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/conditions/conjunctionInDoWhile.kt @@ -0,0 +1,16 @@ +val a = false +val b = false +val c = false + +fun main() { + do { + "loop" + } while (a && b && c) +} + +// 0 ICONST_0 +// 0 ICONST_1 +// 2 IFEQ +// 1 IFNE +// 3 IF +// 0 GOTO \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/conditions/conjunctionInWhile.kt b/compiler/testData/codegen/bytecodeText/conditions/conjunctionInWhile.kt new file mode 100644 index 00000000000..0d0cc3505ba --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/conditions/conjunctionInWhile.kt @@ -0,0 +1,16 @@ +val a = false +val b = false +val c = false + +fun main() { + while (a && b && c) { + "loop" + } +} + +// 0 ICONST_0 +// 0 ICONST_1 +// 3 IFEQ +// 0 IFNE +// 3 IF +// 1 GOTO \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/conditions/negatedConjuction.kt b/compiler/testData/codegen/bytecodeText/conditions/negatedConjuction.kt index b1776a887d0..522c2185004 100644 --- a/compiler/testData/codegen/bytecodeText/conditions/negatedConjuction.kt +++ b/compiler/testData/codegen/bytecodeText/conditions/negatedConjuction.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR val a = false val b = false val c = false diff --git a/compiler/testData/lineNumber/custom/ifThenElseFalse.kt b/compiler/testData/lineNumber/custom/ifThenElseFalse.kt new file mode 100644 index 00000000000..57ece85a601 --- /dev/null +++ b/compiler/testData/lineNumber/custom/ifThenElseFalse.kt @@ -0,0 +1,10 @@ +fun cond() = false + +fun foo() { + if (cond()) + cond() + else + false +} + +// 1 4 5 7 8 \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 5ea91696c18..cc3cb4e629e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -13534,6 +13534,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/intrinsics/longRangeWithExplicitDot.kt"); } + @TestMetadata("nonShortCircuitAnd.kt") + public void testNonShortCircuitAnd() throws Exception { + runTest("compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt"); + } + @TestMetadata("prefixIncDec.kt") public void testPrefixIncDec() throws Exception { runTest("compiler/testData/codegen/box/intrinsics/prefixIncDec.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 082b3b3a785..4d28aac4c31 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -894,9 +894,19 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/conditions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } - @TestMetadata("conjuction.kt") - public void testConjuction() throws Exception { - runTest("compiler/testData/codegen/bytecodeText/conditions/conjuction.kt"); + @TestMetadata("conjunction.kt") + public void testConjunction() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/conditions/conjunction.kt"); + } + + @TestMetadata("conjunctionInDoWhile.kt") + public void testConjunctionInDoWhile() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/conditions/conjunctionInDoWhile.kt"); + } + + @TestMetadata("conjunctionInWhile.kt") + public void testConjunctionInWhile() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/conditions/conjunctionInWhile.kt"); } @TestMetadata("disjunction.kt") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index e68c78a69b9..e1d093c180a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -13534,6 +13534,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/intrinsics/longRangeWithExplicitDot.kt"); } + @TestMetadata("nonShortCircuitAnd.kt") + public void testNonShortCircuitAnd() throws Exception { + runTest("compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt"); + } + @TestMetadata("prefixIncDec.kt") public void testPrefixIncDec() throws Exception { runTest("compiler/testData/codegen/box/intrinsics/prefixIncDec.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LineNumberTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LineNumberTestGenerated.java index 8c1e1b8e681..0dee935ce00 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LineNumberTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LineNumberTestGenerated.java @@ -181,6 +181,11 @@ public class LineNumberTestGenerated extends AbstractLineNumberTest { runTest("compiler/testData/lineNumber/custom/ifThenElse.kt"); } + @TestMetadata("ifThenElseFalse.kt") + public void testIfThenElseFalse() throws Exception { + runTest("compiler/testData/lineNumber/custom/ifThenElseFalse.kt"); + } + @TestMetadata("inTheEndOfLambdaArgumentOfInlineCall.kt") public void testInTheEndOfLambdaArgumentOfInlineCall() throws Exception { runTest("compiler/testData/lineNumber/custom/inTheEndOfLambdaArgumentOfInlineCall.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 561e3121f76..ceb7fb57f46 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -13539,6 +13539,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/intrinsics/longRangeWithExplicitDot.kt"); } + @TestMetadata("nonShortCircuitAnd.kt") + public void testNonShortCircuitAnd() throws Exception { + runTest("compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt"); + } + @TestMetadata("prefixIncDec.kt") public void testPrefixIncDec() throws Exception { runTest("compiler/testData/codegen/box/intrinsics/prefixIncDec.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java index 1850ccde0fb..e448271d7ea 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBytecodeTextTestGenerated.java @@ -894,9 +894,19 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/conditions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true); } - @TestMetadata("conjuction.kt") - public void testConjuction() throws Exception { - runTest("compiler/testData/codegen/bytecodeText/conditions/conjuction.kt"); + @TestMetadata("conjunction.kt") + public void testConjunction() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/conditions/conjunction.kt"); + } + + @TestMetadata("conjunctionInDoWhile.kt") + public void testConjunctionInDoWhile() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/conditions/conjunctionInDoWhile.kt"); + } + + @TestMetadata("conjunctionInWhile.kt") + public void testConjunctionInWhile() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/conditions/conjunctionInWhile.kt"); } @TestMetadata("disjunction.kt") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrLineNumberTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrLineNumberTestGenerated.java index c91d12b066b..0b345a29741 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrLineNumberTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrLineNumberTestGenerated.java @@ -181,6 +181,11 @@ public class IrLineNumberTestGenerated extends AbstractIrLineNumberTest { runTest("compiler/testData/lineNumber/custom/ifThenElse.kt"); } + @TestMetadata("ifThenElseFalse.kt") + public void testIfThenElseFalse() throws Exception { + runTest("compiler/testData/lineNumber/custom/ifThenElseFalse.kt"); + } + @TestMetadata("inTheEndOfLambdaArgumentOfInlineCall.kt") public void testInTheEndOfLambdaArgumentOfInlineCall() throws Exception { runTest("compiler/testData/lineNumber/custom/inTheEndOfLambdaArgumentOfInlineCall.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index 00c8af99961..8caf811a11f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -10854,6 +10854,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/intrinsics/longRangeWithExplicitDot.kt"); } + @TestMetadata("nonShortCircuitAnd.kt") + public void testNonShortCircuitAnd() throws Exception { + runTest("compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt"); + } + @TestMetadata("prefixIncDec.kt") public void testPrefixIncDec() throws Exception { runTest("compiler/testData/codegen/box/intrinsics/prefixIncDec.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 86833693504..8184d29b9ee 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 @@ -11999,6 +11999,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/intrinsics/longRangeWithExplicitDot.kt"); } + @TestMetadata("nonShortCircuitAnd.kt") + public void testNonShortCircuitAnd() throws Exception { + runTest("compiler/testData/codegen/box/intrinsics/nonShortCircuitAnd.kt"); + } + @TestMetadata("prefixIncDec.kt") public void testPrefixIncDec() throws Exception { runTest("compiler/testData/codegen/box/intrinsics/prefixIncDec.kt");