/*******************************************************************************
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package com.ibm.wala.dataflow.ssa;

import java.util.Iterator;

import com.ibm.wala.analysis.typeInference.TypeInference;
import com.ibm.wala.fixedpoint.impl.DefaultFixedPointSolver;
import com.ibm.wala.fixedpoint.impl.NullaryOperator;
import com.ibm.wala.fixpoint.AbstractOperator;
import com.ibm.wala.fixpoint.IVariable;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SymbolTable;

/**
 * This class performs intra-procedural propagation over an SSA form.
 * 
 * A client will subclass an {@link SSAInference} by providing factories that generate {@link IVariable}s corresponding to SSA value
 * numbers, and {@link AbstractOperator}s corresponding to SSA instructions. This class will set up a dataflow system induced by the
 * SSA def-use graph, and solve the system by iterating to a fixed point.
 * 
 * @see TypeInference for the canonical client of this machinery.
 */
public abstract class SSAInference<T extends IVariable<?>> extends DefaultFixedPointSolver<T> {
  static final boolean DEBUG = false;

  /**
   * The governing SSA form
   */
  private IR ir;

  /**
   * The governing symbol table
   */
  private SymbolTable symbolTable;

  /**
   * Dataflow variables, one for each value in the symbol table.
   */
  private IVariable[] vars;

  public interface OperatorFactory<T extends IVariable> {
    /**
     * Get the dataflow operator induced by an instruction in SSA form.
     * 
     * @param instruction
     * @return dataflow operator for the instruction, or null if the instruction is not applicable to the dataflow system.
     */
    AbstractOperator<T> get(SSAInstruction instruction);
  }

  public interface VariableFactory {
    /**
     * Make the variable for a given value number.
     * 
     * @return a newly created dataflow variable, or null if not applicable.
     */
    public IVariable makeVariable(int valueNumber);
  }

  /**
   * initializer for SSA Inference equations.
   */
  protected void init(IR ir, VariableFactory varFactory, OperatorFactory<T> opFactory) {

    this.ir = ir;
    this.symbolTable = ir.getSymbolTable();

    createVariables(varFactory);
    createEquations(opFactory);
  }

  private void createEquations(OperatorFactory<T> opFactory) {
    SSAInstruction[] instructions = ir.getInstructions();
    for (int i = 0; i < instructions.length; i++) {
      SSAInstruction s = instructions[i];
      makeEquationForInstruction(opFactory, s);
    }
    for (Iterator it = ir.iteratePhis(); it.hasNext();) {
      SSAInstruction s = (SSAInstruction) it.next();
      makeEquationForInstruction(opFactory, s);
    }
    for (Iterator it = ir.iteratePis(); it.hasNext();) {
      SSAInstruction s = (SSAInstruction) it.next();
      makeEquationForInstruction(opFactory, s);
    }
    for (Iterator it = ir.iterateCatchInstructions(); it.hasNext();) {
      SSAInstruction s = (SSAInstruction) it.next();
      makeEquationForInstruction(opFactory, s);
    }
  }

  /**
   * Create a dataflow equation induced by a given instruction
   */
  private void makeEquationForInstruction(OperatorFactory<T> opFactory, SSAInstruction s) {
    if (s != null && s.hasDef()) {
      AbstractOperator<T> op = opFactory.get(s);
      if (op != null) {
        T def = getVariable(s.getDef());
        if (op instanceof NullaryOperator) {
          newStatement(def, (NullaryOperator<T>) op, false, false);
        } else {
          int n = s.getNumberOfUses();
          T[] uses = makeStmtRHS(n);
          for (int j = 0; j < n; j++) {
            if (s.getUse(j) > -1) {
              uses[j] = getVariable(s.getUse(j));
              assert uses[j] != null;
            }
          }
          newStatement(def, op, uses, false, false);
        }
      }
    }
  }

  /**
   * Create a dataflow variable for each value number
   */
  private void createVariables(VariableFactory factory) {
    vars = new IVariable[symbolTable.getMaxValueNumber() + 1];
    for (int i = 1; i < vars.length; i++) {
      vars[i] = factory.makeVariable(i);
    }

  }

  /**
   * @return the dataflow variable representing the value number, or null if none found.
   */
  @SuppressWarnings("unchecked")
  protected T getVariable(int valueNumber) {
    if (valueNumber < 0) {
      throw new IllegalArgumentException("Illegal valueNumber " + valueNumber);
    }
    if (DEBUG) {
      System.err.println(("getVariable for " + valueNumber + " returns " + vars[valueNumber]));
    }
    assert vars != null : "null vars array";
    return (T) vars[valueNumber];
  }

  /**
   * Return a string representation of the system
   * 
   * @return a string representation of the system
   */
  @Override
  public String toString() {
    StringBuffer result = new StringBuffer("Type inference : \n");
    for (int i = 0; i < vars.length; i++) {
      result.append("v").append(i).append("  ").append(vars[i]).append("\n");
    }
    return result.toString();
  }
}
