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

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

import java.util.Enumeration;

import org.jikesrvm.VM;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.ir.BasicBlock;
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.OperandEnumeration;
import org.jikesrvm.compilers.opt.ir.Register;
import org.jikesrvm.compilers.opt.ir.RegisterOperandEnumeration;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.vmmagic.pragma.NoInline;

/**
 * This class computes du-lists and associated information.
 *
 * <P> Note: DU operands are stored on the USE lists, but not the DEF
 * lists.
 */
public final class DefUse {
  static final boolean DEBUG = false;
  static final boolean TRACE_DU_ACTIONS = false;
  static final boolean SUPRESS_DU_FOR_PHYSICALS = true;

  /**
   * Clear defList, useList for an IR.
   *
   * @param ir the IR in question
   */
  public static void clearDU(IR ir) {
    for (Register reg = ir.regpool.getFirstSymbolicRegister(); reg != null; reg = reg.getNext()) {
      reg.defList = null;
      reg.useList = null;
      reg.scratch = -1;
      reg.clearSeenUse();
    }
    for (Enumeration<Register> e = ir.regpool.getPhysicalRegisterSet().enumerateAll(); e.hasMoreElements();) {
      Register reg = e.nextElement();
      reg.defList = null;
      reg.useList = null;
      reg.scratch = -1;
      reg.clearSeenUse();
    }

    if (TRACE_DU_ACTIONS || DEBUG) {
      VM.sysWrite("Cleared DU\n");
    }
  }

  /**
   * Compute the register list and def-use lists for a method.
   *
   * @param ir the IR in question
   */
  @NoInline
  public static void computeDU(IR ir) {
    // Clear old register list (if any)
    clearDU(ir);
    // Create register defList and useList
    for (Instruction instr = ir.firstInstructionInCodeOrder(); instr != null; instr =
        instr.nextInstructionInCodeOrder()) {

      OperandEnumeration defs = instr.getPureDefs();
      OperandEnumeration uses = instr.getUses();

      while (defs.hasMoreElements()) {
        Operand op = defs.next();
        if (op instanceof RegisterOperand) {
          RegisterOperand rop = (RegisterOperand) op;
          recordDef(rop);
        }
      }         // for ( defs = ... )

      while (uses.hasMoreElements()) {
        Operand op = uses.next();
        if (op instanceof RegisterOperand) {
          RegisterOperand rop = (RegisterOperand) op;
          recordUse(rop);
        }
      }         // for ( uses = ... )
    }           // for ( instr = ... )
    // Remove any symbloic registers with no uses/defs from
    // the register pool.  We'll waste analysis time keeping them around.
    Register next;
    for (Register reg = ir.regpool.getFirstSymbolicRegister(); reg != null; reg = next) {
      next = reg.getNext();
      if (reg.defList == null && reg.useList == null) {
        if (DEBUG) {
          VM.sysWrite("Removing " + reg + " from the register pool\n");
        }
        ir.regpool.removeRegister(reg);
      }
    }
  }

  /**
   * Record a use of a register
   * @param regOp the operand that uses the register
   */
  public static void recordUse(RegisterOperand regOp) {
    Register reg = regOp.getRegister();
    regOp.append(reg.useList);
    reg.useList = regOp;
    reg.useCount++;
  }

  /**
   * Record a def/use of a register
   * TODO: For now we just pretend this is a use!!!!
   *
   * @param regOp the operand that uses the register
   */
  public static void recordDefUse(RegisterOperand regOp) {
    Register reg = regOp.getRegister();
    if (SUPRESS_DU_FOR_PHYSICALS && reg.isPhysical()) return;
    regOp.append(reg.useList);
    reg.useList = regOp;
  }

  /**
   * Record a def of a register
   * @param regOp the operand that uses the register
   */
  public static void recordDef(RegisterOperand regOp) {
    Register reg = regOp.getRegister();
    if (SUPRESS_DU_FOR_PHYSICALS && reg.isPhysical()) return;
    regOp.append(reg.defList);
    reg.defList = regOp;
  }

  /**
   * Record that a use of a register no longer applies
   * @param regOp the operand that uses the register
   */
  public static void removeUse(RegisterOperand regOp) {
    Register reg = regOp.getRegister();
    if (SUPRESS_DU_FOR_PHYSICALS && reg.isPhysical()) return;
    if (regOp == reg.useList) {
      reg.useList = reg.useList.getNext();
    } else {
      RegisterOperand prev = reg.useList;
      RegisterOperand curr = prev.getNext();
      while (curr != regOp) {
        prev = curr;
        curr = curr.getNext();
      }
      prev.setNext(curr.getNext());
    }
    reg.useCount--;
    if (DEBUG) {
      VM.sysWrite("removed a use " + regOp.instruction + "\n");
      printUses(reg);
    }
  }

  /**
   * Record that a def of a register no longer applies
   * @param regOp the operand that uses the register
   */
  public static void removeDef(RegisterOperand regOp) {
    Register reg = regOp.getRegister();
    if (SUPRESS_DU_FOR_PHYSICALS && reg.isPhysical()) return;
    if (regOp == reg.defList) {
      reg.defList = reg.defList.getNext();
    } else {
      RegisterOperand prev = reg.defList;
      RegisterOperand curr = prev.getNext();
      while (curr != regOp) {
        prev = curr;
        curr = curr.getNext();
      }
      prev.setNext(curr.getNext());
    }
    if (DEBUG) {
      VM.sysWrite("removed a def " + regOp.instruction + "\n");
      printDefs(reg);
    }
  }

  /**
   *  This code changes the use in <code>origRegOp</code> to use
   *    the use in <code>newRegOp</code>.
   *
   *  <p> If the type of <code>origRegOp</code> is not a reference, but the
   *  type of <code>newRegOp</code> is a reference, we need to update
   *  <code>origRegOp</code> to be a reference.
   *  Otherwise, the GC map code will be incorrect.   -- Mike Hind
   *  @param origRegOp the register operand to change
   *  @param newRegOp the register operand to use for the change
   */
  public static void transferUse(RegisterOperand origRegOp, RegisterOperand newRegOp) {
    if (VM.VerifyAssertions) {
      VM._assert(origRegOp.getRegister().getType() == newRegOp.getRegister().getType());
    }
    Instruction inst = origRegOp.instruction;
    if (DEBUG) {
      VM.sysWrite("Transfering a use of " + origRegOp + " in " + inst + " to " + newRegOp + "\n");
    }
    removeUse(origRegOp);
    // check to see if the regOp type is NOT a ref, but the newRegOp type
    // is a reference.   This can occur because of magic calls.
    if (!origRegOp.getType().isReferenceType() && newRegOp.getType().isReferenceType()) {
      // clone the newRegOp object and use it to replace the regOp object
      RegisterOperand copiedRegOp = (RegisterOperand) newRegOp.copy();
      inst.replaceOperand(origRegOp, copiedRegOp);
      recordUse(copiedRegOp);
    } else {
      // just copy the register
      origRegOp.setRegister(newRegOp.getRegister());
      if (newRegOp.getType() != TypeReference.ObjectReference &&
          !newRegOp.getType().isUnboxedType() && !origRegOp.isPreciseType()) {
        // copy type information from new to orig unless its an unboxed type
        // (we don't want to copy type information for unboxed types as it is
        // likely the result of inlining new) or the type of the original is
        // precise
        origRegOp.copyType(newRegOp);
      }
      recordUse(origRegOp);
    }
    if (DEBUG) {
      printUses(origRegOp.getRegister());
      printUses(newRegOp.getRegister());
    }
  }

  /**
   * Remove an instruction and update register lists.
   */
  public static void removeInstructionAndUpdateDU(Instruction s) {
    for (OperandEnumeration e = s.getPureDefs(); e.hasMoreElements();) {
      Operand op = e.next();
      if (op instanceof RegisterOperand) {
        removeDef((RegisterOperand) op);
      }
    }
    for (OperandEnumeration e = s.getUses(); e.hasMoreElements();) {
      Operand op = e.next();
      if (op instanceof RegisterOperand) {
        removeUse((RegisterOperand) op);
      }
    }
    s.remove();
  }

  /**
   * Update register lists to account for the effect of a new
   * instruction s
   */
  public static void updateDUForNewInstruction(Instruction s) {
    for (OperandEnumeration e = s.getPureDefs(); e.hasMoreElements();) {
      Operand op = e.next();
      if (op instanceof RegisterOperand) {
        recordDef((RegisterOperand) op);
      }
    }
    for (OperandEnumeration e = s.getUses(); e.hasMoreElements();) {
      Operand op = e.next();
      if (op instanceof RegisterOperand) {
        recordUse((RegisterOperand) op);
      }
    }
  }

  /**
   * Replace an instruction and update register lists.
   */
  public static void replaceInstructionAndUpdateDU(Instruction oldI, Instruction newI) {
    oldI.insertBefore(newI);
    removeInstructionAndUpdateDU(oldI);
    updateDUForNewInstruction(newI);
  }

  /**
   * Enumerate all operands that use a given register.
   */
  public static RegisterOperandEnumeration uses(Register reg) {
    return new RegOpListWalker(reg.useList);
  }

  /**
   * Enumerate all operands that def a given register.
   */
  public static RegisterOperandEnumeration defs(Register reg) {
    return new RegOpListWalker(reg.defList);
  }

  /**
   * Does a given register have exactly one use?
   */
  static boolean exactlyOneUse(Register reg) {
    return (reg.useList != null) && (reg.useList.getNext() == null);
  }

  /**
   * Print all the instructions that def a register.
   * @param reg
   */
  static void printDefs(Register reg) {
    VM.sysWrite("Definitions of " + reg + '\n');
    for (RegisterOperandEnumeration e = defs(reg); e.hasMoreElements();) {
      VM.sysWrite("\t" + e.next().instruction + "\n");
    }
  }

  /**
   * Print all the instructions that usea register.
   * @param reg
   */
  static void printUses(Register reg) {
    VM.sysWrite("Uses of " + reg + '\n');
    for (RegisterOperandEnumeration e = uses(reg); e.hasMoreElements();) {
      VM.sysWrite("\t" + e.next().instruction + "\n");
    }
  }

  /**
   * Recompute <code> isSSA </code> for all registers by traversing register
   * list.
   * NOTE: the DU MUST be computed BEFORE calling this function
   *
   * @param ir the IR in question
   */
  public static void recomputeSSA(IR ir) {
    // Use register /ist to enumerate register objects (FAST)
    for (Register reg = ir.regpool.getFirstSymbolicRegister(); reg != null; reg = reg.getNext()) {
      // Set isSSA = true iff reg has exactly one static definition.
      reg.putSSA((reg.defList != null && reg.defList.getNext() == null));
    }
  }

  /**
   * Merge register reg2 into register reg1.
   * Remove reg2 from the DU information
   */
  public static void mergeRegisters(IR ir, Register reg1, Register reg2) {
    RegisterOperand lastOperand;
    if (reg1 == reg2) {
      return;
    }
    if (DEBUG) {
      VM.sysWrite("Merging " + reg2 + " into " + reg1 + "\n");
      printDefs(reg2);
      printUses(reg2);
      printDefs(reg1);
      printUses(reg1);
    }
    // first loop through defs of reg2 (currently, there will only be one def)
    lastOperand = null;
    for (RegisterOperand def = reg2.defList; def != null; lastOperand = def, def = def.getNext()) {
      // Change def to refer to reg1 instead
      def.setRegister(reg1);
      // Track lastOperand
      lastOperand = def;
    }
    if (lastOperand != null) {
      // Set reg1.defList = concat(reg2.defList, reg1.deflist)
      lastOperand.setNext(reg1.defList);
      reg1.defList = reg2.defList;
    }
    // now loop through uses
    lastOperand = null;
    for (RegisterOperand use = reg2.useList; use != null; use = use.getNext()) {
      // Change use to refer to reg1 instead
      use.setRegister(reg1);
      // Track lastOperand
      lastOperand = use;
    }
    if (lastOperand != null) {
      // Set reg1.useList = concat(reg2.useList, reg1.uselist)
      lastOperand.setNext(reg1.useList);
      reg1.useList = reg2.useList;
    }
    // Remove reg2 from RegisterPool
    ir.regpool.removeRegister(reg2);
    if (DEBUG) {
      VM.sysWrite("Merge complete\n");
      printDefs(reg1);
      printUses(reg1);
    }
  }

  /**
   * Recompute spansBasicBlock flags for all registers.
   *
   * @param ir the IR in question
   */
  public static void recomputeSpansBasicBlock(IR ir) {
    // clear fields
    for (Register reg = ir.regpool.getFirstSymbolicRegister(); reg != null; reg = reg.getNext()) {
      reg.scratch = -1;
      reg.clearSpansBasicBlock();
    }
    // iterate over the basic blocks
    for (BasicBlock bb = ir.firstBasicBlockInCodeOrder(); bb != null; bb = bb.nextBasicBlockInCodeOrder()) {
      int bbNum = bb.getNumber();
      // enumerate the instructions in the basic block
      for (InstructionEnumeration e = bb.forwardRealInstrEnumerator(); e.hasMoreElements();) {
        Instruction inst = e.next();
        // check each Operand in the instruction
        for (OperandEnumeration ops = inst.getOperands(); ops.hasMoreElements();) {
          Operand op = ops.next();
          if (op instanceof RegisterOperand) {
            Register reg = ((RegisterOperand) op).getRegister();
            if (reg.isPhysical()) {
              continue;
            }
            if (reg.spansBasicBlock()) {
              continue;
            }
            if (seenInDifferentBlock(reg, bbNum)) {
              reg.setSpansBasicBlock();
              continue;
            }
            if (inst.operator() == PHI) {
              reg.setSpansBasicBlock();
              continue;
            }
            logAppearance(reg, bbNum);
          }
        }
      }
    }
  }

  /**
   * Mark that we have seen a register in a particular
   * basic block, and whether we saw a use
   *
   * @param reg the register
   * @param bbNum the number of the basic block
   */
  private static void logAppearance(Register reg, int bbNum) {
    reg.scratch = bbNum;
  }

  /**
   * Have we seen this register in a different basic block?
   *
   * @param reg the register
   * @param bbNum the number of the basic block
   */
  private static boolean seenInDifferentBlock(Register reg, int bbNum) {
    int bb = reg.scratch;
    return (bb != -1) && (bb != bbNum);
  }

  /**
   * Utility class to encapsulate walking a use/def list.
   */
  private static final class RegOpListWalker implements RegisterOperandEnumeration {

    private RegisterOperand current;

    RegOpListWalker(RegisterOperand start) {
      current = start;
    }

    public boolean hasMoreElements() {
      return current != null;
    }

    public RegisterOperand nextElement() {
      return next();
    }

    public RegisterOperand next() {
      if (current == null) raiseNoSuchElementException();
      RegisterOperand tmp = current;
      current = current.getNext();
      return tmp;
    }

    @NoInline
    private static void raiseNoSuchElementException() {
      throw new java.util.NoSuchElementException("RegOpListWalker");
    }
  }
}
