diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/ConstantConditionEliminationMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/ConstantConditionEliminationMethodTransformer.kt new file mode 100644 index 00000000000..4d0426d4eac --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/ConstantConditionEliminationMethodTransformer.kt @@ -0,0 +1,187 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.optimization + +import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter +import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue +import org.jetbrains.kotlin.codegen.optimization.common.insnText +import org.jetbrains.kotlin.codegen.optimization.fixStack.peek +import org.jetbrains.kotlin.codegen.optimization.fixStack.top +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame + +class ConstantConditionEliminationMethodTransformer : MethodTransformer() { + private val deadCodeElimination = DeadCodeEliminationMethodTransformer() + + override fun transform(internalClassName: String, methodNode: MethodNode) { + do { + val changes = ConstantConditionsOptimization(internalClassName, methodNode).run() + if (changes) deadCodeElimination.transform(internalClassName, methodNode) + } while (changes) + } + + private class ConstantConditionsOptimization(val internalClassName: String, val methodNode: MethodNode) { + fun run(): Boolean { + val actions = collectRewriteActions() + actions.forEach { it() } + return actions.isNotEmpty() + } + + private fun collectRewriteActions(): List<() -> Unit> = + arrayListOf<() -> Unit>().also { actions -> + val frames = analyze(internalClassName, methodNode, ConstantPropagationInterpreter()) + val insns = methodNode.instructions.toArray() + for (i in frames.indices) { + val frame = frames[i] ?: continue + val insn = insns[i] as? JumpInsnNode ?: continue + when (insn.opcode) { + in Opcodes.IFEQ .. Opcodes.IFLE -> + tryRewriteComparisonWithZero(insn, frame, actions) + in Opcodes.IF_ICMPEQ .. Opcodes.IF_ICMPLE -> + tryRewriteBinaryComparison(insn, frame, actions) + } + } + } + + private fun tryRewriteComparisonWithZero(insn: JumpInsnNode, frame: Frame, actions: ArrayList<() -> Unit>) { + val top = frame.top()!!.safeAs() ?: return + + val constCondition = when (insn.opcode) { + Opcodes.IFEQ -> top.value == 0 + Opcodes.IFNE -> top.value != 0 + Opcodes.IFGE -> top.value >= 0 + Opcodes.IFGT -> top.value > 0 + Opcodes.IFLE -> top.value <= 0 + Opcodes.IFLT -> top.value < 0 + else -> throw AssertionError("Unexpected instruction: ${insn.insnText}") + } + + actions.add { + methodNode.instructions.run { + insertBefore(insn, InsnNode(Opcodes.POP)) + if (constCondition) + set(insn, JumpInsnNode(Opcodes.GOTO, insn.label)) + else + remove(insn) + } + } + } + + private fun tryRewriteBinaryComparison(insn: JumpInsnNode, frame: Frame, actions: ArrayList<() -> Unit>) { + val arg1 = frame.peek(1)!! + val arg2 = frame.peek(0)!! + + if (arg1 is IConstValue && arg2 is IConstValue) { + rewriteBinaryComparisonOfConsts(insn, arg1.value, arg2.value, actions) + } + else if (arg2 is IConstValue && arg2.value == 0) { + rewriteBinaryComparisonWith0(insn, actions) + } + } + + private fun rewriteBinaryComparisonOfConsts(insn: JumpInsnNode, value1: Int, value2: Int, actions: ArrayList<() -> Unit>) { + val constCondition = when (insn.opcode) { + Opcodes.IF_ICMPEQ -> value1 == value2 + Opcodes.IF_ICMPNE -> value1 != value2 + Opcodes.IF_ICMPLE -> value1 <= value2 + Opcodes.IF_ICMPLT -> value1 < value2 + Opcodes.IF_ICMPGE -> value1 >= value2 + Opcodes.IF_ICMPGT -> value1 > value2 + else -> throw AssertionError("Unexpected instruction: ${insn.insnText}") + } + + actions.add { + methodNode.instructions.run { + insertBefore(insn, InsnNode(Opcodes.POP)) + insertBefore(insn, InsnNode(Opcodes.POP)) + if (constCondition) + set(insn, JumpInsnNode(Opcodes.GOTO, insn.label)) + else + remove(insn) + } + } + } + + private fun rewriteBinaryComparisonWith0(insn: JumpInsnNode, actions: ArrayList<() -> Unit>) { + actions.add { + methodNode.instructions.run { + insertBefore(insn, InsnNode(Opcodes.POP)) + val cmpWith0Opcode = when (insn.opcode) { + Opcodes.IF_ICMPEQ -> Opcodes.IFEQ + Opcodes.IF_ICMPNE -> Opcodes.IFNE + Opcodes.IF_ICMPLE -> Opcodes.IFLE + Opcodes.IF_ICMPLT -> Opcodes.IFLT + Opcodes.IF_ICMPGE -> Opcodes.IFGE + Opcodes.IF_ICMPGT -> Opcodes.IFGT + else -> throw AssertionError("Unexpected instruction: ${insn.insnText}") + } + set(insn, JumpInsnNode(cmpWith0Opcode, insn.label)) + } + } + } + } + + private class IConstValue private constructor(val value: Int) : StrictBasicValue(Type.INT_TYPE) { + override fun equals(other: Any?): Boolean = + other === this || + other is IConstValue && other.value == this.value + + override fun hashCode(): Int = value + + override fun toString(): String = "IConst($value)" + + companion object { + private val ICONST_CACHE = Array(7) { IConstValue(it - 1) } + + fun of(value: Int) = + if (value in -1 .. 5) + ICONST_CACHE[value + 1] + else + IConstValue(value) + } + } + + private class ConstantPropagationInterpreter : OptimizationBasicInterpreter() { + override fun newOperation(insn: AbstractInsnNode): BasicValue = + when (insn.opcode) { + in Opcodes.ICONST_M1 .. Opcodes.ICONST_5 -> + IConstValue.of(insn.opcode - Opcodes.ICONST_0) + Opcodes.BIPUSH, Opcodes.SIPUSH -> + IConstValue.of(insn.cast().operand) + Opcodes.LDC -> { + val operand = insn.cast().cst + if (operand is Int) + IConstValue.of(operand) + else + super.newOperation(insn) + } + else -> super.newOperation(insn) + } + + override fun merge(v: BasicValue, w: BasicValue): BasicValue = + if (v is IConstValue && w is IConstValue && v == w) + v + else + super.merge(v, w) + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt index 5d52d292cf3..0af77a1a3c2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt @@ -52,6 +52,7 @@ class OptimizationMethodVisitor( CapturedVarsOptimizationMethodTransformer(), RedundantNullCheckMethodTransformer(), RedundantCheckCastEliminationMethodTransformer(), + ConstantConditionEliminationMethodTransformer(), RedundantBoxingMethodTransformer(), RedundantCoercionToUnitTransformer(), DeadCodeEliminationMethodTransformer(), diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/PopBackwardPropagationTransformer.kt similarity index 90% rename from compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt rename to compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/PopBackwardPropagationTransformer.kt index 32af60cad84..41db1d3d3cc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/PopBackwardPropagationTransformer.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.codegen.optimization.boxing +import org.jetbrains.kotlin.codegen.optimization.common.isLoadOperation import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful import org.jetbrains.kotlin.codegen.optimization.fixStack.top import org.jetbrains.kotlin.codegen.optimization.removeNodeGetNext @@ -63,7 +64,7 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { private val frames by lazy { analyzeMethodBody() } fun transform() { - if (!insns.any { it.isUnitInstanceOrNull() }) return + if (!insns.any { it.isPurePush() }) return computeTransformations() for ((insn, transformation) in transformations.entries) { @@ -87,7 +88,7 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { } override fun unaryOperation(insn: AbstractInsnNode, value: SourceValue): SourceValue { - if (insn.opcode != Opcodes.CHECKCAST) { + if (insn.opcode != Opcodes.CHECKCAST && !insn.isPrimitiveTypeConversion()) { value.insns.markAsDontTouch() } return super.unaryOperation(insn, value) @@ -158,10 +159,22 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { transformations[insn] = replaceWithPopTransformation(boxedValueSize) } - insn.isUnitInstanceOrNull() -> { + insn.isPurePush() -> { transformations[insn] = replaceWithNopTransformation() } + insn.isPrimitiveTypeConversion() -> { + val inputTop = getInputTop(insn) + val sources = inputTop.insns + if (sources.all { !isDontTouch(it) }) { + transformations[insn] = replaceWithNopTransformation() + sources.forEach { propagatePopBackwards(it, inputTop.size) } + } + else { + transformations[insn] = replaceWithPopTransformation(poppedValueSize) + } + } + else -> { transformations[insn] = insertPopAfterTransformation(poppedValueSize) } @@ -232,7 +245,7 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { it.isPrimitiveBoxing() && (it as MethodInsnNode).owner == resultType private fun isTransformablePopOperand(insn: AbstractInsnNode) = - insn.opcode == Opcodes.CHECKCAST || insn.isPrimitiveBoxing() || insn.isUnitInstanceOrNull() + insn.opcode == Opcodes.CHECKCAST || insn.isPrimitiveBoxing() || insn.isPurePush() private fun isDontTouch(insn: AbstractInsnNode) = dontTouchInsnIndices[insnList.indexOf(insn)] @@ -240,9 +253,14 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { } -fun AbstractInsnNode.isUnitInstanceOrNull() = - opcode == Opcodes.ACONST_NULL || isUnitInstance() +fun AbstractInsnNode.isPurePush() = + isLoadOperation() || + opcode in Opcodes.ACONST_NULL .. Opcodes.LDC + 2 || + isUnitInstance() fun AbstractInsnNode.isUnitInstance() = opcode == Opcodes.GETSTATIC && this is FieldInsnNode && owner == "kotlin/Unit" && name == "INSTANCE" + +fun AbstractInsnNode.isPrimitiveTypeConversion() = + opcode in Opcodes.I2L .. Opcodes.I2S \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/constantConditions/cmpIntWith0.kt b/compiler/testData/codegen/bytecodeText/constantConditions/cmpIntWith0.kt new file mode 100644 index 00000000000..c62c37e3c31 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/constantConditions/cmpIntWith0.kt @@ -0,0 +1,25 @@ +// FILE: util.kt + +inline fun ieq(x: Int, y: Int) = x == y +inline fun ine(x: Int, y: Int) = x != y +inline fun ilt(x: Int, y: Int) = x < y +inline fun ile(x: Int, y: Int) = x <= y +inline fun igt(x: Int, y: Int) = x > y +inline fun ige(x: Int, y: Int) = x >= y + +// FILE: test.kt + +fun testeq(x: Int) = ieq(x, 0) +fun testne(x: Int) = ine(x, 0) +fun testlt(x: Int) = ilt(x, 0) +fun testle(x: Int) = ile(x, 0) +fun testgt(x: Int) = igt(x, 0) +fun testge(x: Int) = ige(x, 0) + +// @TestKt.class: +// 0 IF_ICMPEQ +// 0 IF_ICMPNE +// 0 IF_ICMPGE +// 0 IF_ICMPGT +// 0 IF_ICMPLE +// 0 IF_ICMPLT \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/constantConditions/constantFlag.kt b/compiler/testData/codegen/bytecodeText/constantConditions/constantFlag.kt new file mode 100644 index 00000000000..5a02a0a4bfe --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/constantConditions/constantFlag.kt @@ -0,0 +1,35 @@ +// FILE: util.kt +const val FLAG = true +const val OTHER_FLAG = false + +fun doStuff1() {} +fun doStuff2() {} +fun doStuff3() {} + +inline fun doStuff1IfTrue(flag: Boolean) { + if (flag) doStuff1() +} + +inline fun doStuff2IfFalse(flag: Boolean) { + if (!flag) doStuff2() +} + +inline fun doStuff3IfComplex(flag: Boolean) { + if (flag && !OTHER_FLAG) doStuff3() +} + +// FILE: test.kt +fun test() { + doStuff1IfTrue(FLAG) + doStuff2IfFalse(FLAG) + doStuff3IfComplex(FLAG) +} + +// @TestKt.class: +// 0 FLAG +// 1 INVOKESTATIC UtilKt.doStuff1 +// 0 INVOKESTATIC UtilKt.doStuff2 +// 1 INVOKESTATIC UtilKt.doStuff3 +// 0 ILOAD 0 +// 0 IFEQ +// 0 IFNE diff --git a/compiler/testData/codegen/bytecodeText/constantConditions/constantInt.kt b/compiler/testData/codegen/bytecodeText/constantConditions/constantInt.kt new file mode 100644 index 00000000000..420868fdf1b --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/constantConditions/constantInt.kt @@ -0,0 +1,58 @@ +// FILE: util.kt +const val MAGIC = 42 + +fun doStuffEq() {} +fun doStuffNotEq() {} +fun doStuffLt() {} +fun doStuffLe() {} +fun doStuffGt() {} +fun doStuffGe() {} + +inline fun doStuffIfEq(i: Int) { + if (i == MAGIC) doStuffEq() +} + +inline fun doStuffIfNotEq(i: Int) { + if (i != MAGIC) doStuffNotEq() +} + +inline fun doStuffIfLt(i: Int) { + if (i < MAGIC) doStuffLt() +} + +inline fun doStuffIfLe(i: Int) { + if (i <= MAGIC) doStuffLe() +} + +inline fun doStuffIfGt(i: Int) { + if (i > MAGIC) doStuffGt() +} + +inline fun doStuffIfGe(i: Int) { + if (i >= MAGIC) doStuffGe() +} + +// FILE: test.kt +fun test() { + doStuffIfEq(100) + doStuffIfNotEq(100) + doStuffIfLt(100) + doStuffIfLe(100) + doStuffIfGt(100) + doStuffIfGe(100) +} + +// @TestKt.class: +// 0 INVOKESTATIC UtilKt.doStuffEq +// 1 INVOKESTATIC UtilKt.doStuffNotEq +// 0 INVOKESTATIC UtilKt.doStuffLt +// 0 INVOKESTATIC UtilKt.doStuffLe +// 1 INVOKESTATIC UtilKt.doStuffGt +// 1 INVOKESTATIC UtilKt.doStuffGe +// 0 ILOAD 0 +// 0 IFEQ +// 0 IFNE +// 0 IFLT +// 0 IFLE +// 0 IFGE +// 0 IFGT \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/constantConditions/kt3098.kt b/compiler/testData/codegen/bytecodeText/constantConditions/kt3098.kt new file mode 100644 index 00000000000..0352c658985 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/constantConditions/kt3098.kt @@ -0,0 +1,9 @@ +fun testLt(x: String, y: String) = x < y +fun testLe(x: String, y: String) = x <= y +fun testGt(x: String, y: String) = x > y +fun testGe(x: String, y: String) = x >= y + +// 0 IF_ICMPGE +// 0 IF_ICMPGT +// 0 IF_ICMPLE +// 0 IF_ICMPLT \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index f8e9a0c7202..c1998ba68df 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -885,6 +885,39 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { } } + @TestMetadata("compiler/testData/codegen/bytecodeText/constantConditions") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConstantConditions extends AbstractBytecodeTextTest { + public void testAllFilesPresentInConstantConditions() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/constantConditions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("cmpIntWith0.kt") + public void testCmpIntWith0() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/constantConditions/cmpIntWith0.kt"); + doTest(fileName); + } + + @TestMetadata("constantFlag.kt") + public void testConstantFlag() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/constantConditions/constantFlag.kt"); + doTest(fileName); + } + + @TestMetadata("constantInt.kt") + public void testConstantInt() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/constantConditions/constantInt.kt"); + doTest(fileName); + } + + @TestMetadata("kt3098.kt") + public void testKt3098() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/constantConditions/kt3098.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/bytecodeText/constants") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)