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
|
||||
Reference in New Issue
Block a user