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

import java.util.Enumeration;
import org.jikesrvm.VM;
import org.jikesrvm.compilers.opt.OptimizingCompilerException;
import org.jikesrvm.compilers.opt.ir.GenericPhysicalRegisterSet;
import org.jikesrvm.compilers.opt.ir.Register;
import org.jikesrvm.compilers.opt.regalloc.ia32.PhysicalRegisterConstants;
import org.jikesrvm.compilers.opt.util.BitSet;
import org.jikesrvm.compilers.opt.util.CompoundEnumerator;
import org.jikesrvm.compilers.opt.util.EmptyEnumerator;
import org.jikesrvm.compilers.opt.util.ReverseEnumerator;
import org.jikesrvm.ia32.ArchConstants;
import org.jikesrvm.ia32.RegisterConstants;

/**
 * This class represents a set of Registers corresponding to the
 * IA32 register set.
 */
public abstract class PhysicalRegisterSet extends GenericPhysicalRegisterSet
    implements RegisterConstants, PhysicalRegisterConstants {

  /**
   * This array holds a pool of objects representing physical registers
   */
  private final Register[] reg = new Register[getSize()];

  /**
   * Cache the set of volatile registers for efficiency
   */
  private final BitSet volatileSet;

  /**
   * Cache the set of floating-point registers for efficiency
   */
  private final BitSet fpSet;

  /**
   * Return the total number of physical registers.
   */
  public static int getSize() {
    return NUM_GPRS + NUM_FPRS + NUM_SPECIALS;
  }

  /**
   * Return the total number of physical registers.
   */
  public final int getNumberOfPhysicalRegisters() {
    return getSize();
  }

  /**
   * Return the total number of nonvolatile GPRs.
   */
  public static int getNumberOfNonvolatileGPRs() {
    return NUM_NONVOLATILE_GPRS;
  }

  /**
   * Return the total number of GPRs that may hold parameters.
   */
  public static int getNumberOfGPRParams() {
    return NUM_PARAMETER_GPRS;
  }

  /**
   * Return the total number of FPRs that may hold parameters.
   */
  public static int getNumberOfFPRParams() {
    return NUM_PARAMETER_FPRS;
  }

  /**
   * Return the (zero-based indexed) nth GPR that may hold a parameter.
   */
  public final Register getGPRParam(int n) {
    if (VM.VerifyAssertions) VM._assert(n < 2);
    if (n == 0) {
      return getEAX();
    } else {
      return getEDX();
    }
  }

  /**
   * Return the (zero-based indexed) nth FPR that may hold a parameter.
   */
  public final Register getFPRParam(int n) {
    return getFPR(VOLATILE_FPRS[n]);
  }

  /**
   * Return the (zero-based indexed) nth GPR that may hold a return value.
   */
  public Register getReturnGPR(int n) {
    if (VM.VerifyAssertions) VM._assert(n < 2);
    if (n == 0) {
      return getEAX();
    } else {
      return getEDX();
    }
  }

  /**
   * Constructor: set up a pool of physical registers.
   */
  protected PhysicalRegisterSet() {

    // 1. Create all the physical registers in the pool.
    for (int i = 0; i < reg.length; i++) {
      Register r = new Register(i);
      r.setPhysical();
      reg[i] = r;
    }

    // 2. Set the 'integer' attribute on each GPR
    for (int i = FIRST_INT; i < FIRST_DOUBLE; i++) {
      reg[i].setInteger();
    }

    // 3. Set the 'double' attribute on each FPR
    for (int i = FIRST_DOUBLE; i < FIRST_SPECIAL; i++) {
      reg[i].setDouble();
    }

    // 4. set up the volatile GPRs
    for (Enumeration<Register> e = enumerateVolatileGPRs(); e.hasMoreElements();) {
      Register r = e.nextElement();
      r.setVolatile();
    }

    // 5. set up the non-volatile GPRs
    for (Enumeration<Register> e = enumerateNonvolatileGPRs(); e.hasMoreElements();) {
      Register r = e.nextElement();
      r.setNonVolatile();
    }

    // 6. set properties on some special registers
    reg[AF].setSpansBasicBlock();
    reg[CF].setSpansBasicBlock();
    reg[OF].setSpansBasicBlock();
    reg[PF].setSpansBasicBlock();
    reg[SF].setSpansBasicBlock();
    reg[ZF].setSpansBasicBlock();
    reg[C0].setSpansBasicBlock();
    reg[C1].setSpansBasicBlock();
    reg[C2].setSpansBasicBlock();
    reg[C3].setSpansBasicBlock();
    reg[PROCESSOR_REGISTER.value()].setSpansBasicBlock();

    // For SSE2
    reg[ST0].setDouble();
    reg[ST1].setDouble();

    // 7. set up the volatile FPRs
    for (Enumeration<Register> e = enumerateVolatileFPRs(); e.hasMoreElements();) {
      Register r = e.nextElement();
      r.setVolatile();
    }

    // 8. set up the non-volatile FPRs
    for (Enumeration<Register> e = enumerateNonvolatileFPRs(); e.hasMoreElements();) {
      Register r = e.nextElement();
      r.setNonVolatile();
    }

    // 9. Cache the volatile registers for efficiency
    volatileSet = new BitSet(this);
    for (Enumeration<Register> e = enumerateVolatiles(); e.hasMoreElements();) {
      Register r = e.nextElement();
      volatileSet.add(r);
    }

    // 10. Cache the FPRs for efficiency
    fpSet = new BitSet(this);
    for (Enumeration<Register> e = enumerateFPRs(); e.hasMoreElements();) {
      Register r = e.nextElement();
      fpSet.add(r);
    }

    // Note no registers are excluded from live analysis (as is done for PPC)

  }

  /**
   * Is a particular register subject to allocation?
   */
  public boolean isAllocatable(Register r) {
    return (r.number < FIRST_SPECIAL && r != getPR() && r != getESP());
  }

  /**
   * @return the processor register
   */
  public Register getPR() {
    return getGPR(PROCESSOR_REGISTER);
  }

  /**
   * @return the frame pointer register
   */
  public Register getFP() {
    throw new OptimizingCompilerException("Framepointer is not a register on IA32");
  }

  /**
   * @return the EAX register
   */
  public Register getEAX() {
    return getGPR(EAX);
  }

  /**
   * @return the ECX register
   */
  public Register getECX() {
    return getGPR(ECX);
  }

  /**
   * @return the EDX register
   */
  public Register getEDX() {
    return getGPR(EDX);
  }

  /**
   * @return the EBX register
   */
  public Register getEBX() {
    return getGPR(EBX);
  }

  /**
   * @return the ESP register
   */
  public Register getESP() {
    return getGPR(ESP);
  }

  /**
   * @return the EBP register
   */
  public Register getEBP() {
    return getGPR(EBP);
  }

  /**
   * @return the ESI register
   */
  public Register getESI() {
    return getGPR(ESI);
  }

  /**
   * @return the EDI register
   */
  public Register getEDI() {
    return getGPR(EDI);
  }

  /**
   * @return a register representing the AF bit of the EFLAGS register.
   */
  public Register getAF() {
    return reg[AF];
  }

  /**
   * @return a register representing the CF bit of the EFLAGS register.
   */
  public Register getCF() {
    return reg[CF];
  }

  /**
   * @return a register representing the OF bit of the EFLAGS register.
   */
  public Register getOF() {
    return reg[OF];
  }

  /**
   * @return a register representing the PF bit of the EFLAGS register.
   */
  public Register getPF() {
    return reg[PF];
  }

  /**
   * @return a register representing the SF bit of the EFLAGS register.
   */
  public Register getSF() {
    return reg[SF];
  }

  /**
   * @return a register representing the ZF bit of the EFLAGS register.
   */
  public Register getZF() {
    return reg[ZF];
  }

  /**
   * @return a register representing the C0 floating-point status bit
   */
  public Register getC0() {
    return reg[C0];
  }

  /**
   * @return a register representing the C1 floating-point status bit
   */
  public Register getC1() {
    return reg[C1];
  }

  /**
   * @return a register representing the C2 floating-point status bit
   */
  public Register getC2() {
    return reg[C2];
  }

  /**
   * @return a register representing the C3 floating-point status bit
   */
  public Register getC3() {
    return reg[C3];
  }

  /**
   * @return the nth physical GPR
   */
  public Register getGPR(GPR n) {
    return reg[FIRST_INT + n.value()];
  }

  /**
   * @return the nth physical GPR
   */
  public Register getGPR(int n) {
    return reg[FIRST_INT + n];
  }

  /**
   * @return the index into the GPR set corresponding to a given register.
   *
   * PRECONDITION: r is a physical GPR
   */
  public static int getGPRIndex(Register r) {
    return r.number - FIRST_INT;
  }

  /**
   * @return the first GPR register used to hold a return value
   */
  public Register getFirstReturnGPR() {
    if (VM.VerifyAssertions) VM._assert(NUM_RETURN_GPRS > 0);
    return getEAX();
  }

  /**
   * @return the second GPR register used to hold a return value
   */
  public Register getSecondReturnGPR() {
    if (VM.VerifyAssertions) VM._assert(NUM_RETURN_GPRS > 1);
    return getEDX();
  }

  /**
   * @return the FPR register used to hold a return value
   */
  public Register getST0() {
    if (VM.VerifyAssertions) VM._assert(NUM_RETURN_FPRS == 1);
    if (VM.VerifyAssertions) VM._assert(ArchConstants.SSE2_FULL);
    return reg[ST0];
  }
  /**
   * @return the special ST1 x87 register
   */
  public Register getST1() {
    if (VM.VerifyAssertions) VM._assert(ArchConstants.SSE2_FULL);
    return reg[ST1];
  }

  /**
   * @return the FPR register used to hold a return value
   */
  public Register getReturnFPR() {
    if (VM.VerifyAssertions) VM._assert(NUM_RETURN_FPRS == 1);
    return getFPR(0);
  }

  /**
   * @return the nth physical FPR
   */
  public Register getFPR(FPR n) {
    return reg[FIRST_DOUBLE + n.value()];
  }

  /**
   * @return the nth physical FPR
   */
  public Register getFPR(int n) {
    return reg[FIRST_DOUBLE + n];
  }

  /**
   * @return the index into the GPR set corresponding to a given register.
   *
   * PRECONDITION: r is a physical GPR
   */
  public static int getFPRIndex(Register r) {
    return r.number - FIRST_DOUBLE;
  }

  /**
   * @return the nth physical register in the pool.
   */
  public Register get(int n) {
    return reg[n];
  }

  /**
   * Given a symbolic register, return a code that gives the physical
   * register type to hold the value of the symbolic register.
   * @param r a symbolic register
   * @return one of INT_REG, DOUBLE_REG
   */
  public static int getPhysicalRegisterType(Register r) {
    if (r.isInteger() || r.isLong() || r.isAddress()) {
      return INT_REG;
    } else if (r.isFloatingPoint()) {
      return DOUBLE_REG;
    } else {
      throw new OptimizingCompilerException("getPhysicalRegisterType " + " unexpected " + r);
    }
  }

  /**
   * Register names for each class. used in printing the IR
   */
  private static final String[] registerName = new String[getSize()];

  static {
    String[] regName = registerName;
    for (GPR r : GPR.values()) {
      regName[r.ordinal() + FIRST_INT] = r.toString();
    }
    if (ArchConstants.SSE2_FULL) {
      for (XMM r : XMM.values()) {
        regName[r.ordinal() + FIRST_DOUBLE] = r.toString();
      }
    } else {
      for (FPR r : FPR.values()) {
        regName[r.ordinal() + FIRST_DOUBLE] = r.toString();
      }
    }
    regName[PROCESSOR_REGISTER.value()] = "PR";
    regName[AF] = "AF";
    regName[CF] = "CF";
    regName[OF] = "OF";
    regName[PF] = "PF";
    regName[SF] = "SF";
    regName[ZF] = "ZF";
    regName[ST0] = "ST0";
    regName[ST1] = "ST1";
  }

  /**
   * Get the register name for a register with a particular number in the
   * pool
   */
  public static String getName(int number) {
    return registerName[number];
  }

  /**
   * Get the spill size for a register with a particular type
   * @param type one of INT_REG, DOUBLE_REG, SPECIAL_REG
   */
  public static int getSpillSize(int type) {
    if (VM.VerifyAssertions) {
      VM._assert((type == INT_REG) || (type == DOUBLE_REG) || (type == SPECIAL_REG));
    }
    if (type == DOUBLE_REG) {
      return 8;
    } else {
      return 4;
    }
  }

  /**
   * Get the required spill alignment for a register with a particular type
   * @param type one of INT_REG, DOUBLE_REG,  SPECIAL_REG
   */
  public static int getSpillAlignment(int type) {
    if (VM.VerifyAssertions) {
      VM._assert((type == INT_REG) || (type == DOUBLE_REG) || (type == SPECIAL_REG));
    }
    if (type == DOUBLE_REG) {
      return 8;
    } else {
      return 4;
    }
  }

  /**
   * Enumerate all the physical registers in this set.
   */
  public Enumeration<Register> enumerateAll() {
    return new RangeEnumeration(0, getSize() - 1);
  }

  /**
   * Enumerate all the GPRs in this set.
   */
  public Enumeration<Register> enumerateGPRs() {
    return new RangeEnumeration(FIRST_INT, FIRST_DOUBLE - 1);
  }

  /**
   * Enumerate all the GPRs in this set.
   */
  public Enumeration<Register> enumerateFPRs() {
    return new RangeEnumeration(FIRST_DOUBLE, FIRST_SPECIAL - 1);
  }

  /**
   * Enumerate all the volatile GPRs in this set.
   */
  public PhysicalRegisterEnumeration enumerateVolatileGPRs() {
    Register[] r = new Register[NUM_VOLATILE_GPRS];
    for (int i = 0; i < NUM_VOLATILE_GPRS; i++) {
      r[i] = getGPR(VOLATILE_GPRS[i]);
    }
    return new PhysicalRegisterEnumeration(r);
  }

  /**
   * Enumerate all the nonvolatile GPRs in this set.
   */
  public PhysicalRegisterEnumeration enumerateNonvolatileGPRs() {
    Register[] r = new Register[NUM_NONVOLATILE_GPRS];
    for (int i = 0; i < NUM_NONVOLATILE_GPRS; i++) {
      r[i] = getGPR(NONVOLATILE_GPRS[i]);
    }
    return new PhysicalRegisterEnumeration(r);
  }

  /**
   * Enumerate the nonvolatile GPRS backwards
   */
  public Enumeration<Register> enumerateNonvolatileGPRsBackwards() {
    return new ReverseEnumerator<Register>(enumerateNonvolatileGPRs());
  }

  /**
   * Enumerate all the volatile FPRs in this set.
   */
  public PhysicalRegisterEnumeration enumerateVolatileFPRs() {
    Register[] r = new Register[NUM_VOLATILE_FPRS];
    for (int i = 0; i < NUM_VOLATILE_FPRS; i++) {
      r[i] = getFPR(VOLATILE_FPRS[i]);
    }
    return new PhysicalRegisterEnumeration(r);
  }

  /**
   * Enumerate all the nonvolatile FPRs in this set.
   */
  public PhysicalRegisterEnumeration enumerateNonvolatileFPRs() {
    Register[] r = new Register[NUM_NONVOLATILE_FPRS];
    for (int i = 0; i < NUM_NONVOLATILE_FPRS; i++) {
      r[i] = getFPR(NONVOLATILE_FPRS[i]);
    }
    return new PhysicalRegisterEnumeration(r);
  }

  /**
   * Enumerate the volatile physical registers of a given class.
   * @param regClass one of INT_REG, DOUBLE_REG, SPECIAL_REG
   */
  public Enumeration<Register> enumerateVolatiles(int regClass) {
    switch (regClass) {
      case INT_REG:
        return enumerateVolatileGPRs();
      case DOUBLE_REG:
        return enumerateVolatileFPRs();
      case SPECIAL_REG:
        return EmptyEnumerator.emptyEnumeration();
      default:
        throw new OptimizingCompilerException("Unsupported volatile type");
    }
  }

  /**
   * Enumerate all the volatile physical registers
   */
  public Enumeration<Register> enumerateVolatiles() {
    Enumeration<Register> e1 = enumerateVolatileGPRs();
    Enumeration<Register> e2 = enumerateVolatileFPRs();
    return new CompoundEnumerator<Register>(e1, e2);
  }

  /**
   * @return the set of volatile physical registers
   */
  public BitSet getVolatiles() {
    return volatileSet;
  }

  /**
   * @return the set of FPR physical registers
   */
  public BitSet getFPRs() {
    return fpSet;
  }

  /**
   * Enumerate the nonvolatile physical registers of a given class.
   * @param regClass one of INT_REG, DOUBLE_REG, SPECIAL_REG
   */
  public Enumeration<Register> enumerateNonvolatiles(int regClass) {
    switch (regClass) {
      case INT_REG:
        return enumerateNonvolatileGPRs();
      case DOUBLE_REG:
        return enumerateNonvolatileFPRs();
      case SPECIAL_REG:
        return EmptyEnumerator.emptyEnumeration();
      default:
        throw new OptimizingCompilerException("Unsupported non-volatile type");
    }
  }

  /**
   * Enumerate the nonvolatile physical registers of a given class,
   * backwards
   * @param regClass one of INT_REG, DOUBLE_REG, SPECIAL_REG
   */
  public Enumeration<Register> enumerateNonvolatilesBackwards(int regClass) {
    return new ReverseEnumerator<Register>(enumerateNonvolatiles(regClass));
  }

  /**
   * An enumerator for use by the physical register utilities.
   */
  static final class PhysicalRegisterEnumeration implements Enumeration<Register> {
    private int index;
    private final Register[] r;

    PhysicalRegisterEnumeration(Register[] r) {
      this.r = r;
      this.index = 0;
    }

    public Register nextElement() {
      return r[index++];
    }

    public boolean hasMoreElements() {
      return (index < r.length);
    }
  }

  /**
   * An enumerator for use by the physical register utilities.
   */
  final class RangeEnumeration implements Enumeration<Register> {
    private final int end;
    private int index;
    private final int exclude; // an index in the register range to exclude

    RangeEnumeration(int start, int end) {
      this.end = end;
      this.exclude = -1;
      this.index = start;
    }

    RangeEnumeration(int start, int end, int exclude) {
      this.end = end;
      this.exclude = exclude;
      this.index = start;
    }

    public Register nextElement() {
      if (index == exclude) index++;
      return reg[index++];
    }

    public boolean hasMoreElements() {
      if (index == exclude) index++;
      return (index <= end);
    }
  }
}
