/*
 *  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 org.jikesrvm.compilers.opt.OptOptions;
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.IR;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;

/**
 * Global code placement comes in two flavours. The first is loop
 * invariant code motion (LICM), the second is global common sub
 * expression elimination (GCSE).<p>
 *
 * LICM is applied to HIR and LIR, GCSE only to LIR and before
 * LICM.<p>
 *
 * Both algorithms run on SSA and use the dominator tree to determine
 * positions for operations. That's why these operations are called
 * code placement instead of code motion. <p>
 *
 * There is no code yet to deal with partial redundancies.
 */
public final class GCP extends OptimizationPlanCompositeElement {

  /**
   * Makes sure we are in SSA and have global value numbers at hand.
   * Then execute the transformations.
   */
  public GCP() {
    super("Global Code Placement", new OptimizationPlanElement[]{
        // 1. Set up IR state to control SSA translation as needed
        new OptimizationPlanAtomicElement(new GCPPreparation()),
        // 2. Get the desired SSA form
        new OptimizationPlanAtomicElement(new EnterSSA()),
        // 3. Perform global CSE
        new OptimizationPlanAtomicElement(new GlobalCSE()),
        // 4. Repair SSA
        new OptimizationPlanAtomicElement(new EnterSSA()),
        // 5. Perform Loop Invariant Code Motion
        new OptimizationPlanAtomicElement(new LICM()),
        // 6. Finalize GCP
        new OptimizationPlanAtomicElement(new GCPFinalization())});
  }

  /**
   * Redefine shouldPerform so that none of the subphases will occur
   * unless we pass through this test.
   */
  public boolean shouldPerform(OptOptions options) {
    if (options.getOptLevel() < 2) {
      return false;
    }
    return options.GCP || options.VERBOSE_GCP || options.GCSE;
  }

  static boolean tooBig(IR ir) {
    boolean res = false;
    if (ir.getMaxBasicBlockNumber() > 1000) {
      //VM.sysWrite (ir.method.toString() + " is too large\n");
      res = true;
    }
    return res;
  }

  /**
   * This class sets up the IR state prior to entering SSA for GCP
   */
  private static class GCPPreparation 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;
    }

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

    /**
     * Return the name of the phase
     */
    public final String getName() {
      return "GCP Preparation";
    }

    /**
     * perform the phase
     * @param ir
     */
    public final void perform(IR ir) {
      boolean dont = false;
      //VM.sysWrite("> " + ir.method + "\n");

      if (ir.hasReachableExceptionHandlers()) {
        //VM.sysWrite("has exceptionhandlers\n");
        dont = true;
      }
      if (GCP.tooBig(ir)) {
        dont = true;
      }
      if (dont) {
        ir.options.SSA = false;
        return;
      }
      ir.desiredSSAOptions = new SSAOptions();
      // register in the IR the SSA properties we need for GCP
      if (ir.IRStage == IR.LIR) {
        ir.desiredSSAOptions.setScalarsOnly(true);
        ir.desiredSSAOptions.setBackwards(false);
        ir.desiredSSAOptions.setInsertUsePhis(false);
        ir.desiredSSAOptions.setInsertPEIDeps(false);
        ir.desiredSSAOptions.setHeapTypes(null);
      } else {
        // HIR options
        ir.desiredSSAOptions.setScalarsOnly(false);
        ir.desiredSSAOptions.setBackwards(true);
        ir.desiredSSAOptions.setInsertUsePhis(true);
        ir.desiredSSAOptions.setInsertPEIDeps(!ir.options.LICM_IGNORE_PEI);
        ir.desiredSSAOptions.setHeapTypes(null);
      }
    }
  }

  /**
   * This class sets up the IR state prior to entering SSA for GCP
   */
  private static class GCPFinalization 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;
    }

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

    /**
     * Return the name of the phase
     */
    public final String getName() {
      return "GCP Finalization";
    }

    /**
     * perform the phase
     * @param ir
     */
    public final void perform(IR ir) {
      ir.options.SSA = true;
      //VM.sysWrite("< " + ir.method + "\n");
      // register in the IR the SSA properties GCP preserves
      if (!GCP.tooBig(ir) && !ir.hasReachableExceptionHandlers() && ir.actualSSAOptions != null) {
        if (ir.IRStage == IR.LIR) {
          ir.actualSSAOptions.setScalarsOnly(true);
          ir.actualSSAOptions.setBackwards(false);
          ir.actualSSAOptions.setInsertUsePhis(false);
          ir.actualSSAOptions.setInsertPEIDeps(false);
          ir.actualSSAOptions.setHeapTypes(null);
        } else {
          // HIR options
          ir.actualSSAOptions.setScalarsOnly(false);
          ir.actualSSAOptions.setBackwards(true);
          ir.actualSSAOptions.setInsertUsePhis(true);
          ir.actualSSAOptions.setInsertPEIDeps(true);
          ir.actualSSAOptions.setHeapTypes(null);
        }
      }
    }
  }

  public static boolean usesOrDefsPhysicalRegisterOrAddressType(Instruction inst) {

    for (int i = inst.getNumberOfOperands() - 1; i >= 0; --i) {
      Operand op = inst.getOperand(i);
      if (op instanceof RegisterOperand) {
        if (op.asRegister().getType().isWordType() || op.asRegister().getRegister().isPhysical()) {
          return true;
        }
      }
    }
    return false;
  }
}



