/*
 *  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.Iterator;
import org.jikesrvm.ArchitectureSpecificOpt;
import org.jikesrvm.ArchitectureSpecificOpt.PhysicalDefUse;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.ir.operand.HeapOperand;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;

import static org.jikesrvm.compilers.opt.ir.Operators.PHI;
import org.vmmagic.pragma.NoInline;

/**
 * This class is not meant to be instantiated.
 * It simply serves as a place to collect the implementation of
 * primitive IR enumerations.
 * None of these functions are meant to be called directly from
 * anywhere except IR, Instruction, and BasicBlock.
 * General clients should use the higher level interfaces provided
 * by those classes
 */
public abstract class IREnumeration {

  /**
   * Forward intra basic block instruction enumerations from
   * from start...last inclusive.
   *
   * NB: start and last _must_ be in the same basic block
   *     and must be in the proper relative order.
   *     This code does _not_ check this invariant, and will
   *     simply fail by eventually thowing a NoSuchElementException
   *     if it is not met. Caller's must be sure the invariants are met.
   *
   * @param start the instruction to start with
   * @param end   the instruction to end with
   * @return an enumeration of the instructions from start to end
   */
  public static InstructionEnumeration forwardIntraBlockIE(final Instruction start, final Instruction end) {
    return new InstructionEnumeration() {
      private Instruction current = start;
      private final Instruction last = end;

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

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

      public Instruction next() {
        Instruction res = current;
        if (current == last) {
          current = null;
        } else {
          try {
            current = current.getNext();
          } catch (NullPointerException e) {
            fail("forwardIntraBlockIE");
          }
        }
        return res;
      }
    };
  }

  /**
   * Reverse intra basic block instruction enumerations from
   * from start...last inclusive.
   *
   * NB: start and last _must_ be in the same basic block
   *     and must be in the proper relative order.
   *     This code does _not_ check this invariant, and will
   *     simply fail by eventually thowing a NoSuchElementException
   *     if it is not met. Caller's must be sure the invariants are met.
   *
   * @param start the instruction to start with
   * @param end   the instruction to end with
   * @return an enumeration of the instructions from start to end
   */
  public static InstructionEnumeration reverseIntraBlockIE(final Instruction start, final Instruction end) {
    return new InstructionEnumeration() {
      private Instruction current = start;
      private final Instruction last = end;

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

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

      public Instruction next() {
        Instruction res = current;
        if (current == last) {
          current = null;
        } else {
          try {
            current = current.getPrev();
          } catch (NullPointerException e) {
            fail("reverseIntraBlockIE");
          }
        }
        return res;
      }
    };
  }

  /**
   * A forward enumeration of all the instructions in the IR.
   *
   * @param ir the IR to walk over
   * @return a forward enumeration of the insturctions in ir
   */
  public static InstructionEnumeration forwardGlobalIE(final IR ir) {
    return new InstructionEnumeration() {
      private Instruction current = ir.firstInstructionInCodeOrder();

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

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

      public Instruction next() {
        try {
          Instruction res = current;
          current = current.nextInstructionInCodeOrder();
          return res;
        } catch (NullPointerException e) {
          fail("forwardGlobalIR");
          return null; // placate jikes
        }
      }
    };
  }

  /**
   * A reverse enumeration of all the instructions in the IR.
   *
   * @param ir the IR to walk over
   * @return a forward enumeration of the insturctions in ir
   */
  public static InstructionEnumeration reverseGlobalIE(final IR ir) {
    return new InstructionEnumeration() {
      private Instruction current = ir.lastInstructionInCodeOrder();

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

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

      public Instruction next() {
        try {
          Instruction res = current;
          current = current.prevInstructionInCodeOrder();
          return res;
        } catch (NullPointerException e) {
          fail("forwardGlobalIR");
          return null; // placate jikes
        }
      }
    };
  }

  /**
   * A forward enumeration of all the basic blocks in the IR.
   *
   * @param ir the IR to walk over
   * @return a forward enumeration of the basic blocks in ir
   */
  public static BasicBlockEnumeration forwardBE(final IR ir) {
    return new BasicBlockEnumeration() {
      private BasicBlock current = ir.firstBasicBlockInCodeOrder();

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

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

      public BasicBlock next() {
        try {
          BasicBlock res = current;
          current = current.nextBasicBlockInCodeOrder();
          return res;
        } catch (NullPointerException e) {
          fail("forwardBE");
          return null; // placate jikes
        }
      }
    };
  }

  /**
   * A reverse enumeration of all the basic blocks in the IR.
   *
   * @param ir the IR to walk over
   * @return a reverse enumeration of the basic blocks in ir
   */
  public static BasicBlockEnumeration reverseBE(final IR ir) {
    return new BasicBlockEnumeration() {
      private BasicBlock current = ir.lastBasicBlockInCodeOrder();

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

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

      public BasicBlock next() {
        try {
          BasicBlock res = current;
          current = current.prevBasicBlockInCodeOrder();
          return res;
        } catch (NullPointerException e) {
          fail("forwardBE");
          return null; // placate jikes
        }
      }
    };
  }

  /**
   * This class implements an {@link InstructionEnumeration} over
   * all instructions for a basic block. This enumeration includes
   * explicit instructions in the IR and implicit phi instructions for
   * heap variables, which are stored only in this lookaside
   * structure.
   * @see org.jikesrvm.compilers.opt.ssa.SSADictionary
   */
  public static final class AllInstructionsEnum implements InstructionEnumeration {
    /**
     * An enumeration of the explicit instructions in the IR for a
     * basic block
     */
    private final InstructionEnumeration explicitInstructions;

    /**
     * An enumeration of the implicit instructions in the IR for a
     * basic block.  These instructions appear only in the SSA
     * dictionary lookaside structure.
     */
    private final Iterator<Instruction> implicitInstructions;

    /**
     * The label instruction for the basic block - the label is
     * special as we want it to appear in the enumeration before the
     * implicit SSA instructions
     */
    private Instruction labelInstruction;

    /**
     * Construct an enumeration for all instructions, both implicit and
     * explicit in the IR, for a given basic block
     *
     * @param block the basic block whose instructions this enumerates
     */
    public AllInstructionsEnum(IR ir, BasicBlock block) {
      explicitInstructions = block.forwardInstrEnumerator();
      if (ir.inSSAForm()) {
        implicitInstructions = ir.HIRInfo.dictionary.getHeapPhiInstructions(block);
      } else {
        implicitInstructions = null;
      }
      labelInstruction = explicitInstructions.next();
    }

    /**
     * Are there more elements in the enumeration?
     *
     * @return true or false
     */
    public boolean hasMoreElements() {
      return (((implicitInstructions != null) && implicitInstructions.hasNext()) ||
              explicitInstructions.hasMoreElements());
    }

    /**
     * Get the next instruction in the enumeration
     *
     * @return the next instruction
     */
    public Instruction next() {
      if (labelInstruction != null) {
        Instruction temp = labelInstruction;
        labelInstruction = null;
        return temp;
      } else if ((implicitInstructions != null) && implicitInstructions.hasNext()) {
        return implicitInstructions.next();
      } else {
        return explicitInstructions.next();
      }
    }

    /**
     * Get the next instruction in the enumeration
     *
     * @return the next instruction
     */
    public Instruction nextElement() {
      return next();
    }
  }

  /**
   * This class implements an {@link OperandEnumeration}. It used
   * for holding the definitions of a particular instruction and being
   * used as an enumeration for iterating over. It differs from other
   * {@link OperandEnumeration} as it iterates over both implicit
   * and explicit operands.
   * @see org.jikesrvm.compilers.opt.ssa.SSADictionary
   */
  public static final class AllDefsEnum implements OperandEnumeration {
    /**
     * Enumeration of non-heap operands defined by the instruction
     */
    private final OperandEnumeration instructionOperands;
    /**
     * Array of heap operands defined by the instruction
     */
    private final HeapOperand<?>[] heapOperands;
    /**
     * Current heap operand we're upto for the enumeration
     */
    private int curHeapOperand;
    /**
     * Implicit definitions from the operator
     */
    private final ArchitectureSpecificOpt.PhysicalDefUse.PDUEnumeration implicitDefs;
    /**
     * Defining instruction
     */
    private final Instruction instr;

    /**
     * Construct/initialize object
     *
     * @param ir    Containing IR
     * @param instr Instruction to get definitions for
     */
    public AllDefsEnum(IR ir, Instruction instr) {
      this.instr = instr;
      instructionOperands = instr.getDefs();
      if (instr.operator().getNumberOfImplicitDefs() > 0) {
        implicitDefs = ArchitectureSpecificOpt.PhysicalDefUse.enumerate(instr.operator().implicitDefs, ir);
      } else {
        implicitDefs = null;
      }
      if (ir.inSSAForm() && (instr.operator != PHI)) {
        // Phi instructions store the heap SSA in the actual
        // instruction
        heapOperands = ir.HIRInfo.dictionary.getHeapDefs(instr);
      } else {
        heapOperands = null;
      }
    }

    /**
     * Are there any more elements in the enumeration
     */
    public boolean hasMoreElements() {
      return ((instructionOperands.hasMoreElements()) ||
              ((heapOperands != null) && (curHeapOperand < heapOperands.length)) ||
              ((implicitDefs != null) && (implicitDefs.hasMoreElements())));
    }

    /**
     * Next element in the enumeration
     */
    public Operand next() {
      if (instructionOperands.hasMoreElements()) {
        return instructionOperands.next();
      } else {
        if ((implicitDefs != null) && implicitDefs.hasMoreElements()) {
          RegisterOperand rop = new RegisterOperand(implicitDefs.nextElement(), TypeReference.Int);
          rop.instruction = instr;
          return rop;
        } else {
          if (curHeapOperand >= heapOperands.length) {
            fail("Regular and heap operands exhausted");
          }
          HeapOperand<?> result = heapOperands[curHeapOperand];
          curHeapOperand++;
          return result;
        }
      }
    }

    /**
     * Next element in the enumeration
     */
    public Operand nextElement() {
      return next();
    }
  }

  /**
   * This class implements an {@link OperandEnumeration}. It used
   * for holding the uses of a particular instruction and being used
   * as an enumeration for iterating over. It differs from other
   * {@link OperandEnumeration} as it iterates over both implicit
   * and explicit operands.
   * @see org.jikesrvm.compilers.opt.ssa.SSADictionary
   */
  public static final class AllUsesEnum implements OperandEnumeration {
    /**
     * Enumeration of non-heap operands defined by the instruction
     */
    private final OperandEnumeration instructionOperands;
    /**
     * Array of heap operands defined by the instruction
     */
    private final HeapOperand<?>[] heapOperands;
    /**
     * Current heap operand we're upto for the enumeration
     */
    private int curHeapOperand;
    /**
     * Implicit uses from the operator
     */
    private final PhysicalDefUse.PDUEnumeration implicitUses;
    /**
     * Defining instruction
     */
    private final Instruction instr;

    /**
     * Construct/initialize object
     *
     * @param ir    Containing IR
     * @param instr Instruction to get uses for
     */
    public AllUsesEnum(IR ir, Instruction instr) {
      this.instr = instr;
      instructionOperands = instr.getUses();
      if (instr.operator().getNumberOfImplicitUses() > 0) {
        implicitUses = PhysicalDefUse.enumerate(instr.operator().implicitUses, ir);
      } else {
        implicitUses = null;
      }
      if (ir.inSSAForm() && (instr.operator != PHI)) {
        // Phi instructions store the heap SSA in the actual
        // instruction
        heapOperands = ir.HIRInfo.dictionary.getHeapUses(instr);
      } else {
        heapOperands = null;
      }
    }

    /**
     * Are there any more elements in the enumeration
     */
    public boolean hasMoreElements() {
      return ((instructionOperands.hasMoreElements()) ||
              ((heapOperands != null) && (curHeapOperand < heapOperands.length)) ||
              ((implicitUses != null) && (implicitUses.hasMoreElements())));
    }

    /**
     * Next element in the enumeration
     */
    public Operand next() {
      if (instructionOperands.hasMoreElements()) {
        return instructionOperands.next();
      } else {
        if ((implicitUses != null) && implicitUses.hasMoreElements()) {
          RegisterOperand rop = new RegisterOperand(implicitUses.nextElement(), TypeReference.Int);
          rop.instruction = instr;
          return rop;
        } else {
          if (curHeapOperand >= heapOperands.length) {
            fail("Regular and heap operands exhausted");
          }
          HeapOperand<?> result = heapOperands[curHeapOperand];
          curHeapOperand++;
          return result;
        }
      }
    }

    /**
     * Next element in the enumeration
     */
    public Operand nextElement() {
      return next();
    }
  }

  @NoInline
  private static void fail(String msg) throws java.util.NoSuchElementException {
    throw new java.util.NoSuchElementException(msg);
  }
}

