/*
 *  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.ssa;

import static org.jikesrvm.compilers.opt.ir.Operators.*;

import java.lang.reflect.Constructor;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;

import org.jikesrvm.VM;
import org.jikesrvm.compilers.opt.DefUse;
import org.jikesrvm.compilers.opt.OptOptions;
import org.jikesrvm.compilers.opt.controlflow.DominatorInfo;
import org.jikesrvm.compilers.opt.controlflow.DominatorTree;
import org.jikesrvm.compilers.opt.controlflow.Dominators;
import org.jikesrvm.compilers.opt.controlflow.DominatorsPhase;
import org.jikesrvm.compilers.opt.driver.CompilerPhase;
import org.jikesrvm.compilers.opt.ir.AStore;
import org.jikesrvm.compilers.opt.ir.BBend;
import org.jikesrvm.compilers.opt.ir.BasicBlock;
import org.jikesrvm.compilers.opt.ir.BasicBlockEnumeration;
import org.jikesrvm.compilers.opt.ir.ExceptionHandlerBasicBlock;
import org.jikesrvm.compilers.opt.ir.GuardResultCarrier;
import org.jikesrvm.compilers.opt.ir.IR;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.ir.InstructionEnumeration;
import org.jikesrvm.compilers.opt.ir.Label;
import org.jikesrvm.compilers.opt.ir.LocationCarrier;
import org.jikesrvm.compilers.opt.ir.OperandEnumeration;
import org.jikesrvm.compilers.opt.ir.Operator;
import org.jikesrvm.compilers.opt.ir.Phi;
import org.jikesrvm.compilers.opt.ir.PutField;
import org.jikesrvm.compilers.opt.ir.PutStatic;
import org.jikesrvm.compilers.opt.ir.Register;
import org.jikesrvm.compilers.opt.ir.RegisterOperandEnumeration;
import org.jikesrvm.compilers.opt.ir.ResultCarrier;
import org.jikesrvm.compilers.opt.ir.operand.BasicBlockOperand;
import org.jikesrvm.compilers.opt.ir.operand.HeapOperand;
import org.jikesrvm.compilers.opt.ir.operand.LocationOperand;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.jikesrvm.compilers.opt.liveness.LiveAnalysis;
import org.jikesrvm.compilers.opt.util.Queue;

/**
 * This class does the job. It is a subphase of GCP.
 */
public class LICM extends CompilerPhase {
  /** Generate debug output? */
  private static final boolean DEBUG = false;
  /** Generate verbose debug output? */
  private static boolean verbose = false;

  /**
   * Constructor for this compiler phase
   */
  private static final Constructor<CompilerPhase> constructor = getCompilerPhaseConstructor(LICM.class);

  /**
   * Get a constructor object for this compiler phase
   * @return compiler phase constructor
   */
  public Constructor<CompilerPhase> getClassConstructor() {
    return constructor;
  }

  /**
   * Execute loop invariant code motion on the given IR.
   * @param ir
   */
  public void perform(IR ir) {
    this.ir = ir;

    if (DEBUG && ir.hasReachableExceptionHandlers()) {
      VM.sysWrite("] " + ir.method + "\n");
      (new LiveAnalysis(false, false, true, false)).perform(ir);
      BasicBlockEnumeration e = ir.getBasicBlocks();
      while (e.hasMoreElements()) {
        BasicBlock b = e.next();
        if (b instanceof ExceptionHandlerBasicBlock) {
          VM.sysWrite("] " + b + ": " + ((ExceptionHandlerBasicBlock) b).getLiveSet() + "\n");
        }
      }
    }

    if (ir.hasReachableExceptionHandlers() || GCP.tooBig(ir)) {
      resetLandingPads();
      return;
    }

    verbose = ir.options.VERBOSE_GCP;

    if (verbose && ir.options.hasMETHOD_TO_PRINT()) {
      verbose = ir.options.fuzzyMatchMETHOD_TO_PRINT(ir.method.toString());
      if (!verbose) {
        resetLandingPads();
        return;
      }
    }

    if (verbose) VM.sysWrite("] " + ir.method + "\n");
    initialize(ir);
    if (verbose) SSA.printInstructions(ir);

    Instruction inst = ir.firstInstructionInCodeOrder();
    while (inst != null) {
      Instruction next = inst.nextInstructionInCodeOrder();
      if (DEBUG) System.out.println("scheduleEarly: " + inst);
      scheduleEarly(inst);
      inst = next;
    }

    inst = ir.lastInstructionInCodeOrder();
    while (inst != null) {
      Instruction next = inst.prevInstructionInCodeOrder();
      scheduleLate(inst);
      inst = next;
    }
    resetLandingPads();
    if (DEBUG) SSA.printInstructions(ir);
    ir.actualSSAOptions.setScalarValid(false);
  }

  /**
   * Returns the name of the phase
   */
  public String getName() {
    return "LICM";
  }

  /**
   * Should this phase be executed?
   * @param options
   */
  public boolean shouldPerform(OptOptions options) {
    return options.GCP || options.VERBOSE_GCP;
  }

  //------------------------- Implementation -------------------------

  /**
   * Is it save to move the given instruction, depending on we are
   * in heapSSA form or not?
   * @param inst
   * @param ir
   */
  public static boolean shouldMove(Instruction inst, IR ir) {
    if ((inst.isAllocation()) || inst.isDynamicLinkingPoint() || inst.operator.opcode >= ARCH_INDEPENDENT_END_opcode) {
      return false;
    }

    if (ir.IRStage != IR.HIR &&
        ((inst.isPEI()) || inst.isThrow() || inst.isImplicitLoad() || inst.isImplicitStore())) {
      return false;
    }

    switch (inst.operator.opcode) {
      case INT_MOVE_opcode:
      case LONG_MOVE_opcode:
      case INT_COND_MOVE_opcode:
      case LONG_COND_MOVE_opcode:
      case FLOAT_COND_MOVE_opcode:
      case DOUBLE_COND_MOVE_opcode:
      case REF_COND_MOVE_opcode:
      case PUTSTATIC_opcode:
      case PUTFIELD_opcode:
      case GETSTATIC_opcode:
      case GETFIELD_opcode:
      case INT_ALOAD_opcode:
      case LONG_ALOAD_opcode:
      case FLOAT_ALOAD_opcode:
      case DOUBLE_ALOAD_opcode:
      case REF_ALOAD_opcode:
      case BYTE_ALOAD_opcode:
      case UBYTE_ALOAD_opcode:
      case SHORT_ALOAD_opcode:
      case USHORT_ALOAD_opcode:
      case INT_ASTORE_opcode:
      case LONG_ASTORE_opcode:
      case FLOAT_ASTORE_opcode:
      case DOUBLE_ASTORE_opcode:
      case REF_ASTORE_opcode:
      case BYTE_ASTORE_opcode:
      case SHORT_ASTORE_opcode:
      case CHECKCAST_opcode:
      case CHECKCAST_NOTNULL_opcode:
      case CHECKCAST_UNRESOLVED_opcode:
      case MUST_IMPLEMENT_INTERFACE_opcode:
      case INSTANCEOF_opcode:
      case INSTANCEOF_NOTNULL_opcode:
      case INSTANCEOF_UNRESOLVED_opcode:
      case PI_opcode:
      case FLOAT_MOVE_opcode:
      case DOUBLE_MOVE_opcode:
      case REF_MOVE_opcode:
      case GUARD_MOVE_opcode:
      case GUARD_COMBINE_opcode:
      case TRAP_IF_opcode:
      case REF_ADD_opcode:
      case INT_ADD_opcode:
      case LONG_ADD_opcode:
      case FLOAT_ADD_opcode:
      case DOUBLE_ADD_opcode:
      case REF_SUB_opcode:
      case INT_SUB_opcode:
      case LONG_SUB_opcode:
      case FLOAT_SUB_opcode:
      case DOUBLE_SUB_opcode:
      case INT_MUL_opcode:
      case LONG_MUL_opcode:
      case FLOAT_MUL_opcode:
      case DOUBLE_MUL_opcode:
      case INT_DIV_opcode:
      case LONG_DIV_opcode:
      case FLOAT_DIV_opcode:
      case DOUBLE_DIV_opcode:
      case INT_REM_opcode:
      case LONG_REM_opcode:
      case FLOAT_REM_opcode:
      case DOUBLE_REM_opcode:
      case INT_NEG_opcode:
      case LONG_NEG_opcode:
      case FLOAT_NEG_opcode:
      case DOUBLE_NEG_opcode:
      case REF_SHL_opcode:
      case INT_SHL_opcode:
      case LONG_SHL_opcode:
      case REF_SHR_opcode:
      case INT_SHR_opcode:
      case LONG_SHR_opcode:
      case REF_USHR_opcode:
      case INT_USHR_opcode:
      case LONG_USHR_opcode:
      case REF_AND_opcode:
      case INT_AND_opcode:
      case LONG_AND_opcode:
      case REF_OR_opcode:
      case INT_OR_opcode:
      case LONG_OR_opcode:
      case REF_XOR_opcode:
      case INT_XOR_opcode:
      case REF_NOT_opcode:
      case INT_NOT_opcode:
      case LONG_NOT_opcode:
      case LONG_XOR_opcode:
      case INT_2LONG_opcode:
      case INT_2FLOAT_opcode:
      case INT_2DOUBLE_opcode:
      case INT_2ADDRSigExt_opcode:
      case INT_2ADDRZerExt_opcode:
      case LONG_2ADDR_opcode:
      case ADDR_2INT_opcode:
      case ADDR_2LONG_opcode:
      case LONG_2INT_opcode:
      case LONG_2FLOAT_opcode:
      case LONG_2DOUBLE_opcode:
      case FLOAT_2INT_opcode:
      case FLOAT_2LONG_opcode:
      case FLOAT_2DOUBLE_opcode:
      case DOUBLE_2INT_opcode:
      case DOUBLE_2LONG_opcode:
      case DOUBLE_2FLOAT_opcode:
      case INT_2BYTE_opcode:
      case INT_2USHORT_opcode:
      case INT_2SHORT_opcode:
      case LONG_CMP_opcode:
      case FLOAT_CMPL_opcode:
      case FLOAT_CMPG_opcode:
      case DOUBLE_CMPL_opcode:
      case DOUBLE_CMPG_opcode:
      case NULL_CHECK_opcode:
      case BOUNDS_CHECK_opcode:
      case INT_ZERO_CHECK_opcode:
      case LONG_ZERO_CHECK_opcode:
      case OBJARRAY_STORE_CHECK_opcode:
      case OBJARRAY_STORE_CHECK_NOTNULL_opcode:
      case BOOLEAN_NOT_opcode:
      case BOOLEAN_CMP_INT_opcode:
      case BOOLEAN_CMP_ADDR_opcode:
      case FLOAT_AS_INT_BITS_opcode:
      case INT_BITS_AS_FLOAT_opcode:
      case DOUBLE_AS_LONG_BITS_opcode:
      case LONG_BITS_AS_DOUBLE_opcode:
      case ARRAYLENGTH_opcode:
      case GET_OBJ_TIB_opcode:
      case GET_CLASS_TIB_opcode:
      case GET_TYPE_FROM_TIB_opcode:
      case GET_SUPERCLASS_IDS_FROM_TIB_opcode:
      case GET_DOES_IMPLEMENT_FROM_TIB_opcode:
      case GET_ARRAY_ELEMENT_TIB_FROM_TIB_opcode:
        return !(GCP.usesOrDefsPhysicalRegisterOrAddressType(inst));
    }
    return false;
  }

  /**
   * Schedule this instruction as early as possible
   * @param inst
   */
  private Instruction scheduleEarly(Instruction inst) {
    Instruction _earlyPos;

    if (getState(inst) >= early) return getEarlyPos(inst);

    setState(inst, early);
    setEarlyPos(inst, inst);

    // already on outer level?
    //if (ir.HIRInfo.LoopStructureTree.getLoopNestDepth(getBlock(inst)) == 0)
    //  return inst;

    if (ir.options.FREQ_FOCUS_EFFORT && getOrigBlock(inst).getInfrequent()) {
      return inst;
    }

    // explicitly INCLUDE instructions
    if (!shouldMove(inst, ir)) {
      return inst;
    }
    // dependencies via scalar operands
    _earlyPos = scheduleScalarDefsEarly(inst.getUses(), ir.firstInstructionInCodeOrder(), inst);
    if (VM.VerifyAssertions) VM._assert(_earlyPos != null);

    // memory dependencies
    if (ir.IRStage == IR.HIR) {
      _earlyPos = scheduleHeapDefsEarly(ssad.getHeapUses(inst), _earlyPos, inst);
      if (VM.VerifyAssertions) VM._assert(_earlyPos != null);
    }

    /* don't put memory stores or PEIs on speculative path */
    if ((inst.isPEI() && !ir.options.LICM_IGNORE_PEI) || inst.isImplicitStore()) {
      while (!postDominates(getBlock(inst), getBlock(_earlyPos))) {
        _earlyPos = dominanceSuccessor(_earlyPos, inst);
      }
    }

    setEarlyPos(inst, _earlyPos);

    if (DEBUG && getBlock(_earlyPos) != getBlock(inst)) {
      VM.sysWrite("new earlyBlock: " + getBlock(_earlyPos) + " for " + getBlock(inst) + ": " + inst + "\n");
    }

    setBlock(inst, getBlock(_earlyPos));
    return _earlyPos;
  }

  /**
   * Schedule as late as possible.
   * @param inst
   */
  BasicBlock scheduleLate(Instruction inst) {
    if (DEBUG) VM.sysWrite("Schedule Late: " + inst + "\n");

    BasicBlock lateBlock = null;
    int _state = getState(inst);
    if (_state == late || _state == done) return getBlock(inst);

    setState(inst, late);

    if (ir.options.FREQ_FOCUS_EFFORT) {
      BasicBlock _origBlock = getOrigBlock(inst);
      if (_origBlock.getInfrequent()) {
        return _origBlock;
      }
    }

    // explicitly INCLUDE instructions
    if (!shouldMove(inst, ir)) {
      return getOrigBlock(inst);
    }

    // dependencies via scalar operands
    lateBlock = scheduleScalarUsesLate(inst, lateBlock);
    if (DEBUG) VM.sysWrite("lateBlock1: " + lateBlock + " for " + inst + "\n");

    // dependencies via heap operands
    if (ir.IRStage == IR.HIR) {
      lateBlock = scheduleHeapUsesLate(inst, lateBlock);
      if (DEBUG) VM.sysWrite("lateBlock2: " + lateBlock + " for " + inst + "\n");
    }

    // if there are no uses, this instruction is dead.
    if (lateBlock == null) {
      if (verbose) VM.sysWrite("deleting " + inst + "\n");
      inst.remove();
    } else {
      if (DEBUG && lateBlock != getOrigBlock(inst)) {
        VM.sysWrite("new lateBlock: " + lateBlock + " for " + getOrigBlock(inst) + ": " + inst + "\n");
      }

      BasicBlock to = upto(getEarlyPos(inst), lateBlock, inst);
      if (to == null) {
        lateBlock = getOrigBlock(inst);
      } else {
        if (VM.VerifyAssertions) VM._assert(getState(inst) != done);
        lateBlock = to;
        if (getOrigBlock(inst) != to) move(inst, to);
      }
    }
    setState(inst, done);
    setBlock(inst, lateBlock);
    return lateBlock;
  }

  /**
   * return `a's successor on the path from `a' to `b' in the dominator
   * tree. `a' must dominate `b' and `a' and `b' must belong to
   * different blocks.
   */
  private Instruction dominanceSuccessor(Instruction a, Instruction b) {
    BasicBlock aBlock = getBlock(a);
    BasicBlock bBlock = getBlock(b);

    if (VM.VerifyAssertions) {
      VM._assert(aBlock != bBlock && dominator.dominates(aBlock, bBlock));
    }

    BasicBlock last = null;

    while (bBlock != aBlock) {
      last = bBlock;
      bBlock = dominator.getParent(bBlock);
    }
    return last.firstInstruction();
  }

  /**
   * compare a and b according to their depth in the dominator tree
   * and return the one with the greatest depth.
   */
  private Instruction maxDominatorDepth(Instruction a, Instruction b) {
    BasicBlock aBlock = getBlock(a);
    BasicBlock bBlock = getBlock(b);
    int aDomDepth = dominator.depth(aBlock);
    int bDomDepth = dominator.depth(bBlock);

    if (aDomDepth > bDomDepth) return a;
    if (aDomDepth < bDomDepth) return b;

    if (VM.VerifyAssertions) VM._assert(aBlock == bBlock);

    // if an instruction depends on a branch, it can not be placed in
    // this block. Make sure we record this fact. We use this
    // information in upto()
    return a.isBranch() ? a : b;
  }

  private BasicBlock commonDominator(BasicBlock a, BasicBlock b) {
    //VM.sysWrite ("CD: "+a+", "+b);
    if (a == null) return b;
    if (b == null) return a;

    while (a != b) {
      int aDomDepth = dominator.depth(a);
      int bDomDepth = dominator.depth(b);
      if (aDomDepth >= bDomDepth) a = dominator.getParent(a);
      if (bDomDepth >= aDomDepth) b = dominator.getParent(b);
    }
    //VM.sysWrite (" = "+a+"\n");
    return a;
  }

  /**
   * Schedule me as early as possible,
   * but behind the definitions in e and behind earlyPos
   */
  private Instruction scheduleScalarDefsEarly(OperandEnumeration e, Instruction earlyPos,
                                                  Instruction inst) {
    while (e.hasMoreElements()) {
      Operand op = e.next();
      Instruction def = definingInstruction(op);

      scheduleEarly(def);

      if (def.isBranch()) def = dominanceSuccessor(def, inst);

      earlyPos = maxDominatorDepth(def, earlyPos);
    }
    return earlyPos;
  }

  /**
   * Schedule me as early as possible,
   * but behind the definitions of op[i] and behind earlyPos
   */
  Instruction scheduleHeapDefsEarly(HeapOperand<?>[] op, Instruction earlyPos, Instruction me) {
    if (op == null) return earlyPos;

    for (HeapOperand<?> anOp : op) {
      Instruction def = definingInstruction(anOp);

      //  if (me.isImplicitLoad() || me.isImplicitStore())
//      def = _getRealDef(def, me)
//        ;
//        else if (me.isPEI())
//      def = _getRealExceptionDef(def)
      //  ;

      if (VM.VerifyAssertions) VM._assert(def != null);
      earlyPos = maxDominatorDepth(scheduleEarly(def), earlyPos);
    }
    return earlyPos;
  }

  BasicBlock useBlock(Instruction use, Operand op) {
    //VM.sysWrite ("UseBlock: "+use+"\n");
    BasicBlock res = scheduleLate(use);
    if (res != null && Phi.conforms(use)) {
      int i;
      for (i = Phi.getNumberOfValues(use) - 1; i >= 0; --i) {
        if (Phi.getValue(use, i) == op) {
          res = Phi.getPred(use, i).block;
          break;
        }
      }
      if (VM.VerifyAssertions) VM._assert(i >= 0);
    }
    return res;
  }

  /**
   * Schedule me as late as possible,
   * but in front of my uses and before latePos
   */
  private BasicBlock scheduleScalarUsesLate(Instruction inst, BasicBlock lateBlock) {
    Operand resOp = getResult(inst);

    if (resOp == null || !(resOp instanceof RegisterOperand)) {
      return lateBlock;
    }

    Register res = ((RegisterOperand) resOp).getRegister();
    RegisterOperandEnumeration e = DefUse.uses(res);

    while (e.hasMoreElements()) {
      Operand op = e.next();
      Instruction use = op.instruction;
      BasicBlock _block = useBlock(use, op);
      lateBlock = commonDominator(_block, lateBlock);
    }
    return lateBlock;
  }

  /**
   * Schedule me as early as possible,
   * but behind the definitions of op[i] and behind earlyPos
   */
  BasicBlock scheduleHeapUsesLate(Instruction inst, BasicBlock lateBlock) {
    //VM.sysWrite (" scheduleHeapUsesLate\n");
    Operand[] defs = ssad.getHeapDefs(inst);
    if (defs == null) return lateBlock;

    //VM.sysWrite (" defs: "+defs.length+"\n");
    for (Operand def : defs) {
      @SuppressWarnings("unchecked") // Cast to generic HeapOperand
          HeapOperand<Object> dhop = (HeapOperand) def;
      HeapVariable<Object> H = dhop.value;
      if (DEBUG) VM.sysWrite("H: " + H + "\n");
      Iterator<HeapOperand<Object>> it = ssad.iterateHeapUses(H);
      //VM.sysWrite (" H: "+H+" ("+ssad.getNumberOfUses (H)+")\n");
      while (it.hasNext()) {
        HeapOperand<Object> uhop = it.next();
        //VM.sysWrite (" uhop: "+uhop+"\n");
        Instruction use = uhop.instruction;
        //VM.sysWrite ("use: "+use+"\n");
        BasicBlock _block = useBlock(use, uhop);
        lateBlock = commonDominator(_block, lateBlock);
      }
    }
    return lateBlock;
  }

  /**
   * Return the instruction that defines the operand.
   * @param op
   */
  Instruction definingInstruction(Operand op) {
    if (op instanceof HeapOperand) {
      @SuppressWarnings("unchecked") // Cast to generic HeapOperand
          HeapOperand<Object> hop = (HeapOperand) op;
      HeapVariable<Object> H = hop.value;
      HeapOperand<Object> defiOp = ssad.getUniqueDef(H);
      // Variable may be defined by caller, so depends on method entry
      if (defiOp == null || defiOp.instruction == null) {
        return ir.firstInstructionInCodeOrder();
      } else {
        //VM.sysWrite ("def of "+op+" is "+defiOp.instruction+"\n");
        return defiOp.instruction;
      }
    } else if (op instanceof RegisterOperand) {
      Register reg = ((RegisterOperand) op).getRegister();
      RegisterOperandEnumeration defs = DefUse.defs(reg);
      if (!defs.hasMoreElements()) {          // params have no def
        return ir.firstInstructionInCodeOrder();
      } else {
        Instruction def = defs.next().instruction;
        // we are in SSA, so there is at most one definition.
        if (VM.VerifyAssertions) VM._assert(!defs.hasMoreElements());
        //if (defs.hasMoreElements()) {
        //  VM.sysWrite("GCP: multiple defs: " + reg + "\n");
        //  return  null;
        //}
        return def;
      }
    } else {                    // some constant
      return ir.firstInstructionInCodeOrder();
    }
  }

  /**
   * Get the result operand of the instruction
   * @param inst
   */
  Operand getResult(Instruction inst) {
    if (ResultCarrier.conforms(inst)) {
      return ResultCarrier.getResult(inst);
    }
    if (GuardResultCarrier.conforms(inst)) {
      return GuardResultCarrier.getGuardResult(inst);
    }
    if (Phi.conforms(inst)) {
      return Phi.getResult(inst);
    }
    return null;
  }

  /**
   * Visit the blocks between the late and the early position along
   * their path in the dominator tree.
   * Return the block with the smallest execution costs.
   */
  BasicBlock upto(Instruction earlyPos, BasicBlock lateBlock, Instruction inst) {

    BasicBlock _origBlock = getOrigBlock(inst);
    BasicBlock actBlock = lateBlock;
    BasicBlock bestBlock = lateBlock;
    BasicBlock earlyBlock = getBlock(earlyPos);

    if (VM.VerifyAssertions) {
      if (!dominator.dominates(earlyBlock.getNumber(), _origBlock.getNumber()) ||
          !dominator.dominates(earlyBlock.getNumber(), lateBlock.getNumber())) {
        SSA.printInstructions(ir);
        VM.sysWrite("> " +
                    earlyBlock.getNumber() +
                    ", " +
                    _origBlock.getNumber() +
                    ", " +
                    lateBlock.getNumber() +
                    "\n");
        VM.sysWrite("" + inst + "\n");
      }
      VM._assert(dominator.dominates(earlyBlock.getNumber(), _origBlock.getNumber()));
      VM._assert(dominator.dominates(earlyBlock.getNumber(), lateBlock.getNumber()));
    }
    for (; ;) {
      /* is the actual block better (less frequent)
         than the so far best block? */
      if (frequency(actBlock) < frequency(bestBlock)) {
        if (DEBUG) {
          VM.sysWrite("going from " + frequency(_origBlock) + " to " + frequency(actBlock) + "\n");
        }
        bestBlock = actBlock;
      }
      /* all candidates checked? */
      if (actBlock == earlyBlock) {
        break;
      }

      /* walk up the dominator tree for next candidate*/
      actBlock = dominator.getParent(actBlock);
    }
    if (bestBlock == _origBlock) return null;
    if (DEBUG) VM.sysWrite("best Block: " + bestBlock + "\n");
    return bestBlock;
  }

  /**
   * How expensive is it to place an instruction in this block?
   */
  final float frequency(BasicBlock b) {
    return b.getExecutionFrequency();
  }

  /**
   * move `inst' behind `pred'
   */
  void move(Instruction inst, BasicBlock to) {

    BasicBlock _origBlock = getOrigBlock(inst);
    Instruction cand = null;

    /* find a position within bestBlock */
    if (dominator.dominates(_origBlock.getNumber(), to.getNumber())) {
      // moved down, so insert in from
      Instruction last = null;
      InstructionEnumeration e = to.forwardInstrEnumerator();
      while (e.hasMoreElements()) {
        cand = e.next();
        if (DEBUG) VM.sysWrite(cand.toString() + "\n");
        // skip labels, phis, and yieldpoints
        if (!Label.conforms(cand) && !cand.isYieldPoint() && !Phi.conforms(cand)) {
          break;
        }
        last = cand;
      }
      cand = last;
    } else {
      // moved up, so insert at end of block
      InstructionEnumeration e = to.reverseInstrEnumerator();
      while (e.hasMoreElements()) {
        cand = e.next();
        if (DEBUG) VM.sysWrite(cand.toString() + "\n");
        // skip branches and newly placed insts
        if (!BBend.conforms(cand) && !cand.isBranch() && !relocated.contains(cand)) {
          break;
        }
      }
      if (DEBUG) VM.sysWrite("Adding to relocated: " + inst + "\n");
      relocated.add(inst);
    }

    if (DEBUG && moved.add(inst.operator)) {
      VM.sysWrite("m(" + (ir.IRStage == IR.LIR ? "l" : "h") + ") " + inst.operator + "\n");
    }
    if (verbose) {
      VM.sysWrite(ir.IRStage == IR.LIR ? "%" : "#");
      VM.sysWrite(" moving " + inst + " from " + _origBlock + " to " + to + "\n" + "behind  " + cand + "\n");

    }
    inst.remove();
    cand.insertAfter(inst);
  }

  //------------------------------------------------------------
  // some helper methods
  //------------------------------------------------------------

  /**
   * does a post dominate b?
   * @param a
   * @param b
   */
  boolean postDominates(BasicBlock a, BasicBlock b) {
    boolean res;
    if (a == b) {
      return true;
    }
    //VM.sysWrite ("does " + a + " postdominate " + b + "?: ");
    DominatorInfo info = (DominatorInfo) b.scratchObject;
    res = info.isDominatedBy(a);
    //VM.sysWrite (res ? "yes\n" : "no\n");
    return res;
  }

  /**
   * Get the basic block of an instruction
   * @param inst
   */
  BasicBlock getBlock(Instruction inst) {
    return block[inst.scratch];
  }

  /**
   * Set the basic block for an instruction
   * @param inst
   * @param b
   */
  void setBlock(Instruction inst, BasicBlock b) {
    block[inst.scratch] = b;
  }

  /**
   * Get the early position of an instruction
   * @param inst
   */
  Instruction getEarlyPos(Instruction inst) {
    return earlyPos[inst.scratch];
  }

  /**
   * Set the early position for an instruction
   * @param inst
   * @param pos
   */
  void setEarlyPos(Instruction inst, Instruction pos) {
    earlyPos[inst.scratch] = pos;
  }

  /**
   * Get the block, where the instruction was originally located
   * @param inst
   */
  BasicBlock getOrigBlock(Instruction inst) {
    return origBlock[inst.scratch];
  }

  /**
   * Set the block, where the instruction is originally located.
   * @param inst
   * @param b
   */
  void setOrigBlock(Instruction inst, BasicBlock b) {
    origBlock[inst.scratch] = b;
  }

  /**
   * In what state (initial, early, late, done) is this instruction
   * @param inst
   */
  int getState(Instruction inst) {
    return state[inst.scratch];
  }

  /**
   * Set the state (initial, early, late, done) of the instruction
   * @param inst
   * @param s
   */
  void setState(Instruction inst, int s) {
    state[inst.scratch] = s;
  }

  //------------------------------------------------------------
  // initialization
  //------------------------------------------------------------

  /**
   * initialize the state of the algorithm
   */
  void initialize(IR ir) {
    this.ir = ir;

    relocated = new HashSet<Instruction>();
    // Note: the following unfactors the CFG
    new DominatorsPhase(true).perform(ir);
    Dominators.computeApproxPostdominators(ir);
    dominator = ir.HIRInfo.dominatorTree;
    if (DEBUG) VM.sysWrite("" + dominator.toString() + "\n");
    int instructions = ir.numberInstructions();
    ssad = ir.HIRInfo.dictionary;
    DefUse.computeDU(ir);
    ssad.recomputeArrayDU();
    // also number implicit heap phis
    BasicBlockEnumeration e = ir.getBasicBlocks();
    while (e.hasMoreElements()) {
      BasicBlock b = e.next();
      Iterator<Instruction> pe = ssad.getHeapPhiInstructions(b);
      while (pe.hasNext()) {
        Instruction inst = pe.next();
        inst.scratch = instructions++;
        inst.scratchObject = null;
      }
    }
    state = new int[instructions];
    origBlock = new BasicBlock[instructions];
    block = new BasicBlock[instructions];
    earlyPos = new Instruction[instructions];
    e = ir.getBasicBlocks();
    while (e.hasMoreElements()) {
      BasicBlock b = e.next();
      Enumeration<Instruction> ie = ssad.getAllInstructions(b);
      while (ie.hasMoreElements()) {
        Instruction inst = ie.nextElement();
        setBlock(inst, b);
        setOrigBlock(inst, b);
        setState(inst, initial);
      }
    }
    if (ir.IRStage == IR.HIR) {
      e = ir.getBasicBlocks();
      while (e.hasMoreElements()) {
        BasicBlock b = e.next();

        if (ir.options.FREQ_FOCUS_EFFORT && b.getInfrequent()) continue;

        Enumeration<Instruction> ie = ssad.getAllInstructions(b);
        while (ie.hasMoreElements()) {
          Instruction inst = ie.nextElement();
          while (simplify(inst, b)) ;
        }
      }
      ssad.recomputeArrayDU();
    }
  }

  //------------------------------------------------------------
  // private state
  //------------------------------------------------------------
  private static final int initial = 0;
  private static final int early = 1;
  private static final int late = 2;
  private static final int done = 3;

  private HashSet<Instruction> relocated;

  private int[] state;
  private BasicBlock[] block;
  private BasicBlock[] origBlock;
  private Instruction[] earlyPos;
  private SSADictionary ssad;
  private DominatorTree dominator;
  private IR ir;
  private final HashSet<Operator> moved = DEBUG ? new HashSet<Operator>() : null;

  private boolean simplify(Instruction inst, BasicBlock block) {
    if (!Phi.conforms(inst)) return false;  // no phi

    //if (Phi.getNumberOfValues (inst) != 2) return false; // want exactly 2 inputs

    //VM.sysWrite ("Simplify "+inst+"\n");

    Operand resOp = Phi.getResult(inst);

    if (!(resOp instanceof HeapOperand)) {
      //VM.sysWrite (" no heap op result\n");
      return false; // scalar phi
    }

    int xidx = -1;
    Instruction x = null;

    for (int i = Phi.getNumberOfValues(inst) - 1; i >= 0; --i) {
      Instruction in = definingInstruction(Phi.getValue(inst, i));
      if (getOrigBlock(in) != getOrigBlock(inst) && dominator.dominates(getOrigBlock(in), getOrigBlock(inst))) {
        if (xidx != -1) return false;
        xidx = i;
        x = in;
      } else if (!dominator.dominates(getOrigBlock(inst), getOrigBlock(in))) {
        return false;
      }
    }
    if (x == null) return false;

    replaceUses(inst, (HeapOperand<?>) Phi.getValue(inst, xidx), Phi.getPred(inst, xidx), true);

    @SuppressWarnings("unchecked") // Cast to generic HeapOperand
        HeapOperand<Object> hop = (HeapOperand) resOp;
    if (hop.value.isExceptionHeapType()) return false;

    /* check that inside the loop, the heap variable is only used/defed
       by simple, non-volatile loads or only by stores

       if so, replace uses of inst (a memory phi) with its dominating input
    */
    int type = checkLoop(inst, hop, xidx, block);
    if (type == CL_LOADS_ONLY || type == CL_STORES_ONLY || type == CL_NONE) {
      replaceUses(inst, (HeapOperand<?>) Phi.getValue(inst, xidx), Phi.getPred(inst, xidx), false);
    }
    return false;
  }

  static final int CL_NONE = 0;
  static final int CL_LOADS_ONLY = 1;
  static final int CL_STORES_ONLY = 2;
  static final int CL_LOADS_AND_STORES = 3;
  static final int CL_COMPLEX = 4;

  /**
   * check that inside the loop, the heap variable is only used/defed
   * by simple, non-volatile loads/stores
   *
   * returns one of:
   * CL_LOADS_ONLY, CL_STORES_ONLY, CL_LOADS_AND_STORES, CL_COMPLEX
   */
  @SuppressWarnings("unused")
  // useful for debugging
  private int _checkLoop(Instruction inst, HeapOperand<?> hop, int xidx) {
    for (int i = Phi.getNumberOfValues(inst) - 1; i >= 0; --i) {
      if (i == xidx) continue;
      Instruction y = definingInstruction(Phi.getValue(inst, i));
      while (y != inst) {
        //VM.sysWrite (" y: "+y+"\n");
        if (y.isImplicitStore() || y.isPEI() || !LocationCarrier.conforms(y)) {
          return CL_COMPLEX;
        }

        // check for access to volatile field
        LocationOperand loc = LocationCarrier.getLocation(y);
        if (loc == null || loc.mayBeVolatile()) {
          //VM.sysWrite (" no loc or volatile field\n");
          return CL_COMPLEX;
        }
        for (HeapOperand<?> op : ssad.getHeapUses(y)) {
          if (op.value.isExceptionHeapType()) continue;
          if (op.getHeapType() != hop.getHeapType()) return CL_COMPLEX;
          y = definingInstruction(op);
        }
      }
    }
    return CL_LOADS_ONLY;
  }

  /**
   * check that inside the loop, the heap variable is only used/defed
   * by simple, non-volatile loads/stores
   *
   * returns one of:
   * CL_LOADS_ONLY, CL_STORES_ONLY, CL_LOADS_AND_STORES, CL_COMPLEX
   */
  private int checkLoop(Instruction inst, HeapOperand<Object> hop, int xidx, BasicBlock block) {
    HashSet<Instruction> seen = new HashSet<Instruction>();
    Queue<Instruction> workList = new Queue<Instruction>();
    int _state = CL_NONE;
    int instUses = 0;

    seen.add(inst);
    for (int i = Phi.getNumberOfValues(inst) - 1; i >= 0; --i) {
      if (i == xidx) continue;
      Instruction y = definingInstruction(Phi.getValue(inst, i));
      if (y == inst) instUses++;
      if (!(seen.contains(y))) {
        seen.add(y);
        workList.insert(y);
      }
    }

    while (!(workList.isEmpty())) {
      Instruction y = workList.remove();
      if (Phi.conforms(y)) {
        for (int i = Phi.getNumberOfValues(y) - 1; i >= 0; --i) {
          Instruction z = definingInstruction(Phi.getValue(y, i));
          if (z == inst) instUses++;
          if (!(seen.contains(z))) {
            seen.add(z);
            workList.insert(z);
          }
        }
      } else if ((y.isPEI()) || !LocationCarrier.conforms(y) || y.operator.isAcquire() || y.operator.isRelease()) {
        return CL_COMPLEX;
      } else {
        // check for access to volatile field
        LocationOperand loc = LocationCarrier.getLocation(y);
        if (loc == null || loc.mayBeVolatile()) {
          //VM.sysWrite (" no loc or volatile field\n");
          return CL_COMPLEX;
        }
        if (y.isImplicitStore()) {
          // only accept loop-invariant stores
          // conservatively estimate loop-invariance by header domination
          if (!inVariantLocation(y, block)) return CL_COMPLEX;
          _state |= CL_STORES_ONLY;
        } else {
          _state |= CL_LOADS_ONLY;
        }
        for (HeapOperand<?> op : ssad.getHeapUses(y)) {
          if (op.value.isExceptionHeapType()) continue;
          if (op.getHeapType() != hop.getHeapType()) return CL_COMPLEX;
          y = definingInstruction(op);
          if (y == inst) instUses++;
          if (!(seen.contains(y))) {
            seen.add(y);
            workList.insert(y);
          }
        }
      }
    }
    if (_state == CL_STORES_ONLY && ssad.getNumberOfUses(hop.value) != instUses) {
      return CL_COMPLEX;
    }

    return _state;
  }

  private boolean inVariantLocation(Instruction inst, BasicBlock block) {
    if (PutStatic.conforms(inst)) return true;
    if (PutField.conforms(inst)) {
      return useDominates(PutField.getRef(inst), block);
    }
    if (AStore.conforms(inst)) {
      return ((useDominates(AStore.getArray(inst), block)) && useDominates(AStore.getIndex(inst), block));
    }
    if (VM.VerifyAssertions) {
      VM._assert(false, "inst: " + inst);
    }
    return false;
  }

  private boolean useDominates(Operand op, BasicBlock block) {
    if (!(op instanceof RegisterOperand)) return true;
    Instruction inst = definingInstruction(op);
    BasicBlock b = getOrigBlock(inst);
    return b != block && dominator.dominates(b, block);
  }

  /**
   * In the consumers of `inst', replace uses of `inst's result
   * with uses of `replacement'
   */
  private boolean replaceUses(Instruction inst, HeapOperand<?> replacement,
                              BasicBlockOperand replacementBlock, boolean onlyPEIs) {
    if (VM.VerifyAssertions) VM._assert(Phi.conforms(inst));

    boolean changed = false;
    @SuppressWarnings("unchecked") // Cast to generic HeapOperand
        HeapOperand<Object> hop = (HeapOperand) Phi.getResult(inst);
    HeapVariable<Object> H = hop.value;
    Iterator<HeapOperand<Object>> it = ssad.iterateHeapUses(H);
    while (it.hasNext()) {
      hop = it.next();
      Instruction user = hop.instruction;
      if (onlyPEIs && !user.isPEI()) continue;

      if (Phi.conforms(user)) {
        for (int i = 0; i < Phi.getNumberOfValues(user); i++) {
          if (Phi.getValue(user, i) == hop) {
            Phi.setValue(user, i, replacement.copy());
            Phi.setPred(user, i, (BasicBlockOperand) replacementBlock.copy());
          }
        }
        changed |= replacement.value != H;
      } else {
        HeapOperand<?>[] uses = ssad.getHeapUses(user);
        for (int i = uses.length - 1; i >= 0; --i) {
          if (uses[i].value == H) {
            changed |= replacement.value != H;
            uses[i] = replacement.copy();
            uses[i].setInstruction(user);
          }
        }
      }
      if (DEBUG && changed) {
        VM.sysWrite(" changing dependency of " + user + "\n" + "from " + H + " to " + replacement + "\n");
      }
    }
    if (!onlyPEIs) {
      for (int i = Phi.getNumberOfValues(inst) - 1; i >= 0; --i) {
        Phi.setValue(inst, i, replacement.copy());
      }
    }
    return changed;
  }

  private void resetLandingPads() {
    BasicBlockEnumeration e = ir.getBasicBlocks();
    while (e.hasMoreElements()) e.next().clearLandingPad();
  }
}
