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

import java.util.Enumeration;
import org.jikesrvm.ArchitectureSpecificOpt.RegisterPool;
import org.jikesrvm.Configuration;
import org.jikesrvm.classloader.FieldReference;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.ir.operand.AddressConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.BranchOperand;
import org.jikesrvm.compilers.opt.ir.operand.DoubleConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.FloatConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.IntConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.LongConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.NullConstantOperand;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.jikesrvm.compilers.opt.ir.operand.TrueGuardOperand;

import static org.jikesrvm.compilers.opt.ir.Operators.BYTE_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.BYTE_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_COND_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.DOUBLE_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_COND_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.FLOAT_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.GOTO;
import static org.jikesrvm.compilers.opt.ir.Operators.GUARD_COND_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.GUARD_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_COND_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.INT_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_COND_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.LONG_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_COND_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_MOVE;
import static org.jikesrvm.compilers.opt.ir.Operators.REF_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.SHORT_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.SHORT_STORE;
import static org.jikesrvm.compilers.opt.ir.Operators.UBYTE_LOAD;
import static org.jikesrvm.compilers.opt.ir.Operators.USHORT_LOAD;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;

/**
 * This abstract class contains a bunch of useful static methods for
 * performing operations on IR.
 */
public abstract class IRTools {

  /**
   * Create an integer register operand for a given register.
   * To be used in passthrough expressions like
   * <pre>
   *    ... Load.create(INT_LOAD, I(r2), A(r1), IC(4)) ...
   * </pre>
   *
   * @param reg the given register
   * @return integer register operand
   */
  public static RegisterOperand A(Register reg) {
    return new RegisterOperand(reg, TypeReference.Address);
  }

  /**
   * Create an integer register operand for a given register.
   * To be used in passthrough expressions like
   * <pre>
   *    ... Load.create(INT_LOAD, I(r2), A(r1), IC(4)) ...
   * </pre>
   * @param reg the given register
   * @return integer register operand
   */
  public static RegisterOperand I(Register reg) {
    return new RegisterOperand(reg, TypeReference.Int);
  }

  /**
   * Create a float register operand for a given register.
   * To be used in passthrough expressions like
   * <pre>
   *    ... Load.create(FLOAT_LOAD, F(r2), A(r1), IC(4)) ...
   * </pre>
   *
   * @param reg the given register
   * @return float register operand
   */
  public static RegisterOperand F(Register reg) {
    return new RegisterOperand(reg, TypeReference.Float);
  }

  /**
   * Create a double register operand for a given register.
   * To be used in passthrough expressions like
   * <pre>
   *    ... Load.create(DOUBLE_LOAD, D(r2), A(r1), IC(4)) ...
   * </pre>
   *
   * @param reg the given register
   * @return double register operand
   */
  public static RegisterOperand D(Register reg) {
    return new RegisterOperand(reg, TypeReference.Double);
  }

  /**
   * Create a long register operand for a given register.
   * To be used in passthrough expressions like
   * <pre>
   *    ... Binary.create(LONG_LOAD, L(r2), A(r1), IC(4)) ...
   * </pre>
   *
   * @param reg the given register
   * @return long register operand
   */
  public static RegisterOperand L(Register reg) {
    return new RegisterOperand(reg, TypeReference.Long);
  }

  /**
   * Create a condition register operand for a given register.
   * To be used in passthrough expressions like
   * <pre>
   *    ... Binary.create(INT_CMP, CR(c2), I(r1), IC(4)) ...
   * </pre>
   *
   * @param reg the given register
   * @return condition register operand
   */
  public static RegisterOperand CR(Register reg) {
    return new RegisterOperand(reg, TypeReference.Int);
  }

  /**
   * Create an address constant operand with a given value.
   * To be used in passthrough expressions like
   * <pre>
   *    ...<op>.create(...., AC(Address.zero()) ...
   * </pre>
   *
   * @param value    The address constant
   * @return address constant operand
   */
  public static AddressConstantOperand AC(Address value) {
    return new AddressConstantOperand(value);
  }

  public static AddressConstantOperand AC(Offset value) {
    return new AddressConstantOperand(value);
  }

  /**
   * Create an integer constant operand with a given value.
   * To be used in passthrough expressions like
   * <pre>
   *    ...<op>.create(...., IC(0) ...
   * </pre>
   *
   * @param value   The int constant
   * @return integer constant operand
   */
  public static IntConstantOperand IC(int value) {
    return new IntConstantOperand(value);
  }

  /**
   * Create a long constant operand with a given value.
   * To be used in passthrough expressions like
   * <pre>
   *    ...<op>.create(...., LC(0L) ...
   * </pre>
   *
   * @param value the long value
   * @return long constant operand
   */
  public static LongConstantOperand LC(long value) {
    return new LongConstantOperand(value);
  }

  /**
   * Create a long constant operand with a given value.
   * To be used in passthrough expressions like
   * <pre>
   *    ...<op>.create(...., FC(0L) ...
   * </pre>
   *
   * @param value the float value
   * @return float constant operand
   */
  public static FloatConstantOperand FC(float value) {
    return new FloatConstantOperand(value);
  }

  /**
   * Create a long constant operand with a given value.
   * To be used in passthrough expressions like
   * <pre>
   *    ...<op>.create(...., DC(0L) ...
   * </pre>
   *
   * @param value the double value
   * @return double constant operand
   */
  public static DoubleConstantOperand DC(double value) {
    return new DoubleConstantOperand(value);
  }

  /**
   * Create a new TrueGuardOperand.
   * To be used in passthrough expressions like
   * <pre>
   *    ...<op>.create(...., TG() ...
   * </pre>
   *
   * @return true guard operand
   */
  public static TrueGuardOperand TG() {
    return new TrueGuardOperand();
  }

  /**
   * Copy the position information from the source instruction to
   * the destination instruction, returning the source instruction.
   * To be used in passthrough expressions like
   * <pre>
   *    instr.insertBack(CPOS(instr, Load.create(...)));
   * </pre>
   *
   * @param src the instruction to copy position information from
   * @param dst the instruction to copy position information to
   * @return dest
   */
  public static Instruction CPOS(Instruction src, Instruction dst) {
    dst.copyPosition(src);
    return dst;
  }

  /**
   * Returns a constant operand with a default value for a given type
   *
   * @param type desired type
   * @return a constant operand with the default value for type
   */
  public static Operand getDefaultOperand(TypeReference type) {
    if (type.isBooleanType()) return new IntConstantOperand(0);
    if (type.isByteType()) return new IntConstantOperand(0);
    if (type.isCharType()) return new IntConstantOperand(0);
    if (type.isIntType()) return new IntConstantOperand(0);
    if (type.isShortType()) return new IntConstantOperand(0);
    if (type.isLongType()) return new LongConstantOperand(0);
    if (type.isFloatType()) return new FloatConstantOperand(0f);
    if (type.isDoubleType()) return new DoubleConstantOperand(0.0);
    return new NullConstantOperand();
  }

  /**
   * Returns the correct operator for moving the given data type.
   *
   * @param type desired type to move
   * @return the Operator to use for moving a value of the given type
   */
  public static Operator getMoveOp(TypeReference type) {
    if (type.isLongType()) return LONG_MOVE;
    if (type.isFloatType()) return FLOAT_MOVE;
    if (type.isDoubleType()) return DOUBLE_MOVE;
    if (type == TypeReference.VALIDATION_TYPE) return GUARD_MOVE;
    if (type.isReferenceType() || type.isWordType()) return REF_MOVE;
    return INT_MOVE;
  }

  /**
   * Returns the correct operator for a conditional move with the given data
   * type.
   *
   * @param type desired type to move
   * @return the Operator to use for moving a value of the given type
   */
  public static Operator getCondMoveOp(TypeReference type) {
    if (type.isLongType()) return LONG_COND_MOVE;
    if (type.isFloatType()) return FLOAT_COND_MOVE;
    if (type.isDoubleType()) return DOUBLE_COND_MOVE;
    if (type == TypeReference.VALIDATION_TYPE) return GUARD_COND_MOVE;
    if (type.isReferenceType() || type.isWordType()) return REF_COND_MOVE;
    return INT_COND_MOVE;
  }

  /**
   * Returns the correct operator for loading from the given field
   *
   * @param field field to load from
   * @param isStatic is the field static
   * @return the Operator to use when loading the given field
   */
  public static Operator getLoadOp(FieldReference field, boolean isStatic) {
    return getLoadOp(field.getFieldContentsType(), isStatic);
  }

  /**
   * Returns the correct operator for loading a value of the given type
   *
   * @param type type of value to load
   * @param isStatic is the field static
   * @return the Operator to use when loading the given field
   */
  public static Operator getLoadOp(TypeReference type, boolean isStatic) {
    if (!Configuration.LittleEndian && isStatic) {
      // Handle the statics table hold subword values in ints
      if (type.isByteType()) return INT_LOAD;
      if (type.isBooleanType()) return INT_LOAD;
      if (type.isCharType()) return INT_LOAD;
      if (type.isShortType()) return INT_LOAD;
    }
    if (type.isByteType()) return BYTE_LOAD;
    if (type.isBooleanType()) return UBYTE_LOAD;
    if (type.isCharType()) return USHORT_LOAD;
    if (type.isShortType()) return SHORT_LOAD;
    if (type.isLongType()) return LONG_LOAD;
    if (type.isFloatType()) return FLOAT_LOAD;
    if (type.isDoubleType()) return DOUBLE_LOAD;
    if (type.isReferenceType()) return REF_LOAD;
    if (type.isWordType()) return REF_LOAD;
    return INT_LOAD;
  }

  /**
   * Returns the correct operator for storing to the given field.
   *
   * @param field  The field we're asking about
   * @param isStatic is the field static
   * @return the Operator to use when storing to the given field
   */
  public static Operator getStoreOp(FieldReference field, boolean isStatic) {
    return getStoreOp(field.getFieldContentsType(), isStatic);
  }

  /**
   * Returns the correct operator for storing a value of the given type
   *
   * @param type desired type to store
   * @param isStatic is the field static
   * @return the Operator to use when storing to the given field
   */
  public static Operator getStoreOp(TypeReference type, boolean isStatic) {
    if (!Configuration.LittleEndian && isStatic) {
      // Handle the statics table hold subword values in ints
      if (type.isByteType()) return INT_STORE;
      if (type.isBooleanType()) return INT_STORE;
      if (type.isCharType()) return INT_STORE;
      if (type.isShortType()) return INT_STORE;
    }
    if (type.isByteType()) return BYTE_STORE;
    if (type.isBooleanType()) return BYTE_STORE;
    if (type.isCharType()) return SHORT_STORE;
    if (type.isShortType()) return SHORT_STORE;
    if (type.isLongType()) return LONG_STORE;
    if (type.isFloatType()) return FLOAT_STORE;
    if (type.isDoubleType()) return DOUBLE_STORE;
    if (type.isReferenceType()) return REF_STORE;
    if (type.isWordType()) return REF_STORE;
    return INT_STORE;
  }

  /**
   * Generates an instruction to move the given operand into a register, and
   * inserts it before the given instruction.
   *
   * @param pool register pool to allocate from
   * @param s instruction to insert before
   * @param op operand to copy to a register
   * @return register operand that we copied into
   */
  public static RegisterOperand moveIntoRegister(RegisterPool pool, Instruction s, Operand op) {
    if (op instanceof RegisterOperand) {
      return (RegisterOperand) op;
    }
    TypeReference type = op.getType();
    Operator move_op = IRTools.getMoveOp(type);
    return moveIntoRegister(type, move_op, pool, s, op);
  }

  /**
   * Generates an instruction to move the given operand into a register, and
   * inserts it before the given instruction.
   *
   * @param type type to move
   * @param move_op move operator to use
   * @param pool register pool to allocate from
   * @param s instruction to insert before
   * @param op operand to copy to a register
   * @return last use register operand that we copied into
   */
  public static RegisterOperand moveIntoRegister(TypeReference type, Operator move_op, RegisterPool pool,
                                                     Instruction s, Operand op) {
    RegisterOperand rop = pool.makeTemp(type);
    s.insertBefore(Move.create(move_op, rop, op));
    rop = rop.copyD2U();
    return rop;
  }

  /**
   * Moves the 'from' instruction to immediately before the 'to' instruction.
   *
   * @param from instruction to move
   * @param to instruction after where you want it moved
   */
  public static void moveInstruction(Instruction from, Instruction to) {
    from.remove();
    to.insertBefore(from);
  }

  /**
   * Inserts the instructions in the given basic block after the given
   * instruction.
   *
   * @param after instruction after where you want it inserted
   * @param temp basic block which contains the instructions to be inserted.
   */
  public static void insertInstructionsAfter(Instruction after, BasicBlock temp) {
    if (temp.isEmpty()) return;
    Instruction after_after = after.getNext();
    after.linkWithNext(temp.firstRealInstruction());
    if (after_after == null) {
      temp.lastRealInstruction().setNext(null);
    } else {
      temp.lastRealInstruction().linkWithNext(after_after);
    }
  }

  /**
   * Make an empty basic block on an edge in the control flow graph,
   * and fix up the control flow graph and IR instructions accordingly.
   *
   * This routine will create the control struture
   * <pre>
   * in -> bb -> out.
   * </pre>
   * <em> Precondition </em>: There is an edge in the control flow graph
   * from * in -> out.
   *
   * @param in the source of the control flow edge
   * @param out the sink of the control flow edge
   * @param ir the governing IR
   * @return the new basic block bb
   */
  public static BasicBlock makeBlockOnEdge(BasicBlock in, BasicBlock out, IR ir) {
    // 1. Create the new basic block
    BasicBlock bb = in.createSubBlock(out.firstInstruction().bcIndex, ir);

    // 2. Splice the new basic block into the code order
    BasicBlock next = in.nextBasicBlockInCodeOrder();
    if (next == null) {
      ir.cfg.addLastInCodeOrder(bb);
    } else {
      ir.cfg.breakCodeOrder(in, next);
      ir.cfg.linkInCodeOrder(in, bb);
      ir.cfg.linkInCodeOrder(bb, next);
    }

    // 3. update in's branch instructions
    boolean foundGoto = false;
    BranchOperand target = bb.makeJumpTarget();
    BranchOperand outTarget = out.makeJumpTarget();
    for (InstructionEnumeration e = in.reverseRealInstrEnumerator(); e.hasMoreElements();) {
      Instruction s = e.next();
      if (IfCmp2.conforms(s)) {
        if (IfCmp2.getTarget1(s).similar(outTarget)) {
          IfCmp2.setTarget1(s, (BranchOperand) target.copy());
        }
        if (IfCmp2.getTarget2(s).similar(outTarget)) {
          IfCmp2.setTarget2(s, (BranchOperand) target.copy());
        }
      } else if (IfCmp.conforms(s)) {
        if (IfCmp.getTarget(s).similar(outTarget)) {
          IfCmp.setTarget(s, (BranchOperand) target.copy());
        }
      } else if (InlineGuard.conforms(s)) {
        if (InlineGuard.getTarget(s).similar(outTarget)) {
          InlineGuard.setTarget(s, (BranchOperand) target.copy());
        }
      } else if (Goto.conforms(s)) {
        foundGoto = true;
        if (Goto.getTarget(s).similar(outTarget)) {
          Goto.setTarget(s, (BranchOperand) target.copy());
        }
      } else if (TableSwitch.conforms(s)) {
        foundGoto = true;
        if (TableSwitch.getDefault(s).similar(outTarget)) {
          TableSwitch.setDefault(s, (BranchOperand) target.copy());
        }
        for (int i = 0; i < TableSwitch.getNumberOfTargets(s); i++) {
          if (TableSwitch.getTarget(s, i).similar(outTarget)) {
            TableSwitch.setTarget(s, i, (BranchOperand) target.copy());
          }
        }
      } else if (LowTableSwitch.conforms(s)) {
        foundGoto = true;
        for (int i = 0; i < LowTableSwitch.getNumberOfTargets(s); i++) {
          if (LowTableSwitch.getTarget(s, i).similar(outTarget)) {
            LowTableSwitch.setTarget(s, i, (BranchOperand) target.copy());
          }
        }
      } else if (LookupSwitch.conforms(s)) {
        foundGoto = true;
        if (LookupSwitch.getDefault(s).similar(outTarget)) {
          LookupSwitch.setDefault(s, (BranchOperand) target.copy());
        }
        for (int i = 0; i < LookupSwitch.getNumberOfTargets(s); i++) {
          if (LookupSwitch.getTarget(s, i).similar(outTarget)) {
            LookupSwitch.setTarget(s, i, (BranchOperand) target.copy());
          }
        }
      } else {
        // done processing all branches
        break;
      }
    }

    // 4. Add a goto bb->out
    Instruction s = Goto.create(GOTO, out.makeJumpTarget());
    bb.appendInstruction(s);
    // add goto in->next
    // if out was not the fallthrough, add a GOTO to preserve this
    // control flow
    if (out != next) {
      // if there's already a GOTO, there's no fall through
      if (!foundGoto) {
        /*
         * TODO: come up with a better fix (?).
         *
         * This is a fix to a particular problem in dacapo xalan.
         *
         * We have a loop inside an exception handler, and the exception handler
         * is empty.  The loop termination condition simply falls through the
         * exception handler to the next block.  This works fine until LeaveSSA,
         * when we split the final block and insert a GOTO to the exception handler
         * block.  When we reassemble the IR afterwards, kaboom.
         *
         * I would have though it better not to fall through empty exception handlers
         * at all, and explicitly GOTO past them from the get go.   RJG 4/2/7
         */
        BasicBlock jumpTarget = next;
        while (jumpTarget.isEmpty() && jumpTarget.isExceptionHandlerBasicBlock()) {
          jumpTarget = jumpTarget.nextBasicBlockInCodeOrder();
        }
        s = Goto.create(GOTO, jumpTarget.makeJumpTarget());
        in.appendInstruction(s);
      }
    }

    // 5. Update the CFG
    in.recomputeNormalOut(ir);
    bb.recomputeNormalOut(ir);

    return bb;
  }

  /**
   * Is the operand u, which is a use in instruction s, also a def
   * in instruction s?  That is, is this operand defined as a DU operand
   * in InstructionFormatList.dat.
   *
   * TODO!!: This implementation is slow.  Think about adding
   * some IR support for this functionality; possibly add methods like
   * enumeratePureDefs(), enumerateImpureUses(), etc ..., and restructure
   * the caller to avoid having to call this function.  Not going
   * to put effort into this now, as the whole scratch register
   * architecture has a questionable future.
   */
  public static boolean useDoublesAsDef(Operand u, Instruction s) {
    for (Enumeration<Operand> d = s.getDefs(); d.hasMoreElements();) {
      Operand def = d.nextElement();
      if (def != null) {
        if (def == u) return true;
      }
    }
    return false;
  }

  /**
   * Is the operand d, which is a def in instruction s, also a def
   * in instruction s?  That is, is this operand defined as a DU operand
   * in InstructionFormatList.dat.
   *
   * TODO!!: This implementation is slow.  Think about adding
   * some IR support for this functionality; possibly add methods like
   * enumeratePureDefs(), enumerateImpureUses(), etc ..., and restructure
   * the caller to avoid having to call this function.  Not going
   * to put effort into this now, as the whole scratch register
   * architecture has a questionable future.
   */
  public static boolean defDoublesAsUse(Operand d, Instruction s) {
    for (Enumeration<Operand> u = s.getUses(); u.hasMoreElements();) {
      Operand use = u.nextElement();
      if (use != null) {
        if (use.similar(d)) return true;
      }
    }
    return false;
  }

  /**
   * Does instruction s define register r?
   */
  public static boolean definedIn(Register r, Instruction s) {
    for (Enumeration<Operand> e = s.getDefs(); e.hasMoreElements();) {
      Operand op = e.nextElement();
      if (op != null && op.isRegister()) {
        if (op.asRegister().getRegister().number == r.number) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Does instruction s use register r?
   */
  public static boolean usedIn(Register r, Instruction s) {
    for (Enumeration<Operand> e = s.getUses(); e.hasMoreElements();) {
      Operand op = e.nextElement();
      if (op != null && op.isRegister()) {
        if (op.asRegister().getRegister().number == r.number) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Mark the parameter as nonGC and nonPEI and return it.
   * To be used in passthrough expressions like
   * <pre>
   *    instr.insertBack(notPEIGC(Load.create(...)));
   * </pre>
   *
   * @param instr the given instruction
   * @return the given instruction
   */
  public static Instruction nonPEIGC(Instruction instr) {
    instr.markAsNonPEINonGCPoint();
    return instr;
  }

  /**
   * Might this instruction be a load from a field that is declared
   * to be volatile?
   *
   * @param s the insruction to check
   * @return <code>true</code> if the instruction might be a load
   *         from a volatile field or <code>false</code> if it
   *         cannot be a load from a volatile field
   */
  public static boolean mayBeVolatileFieldLoad(Instruction s) {
    return s.mayBeVolatileFieldLoad();
  }
}

