/*
 *  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 java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;
import org.jikesrvm.VM;
import org.jikesrvm.compilers.opt.ir.IR;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.ir.operand.ConstantOperand;
import org.jikesrvm.compilers.opt.util.GraphNode;

/**
 * This class holds the results of global value numbering.
 * ala Alpern, Wegman and Zadeck, PoPL 88.
 * See Muchnick p.348 for a discussion (which is quite buggy, should
 * have stuck to the dragon book, which gives a high-level description of
 * the correct algorithm on page 143).
 */
public final class GlobalValueNumberState {
  /**
   * Constant used to flag "unknown" value numbers
   */
  public static final int UNKNOWN = -1;
  /**
   * Print verbose debugging output?
   */
  private static final boolean DEBUG = false;
  /**
   * Assume parameters are not aliased?
   */
  private static final boolean NO_PARAM_ALIAS = false;

  /**
   * ArrayList of GVCongruenceClass, indexed by value number.
   */
  private final ArrayList<GVCongruenceClass> B;
  /**
   * The value graph.
   */
  final ValueGraph valueGraph;
  /**
   * Stack used for a work list.
   */
  private final Stack<GVCongruenceClass> workList;

  /**
   * Construct a structure to hold global value number results for an IR.
   *
   * @param ir governing IR
   */
  GlobalValueNumberState(IR ir) {
    B = new ArrayList<GVCongruenceClass>();
    workList = new Stack<GVCongruenceClass>();
    valueGraph = new ValueGraph(ir);
    globalValueNumber();
  }

  /**
   * Compute node congruence over the value number graph.
   *
   * <p> Algorithm: Muchnick pp. 348-355
   * <p> Note: the Muchnick algorithm is buggy.  In particular, it
   * does not put all needed congruence classes on the worklist.
   *
   * <p> Two nodes in the value graph are congruent if one of the
   * following holds:
   * <ul>
   *  <li> they are the same node
   *  <li>  their labels are identical constants
   *  <li>  they have the same operators and their operands are
   *      congruent
   * </ul>
   *
   *  <p> Optimistic algorithm:
   *  <ul>
   *    <li> Initially assume all nodes with the same label are congruent
   *    <li> start a work list with all congruence classes that
   *       have multiple operands
   *    <li> choose a congruence class from the worklist.  partition its
   *       elements into new congruence classes if we can discover that
   *       they are not congruent.
   *     <li>  Add any newly created congruence classes to the work list.
   *     <li> (Here's the step Muchnick omits:)
   *     For each class C which has a dependence on any of the newly
   *       created congruence classes, add C to the work list
   *     <li> repeat until work list is empty
   *   </ul>
   *
   *  <p> The following method breaks Muchnick's algorithm, which will
   *  assign m and n the same value number. Muchnick's problem is that
   *  it does not put the congruence class for 'int_mul' back on the worklist
   *  when we discover, for example, that i is not congruent to k
   *  <pre>
   *   public int foo(int a, int b, int c, int d, int e, int f, int g, int h) {
   *      int i = a+b;
   *      int j = c+d;
   *      int k = e+f;
   *      int l = g+h;
   *      int m = i * j;
   *      int n = k * l;
   *      int o = m/n;
   *      return o;
   *   }
   *  </pre>
   */
  private void globalValueNumber() {
    if (DEBUG) {
      VM.sysWrite(valueGraph.toString());
    }
    // initialize the congurence classes
    initialize();
    // initialize the work list
    initializeWorkList();
    // drain the work list
    while (!workList.empty()) {
      GVCongruenceClass partition = workList.pop();
      partitionClass(partition);
    }
    // all done
    if (DEBUG) {
      printValueNumbers();
    }
  }

  /**
   * Merge the congruence classes containing vertices v1 and v2.;
   */
  void mergeClasses(ValueGraphVertex v1, ValueGraphVertex v2) {
    if (DEBUG) {
      System.out.println("@@@@ mergeClasses called with v1 = " + v1 + " ; v2 = " + v2);
    }

    int val1 = v1.getValueNumber();
    int val2 = v2.getValueNumber();
    if (val1 == val2) return;

    GVCongruenceClass class1 = B.get(val1);

    while (true) {
      GVCongruenceClass class2 = B.get(val2);
      Iterator<ValueGraphVertex> i = class2.iterator();
      if (!i.hasNext()) break;
      ValueGraphVertex v = i.next();
      if (DEBUG) {
        System.out.println("@@@@ moving vertex " + v + " from class " + val2 + " to class " + val1);
      }
      class1.addVertex(v);
      class2.removeVertex(v);
      v.setValueNumber(val1);
    }

    // Null out entry for val2
    B.set(val2, null);
  }

  /**
   * Definitely-same relation.
   * @param name1 first variable
   * @param name2 second variable
   * @return true iff the value numbers for two variables are equal
   */
  boolean DS(Object name1, Object name2) {
    ValueGraphVertex v1 = valueGraph.getVertex(name1);
    ValueGraphVertex v2 = valueGraph.getVertex(name2);
    return v1.getValueNumber() == v2.getValueNumber();
  }

  /**
   * Definitely-different relation for two value numbers.
   * Returns true for the following cases:
   * <ul>
   *  <li> v1 and v2 are both constants, but don't match
   *  <li> v1 and v2 both result from NEW statements, but don't match
   *  <li> one of v1 and v2 is a parameter, and the other results from a
   *      new statement
   * </ul>
   * <p> TODO: add more smarts
   * @param v1 first value number
   * @param v2 second value number
   * @return true iff the value numbers for two variables are definitely
   * different
   */
  boolean DD(int v1, int v2) {
    if ((v1 == -1) || (v2 == -1)) {
      return false;
    }
    GVCongruenceClass class1 = B.get(v1);
    GVCongruenceClass class2 = B.get(v2);
    Object label1 = class1.getLabel();
    Object label2 = class2.getLabel();
    // if one is a constant, they must both be ...
    if (isConstant(label1) && !isConstant(label2)) {
      return false;
    }
    if (!isConstant(label1) && isConstant(label2)) {
      return false;
    }
    if (isConstant(label1)) {
      return (v1 != v2);
    }
    // handle DD for allocations
    if (isBornAtAllocation(label1)) {
      if (isBornAtAllocation(label2)) {
        // both are NEW.  Are they dd?
        return (v1 != v2);
      } else if (class2.containsParameter()) {
        // one is NEW, other is parameter. They are DD.
        return true;
      }
    } else {
      if (isBornAtAllocation(label2)) {
        if (class1.containsParameter()) {
          // one is NEW, other is parameter. They are DD.
          return true;
        }
      }
    }
    // assume parameters are not aliased?
    if (NO_PARAM_ALIAS) {
      if (v1 != v2) {
        if (class1.containsParameter()) {
          if (class2.containsParameter()) {
            return true;
          }
        }
      }
    }

    // if we haven't figured out they're DD, return false;
    return false;
  }

  /**
   * Definitely-different relation.
   * Returns true for the following cases:
   * <ul>
   *  <li> name1 and name2 are both constants, but don't match
   *  <li> name1 and name2 both result from NEW statements, but don't match
   *  <li> one of name1 and name2 is a parameter, and the other results from a
   *      new statement
   * </ul>
   * <p> TODO: add more smarts
   * @param name1 name of first object to compare
   * @param name2 name of second object to compare
   * @return true iff the value numbers for two variables are definitely
   * different
   */
  boolean DD(Object name1, Object name2) {
    ValueGraphVertex v1 = valueGraph.getVertex(name1);
    ValueGraphVertex v2 = valueGraph.getVertex(name2);
    return DD(v1.getValueNumber(), v2.getValueNumber());
  }

  GVCongruenceClass congruenceClass(Object name) {
    ValueGraphVertex v = valueGraph.getVertex(name);
    return B.get(v.getValueNumber());
  }

  /**
   * Return the (integer) value number for a given variable
   *
   * @param name name of the variable to look up
   * @return its value number
   */
  int getValueNumber(Object name) {
    ValueGraphVertex v = valueGraph.getVertex(name);
    if (v == null) {
      return UNKNOWN;
    }
    return v.getValueNumber();
  }

  /**
   * Print the value numbers for each node in the value graph.
   */
  void printValueNumbers() {
    for (Enumeration<GraphNode> e = valueGraph.enumerateVertices(); e.hasMoreElements();) {
      ValueGraphVertex v = (ValueGraphVertex) e.nextElement();
      int valueNumber = v.getValueNumber();
      GVCongruenceClass c = B.get(valueNumber);
      System.out.println(v.getName() + " " + valueNumber + " " + c.getLabel());
    }
  }

  /**
   * Initialize the congruence classes, assuming that all nodes
   * with the same label are congruent.
   */
  private void initialize() {
    // store a map from label -> congruenceClass
    HashMap<Object, GVCongruenceClass> labelMap = new HashMap<Object, GVCongruenceClass>(10);
    for (Enumeration<GraphNode> e = valueGraph.enumerateVertices(); e.hasMoreElements();) {
      ValueGraphVertex v = (ValueGraphVertex) e.nextElement();
      Object label = v.getLabel();
      GVCongruenceClass c = findOrCreateCongruenceClass(label, labelMap);
      // add this node to the congruence class
      c.addVertex(v);
      // set the value number for the node
      v.setValueNumber(c.getValueNumber());
    }
  }

  /**
   * Given a label, return the congruence class for that label.
   * Create one if needed.
   *
   * @param label the label of a congruence class
   * @param labelMap a mapping from labels to congruence class
   * @return the congruence class for the label.
   */
  private GVCongruenceClass findOrCreateCongruenceClass(Object label,
                                                            HashMap<Object, GVCongruenceClass> labelMap) {
    GVCongruenceClass result = labelMap.get(label);
    if ((result == null) || (label == null)) {
      result = createCongruenceClass(label);
      labelMap.put(label, result);
    }
    return result;
  }

  /**
   * Given a label, return a new congruence class for that label.
   * @param label the label of a congruence class
   * @return the congruence class for the label.
   */
  private GVCongruenceClass createCongruenceClass(Object label) {
    // create a new congruence class, and update data structures
    int index = B.size();
    GVCongruenceClass result = new GVCongruenceClass(index, label);
    B.add(result);
    return result;
  }

  /**
   * Initialize the work list.
   * A congruence class gets put on the work list if any two nodes
   * in the class point to corresponding targets in separate partitions.
   */
  private void initializeWorkList() {
    for (GVCongruenceClass c : B) {
      if (c.size() == 1) {
        continue;
      }
      // store a reference to the first node in c
      Iterator<ValueGraphVertex> i = c.iterator();
      ValueGraphVertex first = i.next();
      // now check that each other target matches the first element
      // if not, add this class to the work list
      //
      while (i.hasNext()) {
        ValueGraphVertex v = i.next();
        if (!checkCongruence(first, v)) {
          workList.push(c);
          break;
        }
      }
    }
  }

  /**
   * Partition a congruence class.
   * @param partition the class to partition
   */
  private void partitionClass(GVCongruenceClass partition) {
    // store a reference to the first node in c, which will serve
    // as a representative for this class
    Iterator<ValueGraphVertex> i = partition.iterator();
    ValueGraphVertex first = i.next();
    ArrayList<GVCongruenceClass> newClasses = new ArrayList<GVCongruenceClass>();
    // now check each other node in c, to see if it matches the
    // representative
    ArrayList<ValueGraphVertex> toRemove = new ArrayList<ValueGraphVertex>();
    while (i.hasNext()) {
      ValueGraphVertex v = i.next();
      if (!checkCongruence(first, v)) {
        // NOT CONGRUENT!!  split the partition.  first check if
        // v fits in any other newly created congruence classes
        int index = findCongruenceMatch(newClasses, v);
        if (index > -1) {
          // MATCH FOUND!! place v in newClasses[index]
          GVCongruenceClass match = B.get(index);
          match.addVertex(v);
          v.setValueNumber(match.getValueNumber());
        } else {
          // NO MATCH FOUND!! create a new congruence class
          // find the appropriate label for the new congruence class
          // and create a new congruence class with this label
          GVCongruenceClass c = createCongruenceClass(v);
          newClasses.add(c);
          c.addVertex(v);
          v.setValueNumber(c.getValueNumber());
        }
        // mark v as to be removed from partition
        // (Can't remove it yet while iterating over the set);
        toRemove.add(v);
      }
    }
    // remove necessary vertices
    for (ValueGraphVertex v : toRemove) {
      partition.removeVertex(v);
    }
    // if needed place the original partition back on the work list
    if ((!newClasses.isEmpty()) && (partition.size() > 1)) {
      workList.push(partition);
    }
    // place any new congruence classes with size > 1 on the worklist
    // also place any classes which might indirectly be affected
    for (GVCongruenceClass c : newClasses) {
      if (c.size() > 1) {
        workList.push(c);
      }
      addDependentClassesToWorklist(c);
    }
  }

  /**
   * Assuming congruence class c has changed: find all other classes
   * that might be affected, and add them to the worklist
   * @param c the congruence class that has changed
   */
  private void addDependentClassesToWorklist(GVCongruenceClass c) {
    // for each element of this congruence class:
    for (ValueGraphVertex v : c) {
      // for each vertex which points to v in the value graph
      for (Enumeration<GraphNode> e = v.inNodes(); e.hasMoreElements();) {
        ValueGraphVertex in = (ValueGraphVertex) e.nextElement();
        int vn = in.getValueNumber();
        GVCongruenceClass x = B.get(vn);
        workList.push(x);
      }
    }
  }

  /**
   * Does vertex v belong to any congruence class in a vector?
   * If so, returns the value number of the matching congruence class.
   * If none found, returns -1.
   * @param vector a vector of congruence classes
   * @param v the vertex to search for
   * @return the value number corresponding to the congruence class
   * containing v.  -1 iff no such class is found.
   */
  private int findCongruenceMatch(ArrayList<GVCongruenceClass> vector, ValueGraphVertex v) {
    for (GVCongruenceClass klass : vector) {
      if (checkCongruence(v, klass)) {
        return klass.getValueNumber();
      }
    }
    return -1;
  }

  /**
   * Does the current state of the algorithm optimistically assume
   * that a vertex v is congruent to the vertices in a congruence
   * class? Note: this can return true even if
   * if the value numbers don't currently match.
   * @param v the vertex in question
   * @param c the congurence class to check
   * @return true or false
   */
  private boolean checkCongruence(ValueGraphVertex v, GVCongruenceClass c) {
    ValueGraphVertex r = c.getRepresentative();
    boolean result = checkCongruence(r, v);
    return result;
  }

  /**
   * Does the current state of the algorithm optimistically assume
   * that two nodes are congruent? Note: this can return false
   * even if the value numbers are currently the same.
   * @param v1 first vertex
   * @param v2 second vertex
   */
  private boolean checkCongruence(ValueGraphVertex v1, ValueGraphVertex v2) {
    if (v1 == v2) {
      return true;
    }
    // make sure the two nodes have the same label
    if (v1.getLabel() != v2.getLabel()) {
      return false;
    }
    // make sure that the operands match
    int arity = v1.getArity();
    for (int i = 0; i < arity; i++) {
      ValueGraphVertex target1 = v1.getTarget(i);
      ValueGraphVertex target2 = v2.getTarget(i);
      // if either target is null, then that particular control
      // flow path is never realized, so assume TOP
      if ((target1 == null) || (target2 == null)) {
        continue;
      }
      if (target1.getValueNumber() != target2.getValueNumber()) {
        return false;
      }
    }
    return true;
  }

  /**
   * Does a given label indicate that the object has a constant value?
   */
  private static boolean isConstant(Object label) {
    return (label instanceof ConstantOperand);
  }

  /**
   * Does a given label indicate that the object is created at an
   * allocation site?
   */
  private static boolean isBornAtAllocation(Object label) {
    return (label instanceof Instruction);
  }
}
