Optimize constant conditions
Using basic constant propagation (only integer constants, no arithmetic calculations), rewrite conditional jump instructions with constant arguments. This covers problem description in KT-17007. Note that it also works transparently with inline functions. Partial evaluation is required to cover more "advanced" cases. As a side effect, this also covers KT-3098: rewrite IF_ICMP<cmp_op>(x, 0) to IF<cmp0_op>(x).
This commit is contained in:
+187
@@ -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<BasicValue>, actions: ArrayList<() -> Unit>) {
|
||||
val top = frame.top()!!.safeAs<IConstValue>() ?: 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<BasicValue>, 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<IntInsnNode>().operand)
|
||||
Opcodes.LDC -> {
|
||||
val operand = insn.cast<LdcInsnNode>().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)
|
||||
}
|
||||
}
|
||||
+1
@@ -52,6 +52,7 @@ class OptimizationMethodVisitor(
|
||||
CapturedVarsOptimizationMethodTransformer(),
|
||||
RedundantNullCheckMethodTransformer(),
|
||||
RedundantCheckCastEliminationMethodTransformer(),
|
||||
ConstantConditionEliminationMethodTransformer(),
|
||||
RedundantBoxingMethodTransformer(),
|
||||
RedundantCoercionToUnitTransformer(),
|
||||
DeadCodeEliminationMethodTransformer(),
|
||||
|
||||
+24
-6
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user