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

import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.DefUse;
import org.jikesrvm.compilers.opt.OptOptions;
import org.jikesrvm.compilers.opt.OptimizingCompilerException;
import org.jikesrvm.compilers.opt.controlflow.BranchOptimizations;
import org.jikesrvm.compilers.opt.controlflow.DominanceFrontier;
import org.jikesrvm.compilers.opt.controlflow.DominatorsPhase;
import org.jikesrvm.compilers.opt.controlflow.LSTGraph;
import org.jikesrvm.compilers.opt.controlflow.LSTNode;
import org.jikesrvm.compilers.opt.driver.CompilerPhase;
import org.jikesrvm.compilers.opt.driver.OptimizationPlanAtomicElement;
import org.jikesrvm.compilers.opt.driver.OptimizationPlanCompositeElement;
import org.jikesrvm.compilers.opt.driver.OptimizationPlanElement;
import org.jikesrvm.compilers.opt.ir.BasicBlock;
import org.jikesrvm.compilers.opt.ir.IR;
import org.jikesrvm.compilers.opt.ir.IRTools;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.ir.Register;
import org.jikesrvm.compilers.opt.ir.RegisterOperandEnumeration;
import org.jikesrvm.compilers.opt.ir.Unary;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.jikesrvm.compilers.opt.liveness.LiveAnalysis;
import org.jikesrvm.compilers.opt.regalloc.CoalesceMoves;
import org.jikesrvm.compilers.opt.util.GraphNode;
import org.jikesrvm.util.BitVector;


/**
 * Perform live-range splitting.
 *
 * <p>This pass splits live ranges where they enter and exit loop bodies
 * by normal (unexceptional) control flow.
 * It splits a live range for register r by inserting the instruction
 * <code> r = SPLIT r </code>.  Then,  SSA renaming will introduce a new
 * name for r.  The SPLIT operator is later turned into a MOVE during
 * BURS.
 *
 * <p>This pass also splits live ranges on edges to and from infrequent code.
 *
 * <p> This composite phase should be performed at the end of SSA in LIR.
 */
public class LiveRangeSplitting extends OptimizationPlanCompositeElement {

  public final boolean shouldPerform(OptOptions options) {
    return options.LIVE_RANGE_SPLITTING;
  }

  /**
   * Build this phase as a composite of others.
   */
  public LiveRangeSplitting() {
    super("LIR SSA Live Range Splitting", new OptimizationPlanElement[]{
        // 0. Clean up the IR
        new OptimizationPlanAtomicElement(new BranchOptimizations(2, true, true)),
        new OptimizationPlanAtomicElement(new CoalesceMoves()),
        // 1. Insert the split operations.
        new OptimizationPlanAtomicElement(new LiveRangeSplittingPhase()),
        new OptimizationPlanAtomicElement(new BranchOptimizations(2, true, true)),
        // 2. Use SSA to rename
        new OptimizationPlanAtomicElement(new DominatorsPhase(true)),
        new OptimizationPlanAtomicElement(new DominanceFrontier()),
        new OptimizationPlanAtomicElement(new RenamePreparation()),
        new OptimizationPlanAtomicElement(new EnterSSA()),
        new OptimizationPlanAtomicElement(new LeaveSSA())});
  }

  private static class LiveRangeSplittingPhase extends CompilerPhase {

    /**
     * Return this instance of this phase. This phase contains no
     * per-compilation instance fields.
     * @param ir not used
     * @return this
     */
    public CompilerPhase newExecution(IR ir) {
      return this;
    }

    public final boolean shouldPerform(OptOptions options) {
      return options.LIVE_RANGE_SPLITTING;
    }

    public final String getName() {
      return "Live Range Splitting";
    }

    /**
     * The main entrypoint for this pass.
     */
    public final void perform(IR ir) {
      // 1. Compute an up-to-date loop structure tree.
      DominatorsPhase dom = new DominatorsPhase(true);
      dom.perform(ir);
      LSTGraph lst = ir.HIRInfo.loopStructureTree;
      if (lst == null) {
        throw new OptimizingCompilerException("null loop structure tree");
      }

      // 2. Compute liveness.
      // YUCK: We will later retrieve the live analysis info, relying on the
      // scratchObject field of the Basic Blocks.  Thus, liveness must be
      // computed AFTER the dominators, since the dominator phase also uses
      // the scratchObject field.
      LiveAnalysis live = new LiveAnalysis(false,  // don't create GC maps
                                                   false,  // don't skip (final) local
                                                   // propagation step of live analysis
                                                   false,  // don't store information at handlers
                                                   true);  // skip guards
      live.perform(ir);

      // 3. Perform the analysis
      DefUse.computeDU(ir);
      HashMap<BasicBlockPair, HashSet<Register>> result = findSplitPoints(ir, live, lst);

      // 4. Perform the transformation.
      transform(ir, result);

      // 5. Record that we've destroyed SSA
      if (ir.actualSSAOptions != null) {
        ir.actualSSAOptions.setScalarValid(false);
        ir.actualSSAOptions.setHeapValid(false);
      }
    }

    /**
     * Find the points the IR where live ranges should be split.
     *
     * @param ir the governing IR
     * @param live valid liveness information
     * @param lst a valid loop structure tree
     * @return the result as a mapping from BasicBlockPair to a set of registers
     */
    private static HashMap<BasicBlockPair, HashSet<Register>> findSplitPoints(IR ir, LiveAnalysis live,
                                                                                  LSTGraph lst) {

      HashMap<BasicBlockPair, HashSet<Register>> result = new HashMap<BasicBlockPair, HashSet<Register>>(10);
      for (Enumeration<GraphNode> e = lst.enumerateNodes(); e.hasMoreElements();) {
        LSTNode node = (LSTNode) e.nextElement();
        BasicBlock header = node.getHeader();
        BitVector loop = node.getLoop();
        if (loop == null) continue;

        // First split live ranges on edges coming into the loop header.
        for (Enumeration<BasicBlock> in = header.getIn(); in.hasMoreElements();) {
          BasicBlock bb = in.nextElement();
          if (loop.get(bb.getNumber())) continue;
          HashSet<Register> liveRegisters = live.getLiveRegistersOnEdge(bb, header);
          for (Register r : liveRegisters) {
            if (r.isSymbolic()) {
              HashSet<Register> s = findOrCreateSplitSet(result, bb, header);
              s.add(r);
            }
          }
        }

        // Next split live ranges on every normal exit from the loop.
        for (int i = 0; i < loop.length(); i++) {
          if (loop.get(i)) {
            BasicBlock bb = ir.getBasicBlock(i);
            for (Enumeration<BasicBlock> out = bb.getNormalOut(); out.hasMoreElements();) {
              BasicBlock dest = out.nextElement();
              if (loop.get(dest.getNumber())) continue;
              HashSet<Register> liveRegisters = live.getLiveRegistersOnEdge(bb, dest);
              for (Register r : liveRegisters) {
                if (r.isSymbolic()) {
                  HashSet<Register> s = findOrCreateSplitSet(result, bb, dest);
                  s.add(r);
                }
              }
            }
          }
        }
      }

      addEntriesForInfrequentBlocks(ir, live, result);

      return result;
    }

    /**
     * Split live ranges on entry and exit to infrequent regions.
     * Add this information to 'result', a mapping from BasicBlockPair to a set of
     * registers to split.
     *
     * @param ir the governing IR
     * @param live valid liveness information
     * @param result mapping from BasicBlockPair to a set of registers
     */
    private static void addEntriesForInfrequentBlocks(IR ir, LiveAnalysis live,
                                                      HashMap<BasicBlockPair, HashSet<Register>> result) {
      for (Enumeration<BasicBlock> e = ir.getBasicBlocks(); e.hasMoreElements();) {
        BasicBlock bb = e.nextElement();
        boolean bbInfrequent = bb.getInfrequent();
        for (Enumeration<BasicBlock> out = bb.getNormalOut(); out.hasMoreElements();) {
          BasicBlock dest = out.nextElement();
          boolean destInfrequent = dest.getInfrequent();
          if (bbInfrequent ^ destInfrequent) {
            HashSet<Register> liveRegisters = live.getLiveRegistersOnEdge(bb, dest);
            for (Register r : liveRegisters) {
              if (r.isSymbolic()) {
                HashSet<Register> s = findOrCreateSplitSet(result, bb, dest);
                s.add(r);
              }
            }
          }
        }
      }
    }

    /**
     * Given a mapping from BasicBlockPair -> HashSet, find or create the hash
     * set corresponding to a given basic block pair
     *
     * @param map the mapping to search
     * @param b1 the first basic block in the pair
     * @param b2 the second basic block in the pair
     */
    private static HashSet<Register> findOrCreateSplitSet(HashMap<BasicBlockPair, HashSet<Register>> map,
                                                              BasicBlock b1, BasicBlock b2) {
      BasicBlockPair pair = new BasicBlockPair(b1, b2);
      HashSet<Register> set = map.get(pair);
      if (set == null) {
        set = new HashSet<Register>(5);
        map.put(pair, set);
      }
      return set;
    }

    /**
     * Perform the transformation
     *
     * @param ir the governing IR
     * @param xform a mapping from BasicBlockPair to the set of registers
     * to split
     */
    private static void transform(IR ir, HashMap<BasicBlockPair, HashSet<Register>> xform) {
      for (Map.Entry<BasicBlockPair, HashSet<Register>> entry : xform.entrySet()) {
        BasicBlockPair bbp = entry.getKey();
        HashSet<Register> toSplit = entry.getValue();

        // we go ahead and split all edges, instead of just critical ones.
        // we'll clean up the mess later after SSA.
        BasicBlock target = IRTools.makeBlockOnEdge(bbp.src, bbp.dest, ir);
        SSA.replaceBlockInPhis(bbp.dest, bbp.src, target);

        for (Register r : toSplit) {
          if (r.defList == null) continue;
          Instruction s = null;
          switch (r.getType()) {
            case Register.ADDRESS_TYPE:
              RegisterOperand lhs2 = IRTools.A(r);
              RegisterOperand rhs2 = IRTools.A(r);
              s = Unary.create(SPLIT, lhs2, rhs2);
              // fix up types: only split live ranges when the type is
              // consistent at all defs
              TypeReference t2 = null;
              RegisterOperandEnumeration e2 = DefUse.defs(r);
              if (!e2.hasMoreElements()) {
                s = null;
              } else {
                RegisterOperand rop2 = e2.next();
                t2 = rop2.getType();
                while (e2.hasMoreElements()) {
                  RegisterOperand nextOp2 = e2.next();
                  if (nextOp2.getType() != t2) {
                    s = null;
                  }
                }
              }
              if (s != null) {
                lhs2.setType(t2);
                rhs2.setType(t2);
              }
              break;
            case Register.INTEGER_TYPE:
              RegisterOperand lhs = IRTools.I(r);
              RegisterOperand rhs = IRTools.I(r);
              s = Unary.create(SPLIT, lhs, rhs);
              // fix up types: only split live ranges when the type is
              // consistent at all defs
              TypeReference t = null;
              RegisterOperandEnumeration e = DefUse.defs(r);
              if (!e.hasMoreElements()) {
                s = null;
              } else {
                RegisterOperand rop = e.next();
                t = rop.getType();
                while (e.hasMoreElements()) {
                  RegisterOperand nextOp = e.next();
                  if (nextOp.getType() != t) {
                    s = null;
                  }
                }
              }
              if (s != null) {
                lhs.setType(t);
                rhs.setType(t);
              }
              break;
            case Register.FLOAT_TYPE:
              s = Unary.create(SPLIT, IRTools.F(r), IRTools.F(r));
              break;
            case Register.DOUBLE_TYPE:
              s = Unary.create(SPLIT, IRTools.D(r), IRTools.D(r));
              break;
            case Register.LONG_TYPE:
              s = Unary.create(SPLIT, IRTools.L(r), IRTools.L(r));
              break;
            default:
              // we won't split live ranges for other types.
              s = null;
              break;
          }
          if (s != null) {
            target.prependInstruction(s);
          }
        }
      }
    }

    /**
     * A utility class to represent an edge in the CFG.
     */
    private static class BasicBlockPair {
      /**
       * The source of a control-flow edge
       */
      final BasicBlock src;

      /**
       * The sink of a control-flow edge
       */
      final BasicBlock dest;

      BasicBlockPair(BasicBlock src, BasicBlock dest) {
        this.src = src;
        this.dest = dest;
      }

      static int nextHash = 0;
      int myHash = ++nextHash;

      public int hashCode() {
        return myHash;
      }

      public boolean equals(Object o) {
        if (!(o instanceof BasicBlockPair)) return false;
        BasicBlockPair p = (BasicBlockPair) o;
        return (src.equals(p.src) && dest.equals(p.dest));
      }

      public String toString() {
        return "<" + src + "," + dest + ">";
      }
    }
  }

  /**
   * This class sets up the IR state prior to entering SSA.
   */
  private static class RenamePreparation extends CompilerPhase {

    /**
     * Return this instance of this phase. This phase contains no
     * per-compilation instance fields.
     * @param ir not used
     * @return this
     */
    public CompilerPhase newExecution(IR ir) {
      return this;
    }

    public final boolean shouldPerform(OptOptions options) {
      return options.LIVE_RANGE_SPLITTING;
    }

    public final String getName() {
      return "Rename Preparation";
    }

    /**
     * register in the IR the SSA properties we need for simple scalar
     * renaming
     */
    public final void perform(IR ir) {
      ir.desiredSSAOptions = new SSAOptions();
      ir.desiredSSAOptions.setScalarsOnly(true);
      ir.desiredSSAOptions.setBackwards(false);
      ir.desiredSSAOptions.setInsertUsePhis(false);
    }
  }
}
