All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opalj.br.reader.BytecodeOptimizer.scala Maven / Gradle / Ivy

The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br
package reader

import scala.annotation.switch
import scala.annotation.tailrec

import net.ceedubs.ficus.Ficus._

import org.opalj.collection.immutable.IntTrieSet1
import org.opalj.log.OPALLogger.info
import org.opalj.br.instructions.Instruction
import org.opalj.br.instructions.GotoInstruction
import org.opalj.br.instructions.GOTO
import org.opalj.br.instructions.GOTO_W
import org.opalj.br.instructions.WIDE
import org.opalj.br.instructions.NOP
import org.opalj.br.instructions.POP
import org.opalj.br.instructions.POP2
import org.opalj.br.instructions.TABLESWITCH
import org.opalj.br.instructions.LOOKUPSWITCH
import org.opalj.br.instructions.IF_ACMPNE
import org.opalj.br.instructions.IF_ACMPEQ
import org.opalj.br.instructions.IFNE
import org.opalj.br.instructions.IFEQ
import org.opalj.br.instructions.IFLT
import org.opalj.br.instructions.IFGT
import org.opalj.br.instructions.IFGE
import org.opalj.br.instructions.IFLE
import org.opalj.br.instructions.IF_ICMPNE
import org.opalj.br.instructions.IF_ICMPEQ
import org.opalj.br.instructions.IF_ICMPLT
import org.opalj.br.instructions.IF_ICMPGT
import org.opalj.br.instructions.IF_ICMPGE
import org.opalj.br.instructions.IF_ICMPLE
import org.opalj.br.instructions.IFNONNULL
import org.opalj.br.instructions.IFNULL
import org.opalj.br.instructions.JSRInstruction
import org.opalj.br.instructions.JSR
import org.opalj.br.instructions.JSR_W

/**
 * Performs some very basic, in-place control-flow simplifications to make the code more regular.
 * In particular to make it more likely that an `if` that is actually a loop's `if` actually
 * jumps back.
 *
 * The following transformations are performed:
 *  - trivial gotos which jump to the immediately succeeding instruction are replaced by nops
 *    (the CFG may contain less basic blocks afterwards)
 *  - goto chains are shortened (i.e., a goto jumping to another goto)
 *    (this primarily reduces the number of instructions that need to be evaluated at
 *    abstract interpretation time; it may - however - also reduce the number of basic blocks)
 *  - useless ifs where the jump target is the next instruction are replaced by nops
 *    (the CFG may contain less basic blocks afterwards)
 *  - if -> goto instruction sequences are resolved
 *    (this primarily reduces the number of instructions that need to be evaluated at
 *    abstract interpretation time; it may - however - also reduce the number of basic blocks)
 *  - useless switches are replaced
 *    (the CFG may contain less basic blocks afterwards)
 *
 * The target array has the same size as the source array to make sure that branch offsets/line-
 * numbers etc. point to the correct instruction. Furthermore, we try to avoid the introduction
 * of dead code.
 *
 * @note All transformation always only reduce the number of basic blocks and never create new
 *       basic blocks. The existing stack map table attributes are never effected and remain
 *       valid; they may just contain more entries than strictly necessary.
 *
 * @author Michael Eichberg
 */
trait BytecodeOptimizer extends MethodsBinding {
    this: ClassFileBinding with ConstantPoolBinding with AttributeBinding =>

    final val PerformControlFlowSimplifications: Boolean = {
        val key = BytecodeOptimizer.SimplifyControlFlowKey
        val simplifyControlFlow: Boolean = config.as[Option[Boolean]](key).getOrElse(true)
        if (simplifyControlFlow) {
            info("class file reader", "the control-flow is simplified")
        } else {
            info("class file reader", "the control-flow is not simplified")
        }
        simplifyControlFlow
    }

    final val LogControlFlowSimplifications: Boolean = {
        val key = BytecodeOptimizer.LogControlFlowSimplificationKey
        val logControlFlowSimplification: Boolean = config.as[Option[Boolean]](key).getOrElse(false)
        if (logControlFlowSimplification) {
            info("class file reader", "control flow simplifications are logged")
        } else {
            info("class file reader", "control flow simplifications are not logged")
        }
        logControlFlowSimplification
    }

    abstract override def Method_Info(
        cp:               Constant_Pool,
        accessFlags:      Int,
        name_index:       Int,
        descriptor_index: Int,
        attributes:       Attributes
    ): Method_Info = {
        attributes collectFirst { case c: Code => c } foreach { code =>
            val isSimplified = optimizeInstructions(code.exceptionHandlers, code.instructions)
            if (isSimplified) {
                if (LogControlFlowSimplifications) {
                    val methodSignature = cp(name_index).asString + cp(descriptor_index).asString
                    info("class file reader", s"simplified control flow of $methodSignature")
                }
            }
        }
        super.Method_Info(cp, accessFlags, name_index, descriptor_index, attributes)
    }

    def optimizeInstructions(
        exceptionsHandlers: ExceptionHandlers,
        instructions:       Array[Instruction]
    ): Boolean = {
        var simplified: Boolean = false

        if (!PerformControlFlowSimplifications)
            return simplified;

        // This is the set of instructions which are effectively jumped to by if, switch, goto, and
        // jsr/ret instructions and exception handlers.
        // It contains those instructions which are definitive jump targets AFTER simplification.
        var jumpTargetInstructions =
            exceptionsHandlers.foldLeft(NoPCs) { (c, eh) => c + eh.handlerPC }

        // CONFUSED IF
        // A "confused if" is an if statement where the next instruction
        // is a goto instruction that is only reached by the if instruction:
        //
        //  i1: if a op b => goto i3
        //  i2: goto in
        //  i3: 
        //
        // can be replaced by (if the branchoffset is small enough!)
        //  i1: if !(a op b) => goto in
        //  i2: NOP
        //  i3: 
        var confusedIfs = NoPCs

        // TOTALLY CONFUSED IF
        // A totally confused "if" is an "if" which is directly succeeded by
        // two goto instructions and which jumps to the second goto and
        // both goto statements are only reached via the if...
        // In this case, we want to ensure that we jump back
        // in case of a jump (if possible...).
        // E.g., ..,IFLT(6),GOTO(6),GOTO(-201),...
        var totallyConfusedIfs = NoPCs

        /*
         * Given the pc of an instruction the final jump target is determined; that is, if the
         * instruction at currentPC is a goto, that target (by means of the adjusted branchoffset)
         * is returned.
         */
        @tailrec def finalJumpTarget(
            visitedPCs:            PCs, // required to detect jump cycles... yes they exist!
            currentPC:             PC,
            effectiveBranchoffset: Int
        ): Int = {
            instructions(currentPC) match {
                case GotoInstruction(branchoffset) =>
                    val nextPC = currentPC + branchoffset
                    if (!visitedPCs.contains(nextPC)) {
                        val nextBranchoffset = effectiveBranchoffset + branchoffset
                        finalJumpTarget(visitedPCs + nextPC, nextPC, nextBranchoffset)
                    } else {
                        effectiveBranchoffset
                    }

                case _ =>
                    effectiveBranchoffset
            }
        }

        val max = instructions.length
        var pc = 0
        while (pc < max) {
            var modifiedByWide = false
            var instruction = instructions(pc)
            if (instruction.opcode == WIDE.opcode) {
                modifiedByWide = true
                pc += 1 // <= WIDE.length
                instruction = instructions(pc)
            }
            val nextPC = instruction.indexOfNextInstruction(pc, modifiedByWide)

            (instruction.opcode: @switch) match {

                case IF_ACMPNE.opcode | IF_ACMPEQ.opcode |
                    IFNE.opcode | IFEQ.opcode |
                    IFLT.opcode | IFGT.opcode | IFGE.opcode | IFLE.opcode |
                    IF_ICMPNE.opcode | IF_ICMPEQ.opcode |
                    IF_ICMPLT.opcode | IF_ICMPGT.opcode | IF_ICMPGE.opcode | IF_ICMPLE.opcode |
                    IFNONNULL.opcode | IFNULL.opcode =>
                    val ifInstruction = instruction.asSimpleConditionalBranchInstruction
                    val branchoffset = ifInstruction.branchoffset
                    val jumpTargetPC = pc + branchoffset
                    if (jumpTargetPC == nextPC) {
                        instructions(pc) =
                            if (ifInstruction.numberOfPoppedOperands(NotRequired) == 1)
                                POP
                            else
                                POP2
                        instructions(pc + 1) = NOP
                        instructions(pc + 2) = NOP
                        simplified = true
                        // TODO log the removal of the totally USELESS IF instruction
                    } else {
                        val jumpTargetInstruction = instructions(jumpTargetPC)
                        val nextInstruction = instructions(nextPC)
                        if (nextInstruction.isGotoInstruction && branchoffset == 6) {
                            val nextGotoInstructionBranchoffset =
                                nextInstruction.asGotoInstruction.branchoffset
                            if (nextGotoInstructionBranchoffset == 3) {
                                // we have an if that - even when it falls through – indirectly
                                // just jumps to the overnext instruction; hence, we can
                                // "nop" both instructions right away...
                                instructions(pc + 0) =
                                    if (ifInstruction.numberOfPoppedOperands(NotRequired) == 1)
                                        POP
                                    else
                                        POP2
                                instructions(pc + 1) = NOP
                                instructions(pc + 2) = NOP
                                instructions(pc + 3) = NOP
                                instructions(pc + 4) = NOP
                                instructions(pc + 5) = NOP
                                simplified = true
                                // TODO log the removal of the totally USELESS IF instruction
                            } else if (jumpTargetInstruction.isGotoInstruction) {
                                totallyConfusedIfs += pc
                            } else {
                                confusedIfs += pc
                            }
                        } else if (jumpTargetInstruction.isGotoInstruction) {
                            val nextGoto = jumpTargetInstruction.asGotoInstruction
                            val newBranchoffset = nextGoto.branchoffset + branchoffset
                            if (newBranchoffset >= Short.MinValue && newBranchoffset <= Short.MaxValue) {
                                // Let's use the goto target as the if's target
                                // IMPROVE Consider using +!=
                                jumpTargetInstructions += pc + newBranchoffset
                                instructions(pc) = ifInstruction.copy(newBranchoffset)
                                simplified = true
                            }
                        }
                    }

                case GOTO.opcode =>
                    val GOTO(branchoffset) = instruction
                    val jumpTargetPC = pc + branchoffset
                    if (jumpTargetPC == nextPC) {
                        // let's replace the original jump
                        instructions(pc) = NOP
                        instructions(pc + 1) = NOP
                        instructions(pc + 2) = NOP
                        simplified = true
                    } else {
                        val newBranchoffset = finalJumpTarget(IntTrieSet1(pc), jumpTargetPC, branchoffset)
                        if (newBranchoffset != branchoffset &&
                            newBranchoffset >= Short.MinValue && newBranchoffset <= Short.MaxValue) {
                            // let's replace the original jump
                            // IMPROVE Consider using +!=
                            jumpTargetInstructions += pc + newBranchoffset
                            instructions(pc) = GOTO(newBranchoffset)
                            simplified = true
                        } else {
                            jumpTargetInstructions += jumpTargetPC
                        }
                    }

                case GOTO_W.opcode =>
                    val GOTO_W(branchoffset) = instruction
                    val jumpTargetPC = pc + branchoffset
                    if (jumpTargetPC == nextPC) {
                        // let's replace the original jump
                        instructions(pc) = NOP
                        instructions(pc + 1) = NOP
                        instructions(pc + 2) = NOP
                        instructions(pc + 3) = NOP
                        instructions(pc + 4) = NOP
                        simplified = true
                    } else {
                        val newBranchoffset = finalJumpTarget(IntTrieSet1(pc), jumpTargetPC, branchoffset)
                        if (newBranchoffset != branchoffset) {
                            // IMPROVE Consider using +!=
                            jumpTargetInstructions += pc + newBranchoffset
                            if (newBranchoffset >= Short.MinValue &&
                                newBranchoffset <= Short.MaxValue) {
                                // Replace it by a short goto
                                instructions(pc + 0) = NOP
                                instructions(pc + 1) = NOP
                                instructions(pc + 2) = GOTO(newBranchoffset)
                                simplified = true
                            } else {
                                // let's replace the original jump
                                instructions(pc) = GOTO_W(newBranchoffset)
                                simplified = true
                            }
                        }
                    }

                case TABLESWITCH.opcode | LOOKUPSWITCH.opcode =>
                    val switchInstruction = instruction.asCompoundConditionalBranchInstruction
                    val defaultOffset = switchInstruction.defaultOffset
                    if (switchInstruction.jumpOffsets.forall(_ == defaultOffset)) {
                        var i = pc + 1
                        instructions(pc) = POP
                        var newNextPC = -1
                        val jumpTargetPC = pc + defaultOffset
                        val jumpTargetInstruction = instructions(jumpTargetPC)
                        if (jumpTargetPC == nextPC) {
                            // totally useless..
                            newNextPC = nextPC
                        } else {
                            // This switch is basically just a goto... we will add
                            // the goto at the end of this section of the bytecode
                            // array; however, the original target remains valid...
                            // IMPROVE Consider using +!=
                            jumpTargetInstructions += pc + defaultOffset
                            instructions(pc) = POP
                            i = pc + 1
                            if (jumpTargetInstruction.isGotoInstruction) {
                                // actually... a kind of chain of gotos...
                                val nextGoto = jumpTargetInstruction.asGotoInstruction
                                val newBranchoffset =
                                    nextGoto.branchoffset +
                                        // defaultOffset corrected by the relocation of the goto
                                        defaultOffset - ((nextPC - 3) - pc)
                                if (newBranchoffset >= Short.MinValue &&
                                    newBranchoffset <= Short.MaxValue) {
                                    newNextPC = nextPC - 3
                                    instructions(newNextPC) = GOTO(newBranchoffset)
                                    instructions(nextPC - 2) = null
                                    instructions(nextPC - 1) = null
                                } else {
                                    newNextPC = nextPC - 5
                                    instructions(newNextPC) = GOTO_W(newBranchoffset + 2)
                                    instructions(nextPC - 4) = null
                                    instructions(nextPC - 3) = null
                                    instructions(nextPC - 2) = null
                                    instructions(nextPC - 1) = null
                                }
                            } else {
                                // the switch is just a goto, but not a chain of gotos...
                                newNextPC = nextPC - 3
                                instructions(nextPC - 3) = GOTO(defaultOffset - (newNextPC - pc))
                                instructions(nextPC - 2) = null
                                instructions(nextPC - 1) = null
                            }
                        }

                        while (i < newNextPC) { instructions(i) = NOP; i += 1 }
                        simplified = true
                        // TODO log the removal of the USELESS SWITCH instruction
                    } else {
                        // IMPROVE Optimize goto chains by directly jumping to the final target
                        // IMPROVE Consider using +!=
                        jumpTargetInstructions += pc + defaultOffset
                        switchInstruction.jumpOffsets foreach { branchoffset =>
                            jumpTargetInstructions += pc + branchoffset
                        }
                    }

                case JSR.opcode | JSR_W.opcode =>
                    // We do not need to handle the ret instructions... the target is clear
                    // right away... it is the instruction succeeding the jsr instruction.
                    val JSRInstruction(branchoffset) = instruction

                    // IMPROVE Consider using +!=
                    jumpTargetInstructions += pc + branchoffset
                    jumpTargetInstructions += nextPC

                case _ => // OK
            }
            pc = nextPC
            modifiedByWide = false
        }

        confusedIfs.filter { cIfPC => !jumpTargetInstructions.contains(cIfPC + 3) }.foreach { cIfPC =>
            // i1: if a op b => goto i3, i2: goto in, i3: 
            // =>
            // i1: if !(a op b) => goto in, i2: NOP, i2+1: NOP, i2+2:NOP, i3: 
            //
            // Recall that the next goto is already rewritten and will point to the final
            // jump target.
            val nextInstruction = instructions(cIfPC + 3)
            // if the next goto was a goto 3 then it was NOPified already...
            if (nextInstruction.isGotoInstruction) {
                val newBranchoffset = nextInstruction.asGotoInstruction.branchoffset + 3
                if (newBranchoffset >= Short.MinValue && newBranchoffset <= Short.MaxValue) {
                    val oldIf = instructions(cIfPC).asSimpleConditionalBranchInstruction
                    val newIf = oldIf.negate(newBranchoffset)
                    instructions(cIfPC) = newIf
                    instructions(cIfPC + 3) = NOP
                    instructions(cIfPC + 4) = NOP
                    instructions(cIfPC + 5) = NOP
                    simplified = true
                }
            }
        }

        totallyConfusedIfs.filter { cIfPC =>
            !jumpTargetInstructions.contains(cIfPC + 3) &&
                !jumpTargetInstructions.contains(cIfPC + 6)
        }.foreach { cIfPC =>
            // EXAMPLE:
            //
            // The sequence
            // (SPECIAL, BUT FREQUENT, CASE.. the second GOTO just jumps over the next goto..):
            //      pc=182: IF_ACMPNE(6), pc+3: GOTO(6), pc+6: GOTO(-170)
            // is rewritten to:
            //      pc=182: IF_ACMPNE(6 + (-170) ), pc+[3..9]: NOP
            //
            // AND (GENERAL CASE I - pc of the second target is smaller than the first target's pc)
            //      pc=347: IF_ICMPNE(6),GOTO(79),GOTO(-179)
            // =>   pc=347: IF_ICMPEQ(6-179), NOP*3, GOTO(79-3)
            //
            // AND (GENERAL CASE II - pc of the first target is smaller than the second target's pc)
            //      pc=347: IF_ICMPNE(6),GOTO(-79),GOTO(179)
            // =>   pc=347: IF_ICMPEQ(3-79), NOP*3, GOTO(179)

            val ifInstruction = instructions(cIfPC).asSimpleConditionalBranchInstruction
            val nextInstruction = instructions(cIfPC + 3)
            val jumpTargetInstruction = instructions(cIfPC + 6)
            if (nextInstruction.isGotoInstruction && jumpTargetInstruction.isGotoInstruction) {
                val firstGotoInstruction = nextInstruction.asGotoInstruction
                val secondGotoInstruction = jumpTargetInstruction.asGotoInstruction

                if (firstGotoInstruction.branchoffset == 6) {
                    val newBranchoffset = secondGotoInstruction.branchoffset + 6
                    if (newBranchoffset >= Short.MinValue && newBranchoffset <= Short.MaxValue) {
                        instructions(cIfPC) = ifInstruction.copy(newBranchoffset)
                        instructions(cIfPC + 3) = NOP
                        instructions(cIfPC + 4) = NOP
                        instructions(cIfPC + 5) = NOP
                        instructions(cIfPC + 6) = NOP
                        instructions(cIfPC + 7) = NOP
                        instructions(cIfPC + 8) = NOP
                        simplified = true
                    }
                } else {
                    // 1. determine which jump target has a lower pc
                    val firstAdjustedBranchoffset = firstGotoInstruction.branchoffset + 3
                    val secondAdjustedBranchoffset = secondGotoInstruction.branchoffset + 6
                    if (firstAdjustedBranchoffset < secondAdjustedBranchoffset) {
                        // ... CASE II
                        val newBranchoffset = firstAdjustedBranchoffset
                        if (newBranchoffset >= Short.MinValue && newBranchoffset <= Short.MaxValue) {
                            instructions(cIfPC) = ifInstruction.negate(firstAdjustedBranchoffset)
                            instructions(cIfPC + 3) = NOP
                            instructions(cIfPC + 4) = NOP
                            instructions(cIfPC + 5) = NOP
                            simplified = true
                        }

                    } else {
                        // ... CASE I
                        val newIfBranchoffset = secondAdjustedBranchoffset
                        val newGotoBranchoffset = firstAdjustedBranchoffset - 6
                        if (newIfBranchoffset >= Short.MinValue &&
                            newIfBranchoffset <= Short.MaxValue &&
                            newGotoBranchoffset >= Short.MinValue &&
                            newGotoBranchoffset <= Short.MaxValue) {
                            instructions(cIfPC) = ifInstruction.negate(firstAdjustedBranchoffset)
                            instructions(cIfPC + 3) = NOP
                            instructions(cIfPC + 4) = NOP
                            instructions(cIfPC + 5) = NOP
                            instructions(cIfPC + 6) = GOTO(newGotoBranchoffset)
                            simplified = true
                        }
                    }
                }
            }
        }

        simplified
    }

}

object BytecodeOptimizer {

    final val BytecodeOptimizerConfigKeyPrefix = {
        ClassFileReaderConfiguration.ConfigKeyPrefix+"BytecodeOptimizer."
    }

    final val SimplifyControlFlowKey = BytecodeOptimizerConfigKeyPrefix+"simplifyControlFlow"

    final val LogControlFlowSimplificationKey = {
        BytecodeOptimizerConfigKeyPrefix+"logControlFlowSimplification"
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy