/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.compilers.opt;
import java.lang.reflect.Constructor;
import org.jikesrvm.VM;
import static org.jikesrvm.compilers.opt.OPT_Constants.YES;
import org.jikesrvm.compilers.opt.ir.BoundsCheck;
import org.jikesrvm.compilers.opt.ir.GuardedUnary;
import org.jikesrvm.compilers.opt.ir.Move;
import org.jikesrvm.compilers.opt.ir.NewArray;
import org.jikesrvm.compilers.opt.ir.OPT_BasicBlock;
import org.jikesrvm.compilers.opt.ir.OPT_BasicBlockEnumeration;
import org.jikesrvm.compilers.opt.ir.OPT_IR;
import org.jikesrvm.compilers.opt.ir.OPT_Instruction;
import org.jikesrvm.compilers.opt.ir.OPT_IntConstantOperand;
import org.jikesrvm.compilers.opt.ir.OPT_Operand;
import org.jikesrvm.compilers.opt.ir.OPT_OperandEnumeration;
import org.jikesrvm.compilers.opt.ir.OPT_Operator;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.ARRAYLENGTH_opcode;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.BOUNDS_CHECK_opcode;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.GUARD_MOVE;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.INT_MOVE;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.NEWARRAY;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.NEWARRAY_UNRESOLVED;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.NOP;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.PHI;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.UNINT_BEGIN;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.UNINT_END;
import org.jikesrvm.compilers.opt.ir.OPT_Register;
import org.jikesrvm.compilers.opt.ir.OPT_RegisterOperand;
import org.jikesrvm.compilers.opt.ir.OPT_TrueGuardOperand;
import org.jikesrvm.compilers.opt.ir.Phi;
/*
* Simple flow-insensitive optimizations.
*
* <p> Except for the "CompilerPhase" methods, all fields and methods in
* this class are declared static.
*/
public final class OPT_Simple extends OPT_CompilerPhase {
private final OPT_BranchOptimizations branchOpts = new OPT_BranchOptimizations(-1, false, false, false);
/**
* At what optimization level should this phase be run?
*/
private int level;
/**
* Perform type propagation?
*/
private final boolean typeProp;
/**
* Attempt to eliminate bounds and cast checks?
*/
private final boolean foldChecks;
/**
* Fold conditional branches with constant operands?
*/
private final boolean foldBranches;
public boolean shouldPerform(OPT_Options options) {
return options.getOptLevel() >= level;
}
public String getName() {
return "Simple Opts";
}
public boolean printingEnabled(OPT_Options options, boolean before) {
return false;
}
/**
* By default, perform all optimizations at O1 and higher.
*/
public OPT_Simple() {
this(1, true, true, true);
}
/**
* The constructor is used to specify what pieces of OPT_Simple will
* be enabled for this instance. Some pieces are always enabled.
* Customizing can be useful because some of the optimizations are not
* valid/useful on LIR or even on "late stage" HIR. With this
* constructor, branches will be folded.
*
* @param typeProp should type propagation be peformed
* @param foldChecks should we attempt to eliminate boundscheck
*/
public OPT_Simple(boolean typeProp, boolean foldChecks) {
this(1, typeProp, foldChecks, false);
}
/**
* The constructor is used to specify what pieces of OPT_Simple will
* be enabled for this instance. Some pieces are always enabled.
* Customizing can be useful because some of the optimizations are not
* valid/useful on LIR or even on "late stage" HIR. With this
* constructor, branches will be folded.
*
* @param level at what optimization level should the phase be enabled?
* @param typeProp should type propagation be peformed
* @param foldChecks should we attempt to eliminate boundscheck
*/
public OPT_Simple(int level, boolean typeProp, boolean foldChecks) {
this(level, typeProp, foldChecks, true);
}
/**
* The constructor is used to specify what pieces of OPT_Simple will
* be enabled for this instance. Some pieces are always enabled.
* Customizing can be useful because some of the optimizations are not
* valid/useful on LIR or even on "late stage" HIR.
*
* @param typeProp should type propagation be peformed?
* @param foldChecks should we attempt to eliminate boundscheck?
* @param foldBranches should we attempt to constant fold conditional
* branches?
*/
public OPT_Simple(boolean typeProp, boolean foldChecks, boolean foldBranches) {
this(1, typeProp, foldChecks, foldBranches);
}
/**
* The constructor is used to specify what pieces of OPT_Simple will
* be enabled for this instance. Some pieces are always enabled.
* Customizing can be useful because some of the optimizations are not
* valid/useful on LIR or even on "late stage" HIR.
*
* @param level at what optimization level should the phase be enabled?
* @param typeProp should type propagation be peformed?
* @param foldChecks should we attempt to eliminate boundscheck?
* @param foldBranches should we attempt to constant fold conditional
* branches?
*/
public OPT_Simple(int level, boolean typeProp, boolean foldChecks, boolean foldBranches) {
super(new Object[]{level, typeProp, foldChecks, foldBranches});
this.level = level;
this.typeProp = typeProp;
this.foldChecks = foldChecks;
this.foldBranches = foldBranches;
}
/**
* Constructor for this compiler phase
*/
private static final Constructor<OPT_CompilerPhase> constructor =
getCompilerPhaseConstructor(OPT_Simple.class,
new Class[]{Integer.TYPE, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE});
/**
* Get a constructor object for this compiler phase
* @return compiler phase constructor
*/
public Constructor<OPT_CompilerPhase> getClassConstructor() {
return constructor;
}
/**
* Main driver for the simple optimizations
*
* @param ir the IR to optimize
*/
public void perform(OPT_IR ir) {
// Compute defList, useList, useCount fields for each register.
OPT_DefUse.computeDU(ir);
// Recompute isSSA flags
OPT_DefUse.recomputeSSA(ir);
// Simple copy propagation.
// This pass incrementally updates the register list.
copyPropagation(ir);
// Simple type propagation.
// This pass uses the register list, but doesn't modify it.
if (typeProp) {
typePropagation(ir);
}
// Perform simple bounds-check and arraylength elimination.
// This pass incrementally updates the register list
if (foldChecks) {
arrayPropagation(ir);
}
// Simple dead code elimination.
// This pass incrementally updates the register list
eliminateDeadInstructions(ir);
// constant folding
// This pass usually doesn't modify the DU, but
// if it does it will recompute it.
foldConstants(ir);
// Simple local expression folding respecting DU
if (OPT_ExpressionFolding.performLocal(ir)) {
// constant folding again
foldConstants(ir);
}
// Try to remove conditional branches with constant operands
// If it actually constant folds a branch,
// this pass will recompute the DU
if (foldBranches) {
simplifyConstantBranches(ir);
}
}
/**
* Perform flow-insensitive copy and constant propagation using
* register list information.
*
* <ul>
* <li> Note: register list MUST be initialized BEFORE calling this routine.
* <li> Note: this function incrementally maintains the register list.
* </ul>
*
* @param ir the IR in question
*/
static void copyPropagation(OPT_IR ir) {
// Use register list to enumerate register objects
OPT_Register elemNext;
boolean reiterate = true;
while (reiterate) { // /MT/ better think about proper ordering.
reiterate = false;
for (OPT_Register reg = ir.regpool.getFirstSymbolicRegister(); reg != null; reg = elemNext) {
elemNext = reg.getNext(); // we may remove reg, so get elemNext up front
if (reg.useList == null || // Copy propagation not possible if reg
// has no uses
reg.defList == null || // Copy propagation not possible if reg
// has no defs
!reg.isSSA()) { // Flow-insensitive copy prop only possible
// for SSA registers.
continue;
}
// isSSA => reg has exactly one definition, reg.defList.
OPT_RegisterOperand lhs = reg.defList;
OPT_Instruction defInstr = lhs.instruction;
OPT_Operand rhs;
// Copy/constant propagation only possible when defInstr is a move
if (defInstr.isMove()) {
rhs = Move.getVal(defInstr);
} else if (defInstr.operator() == PHI) {
OPT_Operand phiVal = equivalentValforPHI(defInstr);
if (phiVal == null) continue;
rhs = phiVal;
} else {
continue;
}
if (rhs.isRegister()) {
OPT_Register rrhs = rhs.asRegister().getRegister();
// If rhs is a non-SSA register, then we can't propagate it
// because we can't be sure that the same definition reaches
// all uses.
if (!rrhs.isSSA()) continue;
// If rhs is a physical register, then we can't safely propagate
// it to uses of lhs because we don't understand the implicit
// uses/defs of physical registers well enough to do so safely.
if (rrhs.isPhysical()) continue;
}
reiterate = ir.options.getOptLevel() > 1;
// Now substitute rhs for all uses of lhs, updating the
// register list as we go.
if (rhs.isRegister()) {
OPT_RegisterOperand nextUse;
OPT_RegisterOperand rhsRegOp = rhs.asRegister();
for (OPT_RegisterOperand use = reg.useList; use != null; use = nextUse) {
nextUse = use.getNext(); // get early before reg's useList is updated.
if (VM.VerifyAssertions) VM._assert(rhsRegOp.getRegister().getType() == use.getRegister().getType());
OPT_DefUse.transferUse(use, rhsRegOp);
}
} else if (rhs.isConstant()) {
// NOTE: no need to incrementally update use's register list since we are going
// to blow it all away as soon as this loop is done.
for (OPT_RegisterOperand use = reg.useList; use != null; use = use.getNext()) {
int index = use.getIndexInInstruction();
use.instruction.putOperand(index, rhs.copy());
}
} else {
throw new OPT_OptimizingCompilerException("OPT_Simple.copyPropagation: unexpected operand type");
}
// defInstr is now dead. Remove it.
defInstr.remove();
if (rhs.isRegister()) {
OPT_DefUse.removeUse(rhs.asRegister());
}
ir.regpool.removeRegister(lhs.getRegister());
}
}
}
/**
* Try to find an operand that is equivalent to the result of a
* given phi instruction.
*
* @param phi the instruction to be simplified
* @return one of the phi's operands that is equivalent to the phi's result,
* or null if the phi can not be simplified.
*/
static OPT_Operand equivalentValforPHI(OPT_Instruction phi) {
if (!Phi.conforms(phi)) return null;
// search for the first input that is different from the result
OPT_Operand result = Phi.getResult(phi), equiv = result;
int i = 0, n = Phi.getNumberOfValues(phi);
while (i < n) {
equiv = Phi.getValue(phi, i++);
if (!equiv.similar(result)) break;
}
// no luck if result and equiv aren't the only distinct inputs
while (i < n) {
OPT_Operand opi = Phi.getValue(phi, i++);
if (!opi.similar(equiv) && !opi.similar(result)) return null;
}
return equiv;
}
/**
* Perform flow-insensitive type propagation using register list
* information. Note: register list MUST be initialized BEFORE
* calling this routine.
*
* <p> Kept separate from copyPropagation loop to enable clients
* more flexibility.
*
* @param ir the IR in question
*/
static void typePropagation(OPT_IR ir) {
// Use register list to enumerate register objects (FAST)
OPT_Register elemNext;
for (OPT_Register reg = ir.regpool.getFirstSymbolicRegister(); reg != null; reg = elemNext) {
elemNext = reg.getNext();
// Type propagation not possible if reg has no uses
if (reg.useList == null) {
continue;
}
// Type propagation not possible if reg has no defs
if (reg.defList == null) {
continue;
}
// Do not attempt type propagation if reg has multiple defs
if (!reg.isSSA()) {
continue;
}
// Now reg has exactly one definition
OPT_RegisterOperand lhs = reg.defList;
OPT_Instruction instr = lhs.instruction;
OPT_Operator op = instr.operator();
// Type propagation not possible if lhs is not in a move instr
if (!op.isMove()) {
continue;
}
OPT_Operand rhsOp = Move.getVal(instr);
// Do not attempt type propagation if RHS is not a register
if (!(rhsOp instanceof OPT_RegisterOperand)) {
continue;
}
OPT_RegisterOperand rhs = (OPT_RegisterOperand) rhsOp;
// Propagate the type in the def
lhs.copyType(rhs);
// Now propagate lhs into all uses; substitute rhs.type for lhs.type
for (OPT_RegisterOperand use = reg.useList; use != null; use = use.getNext()) {
// if rhs.type is a supertype of use.type, don't do it
// because use.type has more detailed information
if (OPT_ClassLoaderProxy.includesType(rhs.getType(), use.getType()) == YES) {
continue;
}
// If VM_Magic has been employed to convert an int to a reference,
// don't undo the effects!
if (rhs.getType().isPrimitiveType() && !use.getType().isPrimitiveType()) {
continue;
}
use.copyType(rhs);
}
}
}
/**
* Perform flow-insensitive propagation to eliminate bounds checks
* and arraylength for arrays with static lengths. Only useful on the HIR
* (because BOUNDS_CHECK is expanded in LIR into multiple instrs)
*
* <p> Note: this function incrementally maintains the register list.
*
* @param ir the IR in question
*/
static void arrayPropagation(OPT_IR ir) {
// Use register list to enumerate register objects (FAST)
OPT_Register elemNext;
for (OPT_Register reg = ir.regpool.getFirstSymbolicRegister(); reg != null; reg = elemNext) {
elemNext = reg.getNext();
if (reg.useList == null) {
continue;
}
if (reg.defList == null) {
continue;
}
if (!reg.isSSA()) {
continue;
}
// Now reg has exactly one definition
OPT_RegisterOperand lhs = reg.defList;
OPT_Instruction instr = lhs.instruction;
OPT_Operator op = instr.operator();
if (!(op == NEWARRAY || op == NEWARRAY_UNRESOLVED)) {
continue;
}
OPT_Operand sizeOp = NewArray.getSize(instr);
// check for an array whose length is a compile-time constant
// or an SSA register
boolean boundsCheckOK = false;
boolean arraylengthOK = false;
int size = -1;
if (sizeOp instanceof OPT_IntConstantOperand) {
size = ((OPT_IntConstantOperand) sizeOp).value;
boundsCheckOK = true;
arraylengthOK = true;
} else if (sizeOp instanceof OPT_RegisterOperand) {
if (sizeOp.asRegister().getRegister().isSSA()) {
arraylengthOK = true;
}
}
// Now propagate
for (OPT_RegisterOperand use = reg.useList; use != null; use = use.getNext()) {
OPT_Instruction i = use.instruction;
// bounds-check elimination
if (boundsCheckOK && i.getOpcode() == BOUNDS_CHECK_opcode) {
OPT_Operand indexOp = BoundsCheck.getIndex(i);
if (indexOp instanceof OPT_IntConstantOperand) {
if (((OPT_IntConstantOperand) indexOp).value <= size) {
OPT_Instruction s =
Move.create(GUARD_MOVE, BoundsCheck.getGuardResult(i).copyD2D(), new OPT_TrueGuardOperand());
s.position = i.position;
s.bcIndex = i.bcIndex;
i.insertAfter(s);
OPT_DefUse.updateDUForNewInstruction(s);
OPT_DefUse.removeInstructionAndUpdateDU(i);
}
}
} else if (arraylengthOK && i.getOpcode() == ARRAYLENGTH_opcode) {
OPT_Operand newSizeOp = sizeOp.copy();
OPT_RegisterOperand result = (OPT_RegisterOperand) GuardedUnary.getResult(i).copy();
OPT_Instruction s = Move.create(INT_MOVE, result, newSizeOp);
s.position = i.position;
s.bcIndex = i.bcIndex;
i.insertAfter(s);
OPT_DefUse.updateDUForNewInstruction(s);
OPT_DefUse.removeInstructionAndUpdateDU(i);
}
}
}
}
/**
* Simple conservative dead code elimination.
* An instruction is eliminated if:
* <ul>
* <li> 1. it is not a PEI, store or call
* <li> 2. it DEFs only registers
* <li> 3. all registers it DEFS are dead
* </ul>
*
* <p> Note: this function incrementally maintains the register list.
*
* @param ir the IR to optimize
*/
static void eliminateDeadInstructions(OPT_IR ir) {
eliminateDeadInstructions(ir, false);
}
/**
* Simple conservative dead code elimination.
* An instruction is eliminated if:
* <ul>
* <li> 1. it is not a PEI, store or call
* <li> 2. it DEFs only registers
* <li> 3. all registers it DEFS are dead
* </ul>
*
* <p> Note: this function incrementally maintains the register list.
*
* @param ir IR to optimize
* @param preserveImplicitSSA if this is true, do not eliminate dead
* instructions that have implicit operands for heap array SSA form
*/
static void eliminateDeadInstructions(OPT_IR ir, boolean preserveImplicitSSA) {
// (USE BACKWARDS PASS FOR INCREASED EFFECTIVENESS)
for (OPT_Instruction instr = ir.lastInstructionInCodeOrder(),
prevInstr = null; instr != null; instr = prevInstr) {
prevInstr = instr.prevInstructionInCodeOrder(); // cache because
// remove nulls next/prev fields
// if instr is a PEI, store, branch, or call, then it's not dead ...
if (instr.isPEI() || instr.isImplicitStore() || instr.isBranch() || instr.isCall()) {
continue;
}
if (preserveImplicitSSA && (instr.isImplicitLoad() || instr.isAllocation() || instr.operator() == PHI)) {
continue;
}
// remove NOPs
if (instr.operator() == NOP) {
OPT_DefUse.removeInstructionAndUpdateDU(instr);
}
// remove UNINT_BEGIN/UNINT_END with nothing in between them
if (instr.operator() == UNINT_BEGIN) {
OPT_Instruction s = instr.nextInstructionInCodeOrder();
if (s.operator() == UNINT_END) {
OPT_DefUse.removeInstructionAndUpdateDU(s);
OPT_DefUse.removeInstructionAndUpdateDU(instr);
}
}
// remove trivial assignments
if (Move.conforms(instr)) {
OPT_Register lhs = Move.getResult(instr).asRegister().getRegister();
if (Move.getVal(instr).isRegister()) {
OPT_Register rhs = Move.getVal(instr).asRegister().getRegister();
if (lhs == rhs) {
OPT_DefUse.removeInstructionAndUpdateDU(instr);
continue;
}
}
}
// check that all defs are to dead registers and that
// there is at least 1 def.
boolean isDead = true;
boolean foundRegisterDef = false;
for (OPT_OperandEnumeration defs = instr.getDefs(); defs.hasMoreElements();) {
OPT_Operand def = defs.nextElement();
if (!def.isRegister()) {
isDead = false;
break;
}
foundRegisterDef = true;
OPT_RegisterOperand r = def.asRegister();
if (r.getRegister().useList != null) {
isDead = false;
break;
}
if (r.getRegister().isPhysical()) {
isDead = false;
break;
}
}
if (!isDead) {
continue;
}
if (!foundRegisterDef) {
continue;
}
// There are 1 or more register defs, but all of them are dead.
// Remove instr.
OPT_DefUse.removeInstructionAndUpdateDU(instr);
}
}
/**
* Perform constant folding.
*
* @param ir the IR to optimize
*/
void foldConstants(OPT_IR ir) {
boolean recomputeRegList = false;
for (OPT_Instruction s = ir.firstInstructionInCodeOrder(); s != null; s = s.nextInstructionInCodeOrder()) {
OPT_Simplifier.DefUseEffect code = OPT_Simplifier.simplify(ir.regpool, s);
// If something was reduced (as opposed to folded) then its uses may
// be different. This happens so infrequently that it's cheaper to
// handle it by recomputing the DU from
// scratch rather than trying to do the incremental bookkeeping.
recomputeRegList |=
(code == OPT_Simplifier.DefUseEffect.MOVE_REDUCED ||
code == OPT_Simplifier.DefUseEffect.TRAP_REDUCED ||
code == OPT_Simplifier.DefUseEffect.REDUCED);
}
if (recomputeRegList) {
OPT_DefUse.computeDU(ir);
OPT_DefUse.recomputeSSA(ir);
}
}
/**
* Simplify branches whose operands are constants.
*
* <p> NOTE: This pass ensures that the register list is still valid after it
* is done.
*
* @param ir the IR to optimize
*/
void simplifyConstantBranches(OPT_IR ir) {
boolean didSomething = false;
for (OPT_BasicBlockEnumeration e = ir.forwardBlockEnumerator(); e.hasMoreElements();) {
OPT_BasicBlock bb = e.next();
didSomething |= OPT_BranchSimplifier.simplify(bb, ir);
}
if (didSomething) {
// killed at least one branch, cleanup the CFG removing dead code.
// Then recompute register list and isSSA info
branchOpts.perform(ir, true);
OPT_DefUse.computeDU(ir);
OPT_DefUse.recomputeSSA(ir);
}
}
}