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

import org.jikesrvm.VM;
import org.jikesrvm.classloader.RVMField;
import org.jikesrvm.classloader.FieldReference;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.ClassLoaderProxy;
import org.jikesrvm.compilers.opt.OptimizingCompilerException;
import org.vmmagic.unboxed.Offset;

/**
 * Represents a location in memory. Used to keep track of memory aliasing.
 *
 * @see Operand
 */
public final class LocationOperand extends Operand implements org.jikesrvm.compilers.opt.driver.OptConstants {

  /*
   * TODO: Now that we don't pay a large penalty for dynamic type checks
   * of non-final classes, redesign this mess to have separate subclasses
   * of location operands for each type of memory access.
   * In the process, also switch to using synthetic Fields
   * for the various pieces of the object header
   * (something like the following might work):
   *   (RVMField) VM.getMember("[I", "length", "I");   .
   *      . . .                                        } all primitive types
   * (RVMField) VM.getMember("[J", "length", "I");   '
   * (RVMField) VM.getMember("[Ljava/lang/Object;", "length", "I");
   * (RVMField) VM.getMember("Ljava/lang/Object;", "TIB", "[I");
   */

  /** Enumeration of Access type */
  public static final int FIELD_ACCESS = 0;
  /** Enumeration of Access type */
  public static final int ARRAY_ACCESS = 1;
  /** Enumeration of Access type */
  public static final int JTOC_ACCESS = 2;
  /** Enumeration of Access type */
  public static final int SPILL_ACCESS = 3;
  /** Enumeration of Access type */
  public static final int ALENGTH_ACCESS = 4;
  /** Enumeration of Access type */
  public static final int METHOD_ACCESS = 5;

  /**
   * The type of this location.
   */
  int type;

  /**
   * Field that corresponds to this location;
   * null if this is not a field access.
   */
  FieldReference fieldRef;

  /**
   * Method operand that corresponds to this location;
   * null if this is not a method access.
   */
  MethodOperand methOp;

  /**
   * Array element type that corresponds to the type of the array that contains
   * this location; null if this is not an array access.
   */
  TypeReference arrayElementType;

  /**
   * JTOC index that corresponds to this location.
   * -1 if this is not a JTOC access.
   */
  Offset JTOCoffset = Offset.max();

  /**
   * Spill offset that corresponds to this location.
   * -1 if this is not a spill access.
   */
  int spillOffset = -1;

  /**
   * Constructs a new location operand with the given field.
   * @param loc location
   */
  public LocationOperand(FieldReference loc) {
    type = FIELD_ACCESS;
    fieldRef = loc;
  }

  /**
   * Constructs a new location operand with the given field
   * @param loc location
   */
  public LocationOperand(RVMField loc) {
    type = FIELD_ACCESS;
    fieldRef = loc.getMemberRef().asFieldReference();
  }

  /**
   * Constructs a new location operand with the given method
   *
   * @param m  Method operand that corresponds to this location
   */
  public LocationOperand(MethodOperand m) {
    type = METHOD_ACCESS;
    methOp = m;
  }

  /**
   * Constructs a new location operand with the given array element type.
   *
   * @param t    Array element type
   */
  public LocationOperand(TypeReference t) {
    type = ARRAY_ACCESS;
    arrayElementType = t;
  }

  /**
   * Constructs a new location operand with the given JTOC offset
   */
  public LocationOperand(Offset jtocOffset) {
    type = JTOC_ACCESS;
    JTOCoffset = jtocOffset;
  }

  /**
   * Constructs a new location operand with the given spill offset.
   */
  public LocationOperand(int index) {
    if (VM.VerifyAssertions) VM._assert(index <= 0);
    type = SPILL_ACCESS;
    spillOffset = index;
  }

  /**
   * Constructs a new location operand for array length access.
   */
  public LocationOperand() {
    type = ALENGTH_ACCESS;
  }

  /**
   * Return the {@link TypeReference} of the value represented by the operand.
   *
   * @return this method shouldn't be called and will throw an {@link
   * OptimizingCompilerException}
   */
  public TypeReference getType() {
    throw new OptimizingCompilerException("Getting the type for this operand has no defined meaning");
  }

  public LocationOperand asFieldAccess() { return this; }

  public LocationOperand asArrayAccess() { return this; }

  public LocationOperand asJTOCAccess() { return this; }

  public LocationOperand asSpillAccess() { return this; }

  public LocationOperand asALengthAccess() { return this; }

  public LocationOperand asMethodAccess() { return this; }

  public FieldReference getFieldRef() { return fieldRef; }

  public TypeReference getElementType() { return arrayElementType; }

  //public final int getIndex() { return JTOCoffset; }
  public Offset getJTOCoffset() { return JTOCoffset; }

  public int getOffset() { return spillOffset; }

  public boolean isFieldAccess() { return type == FIELD_ACCESS; }

  public boolean isArrayAccess() { return type == ARRAY_ACCESS; }

  public boolean isJTOCAccess() { return type == JTOC_ACCESS; }

  public boolean isSpillAccess() { return type == SPILL_ACCESS; }

  public boolean isALengthAccess() { return type == ALENGTH_ACCESS; }

  public boolean isMethodAccess() { return type == METHOD_ACCESS; }

  /**
   * Is the accessed location possibly volatile?
   */
  public boolean mayBeVolatile() {
    if (!isFieldAccess()) return false;
    RVMField f = fieldRef.peekResolvedField();
    return f == null || f.isVolatile();
  }

  /**
   * Return a new operand that is semantically equivalent to <code>this</code>.
   *
   * @return a copy of <code>this</code>
   */
  public Operand copy() {
    LocationOperand o = null;
    switch (type) {
      case FIELD_ACCESS:
        o = new LocationOperand(fieldRef);
        break;
      case ARRAY_ACCESS:
        o = new LocationOperand(arrayElementType);
        break;
      case JTOC_ACCESS:
        o = new LocationOperand(JTOCoffset);
        break;
      case SPILL_ACCESS:
        o = new LocationOperand(spillOffset);
        break;
      case ALENGTH_ACCESS:
        o = new LocationOperand();
        break;
      case METHOD_ACCESS:
        o = new LocationOperand(methOp);
        break;
      default:
        o = new LocationOperand();
        break;
    }
    return o;
  }

  // NOTE: not checking for (t1==null xor t2==null) for efficiency
  private static boolean arrayMayBeAliased(TypeReference t1, TypeReference t2) {
    return ((t1 == t2) ||
            (ClassLoaderProxy.includesType(t1, t2) != NO) ||
            (ClassLoaderProxy.includesType(t2, t1) != NO));
  }

  /**
   * Returns true if operands op1 and op2 may be aliased.
   *
   * @param op1 the first operand
   * @param op2 the second operand
   * @return <code>true</code> if the operands might be aliased or
   *         <code>false</code> if they are definitely not aliased
   */
  public static boolean mayBeAliased(LocationOperand op1, LocationOperand op2) {
    if (op1 == null || op2 == null) return true;        // be conservative
    if (op1.type != op2.type) return false;
    if (op1.fieldRef != null) {
      return !op1.fieldRef.definitelyDifferent(op2.fieldRef);
    } else {
      return arrayMayBeAliased(op1.arrayElementType, op2.arrayElementType) &&
             (op1.JTOCoffset.EQ(op2.JTOCoffset)) &&
             (op1.spillOffset == op2.spillOffset);
    }
  }

  /**
   * Are two operands semantically equivalent?
   *
   * @param op other operand
   * @return   <code>true</code> if <code>this</code> and <code>op</code>
   *           are semantically equivalent or <code>false</code>
   *           if they are not.
   */
  public boolean similar(Operand op) {
    return (op instanceof LocationOperand) && mayBeAliased(this, (LocationOperand) op);
  }

  /**
   * Returns the string representation of this operand.
   *
   * @return a string representation of this operand.
   */
  public String toString() {
    if (methOp != null) return methOp.toString();
    switch (type) {
      case METHOD_ACCESS:
        return "<mem loc: methOp is null!>";
      case FIELD_ACCESS:
        return "<mem loc: " + fieldRef.getType().getName() + "." + fieldRef.getName() + ">";
      case ARRAY_ACCESS:
        return "<mem loc: array " + arrayElementType + "[]>";
      case JTOC_ACCESS:
        return "<mem loc: JTOC @" + VM.addressAsHexString(JTOCoffset.toWord().toAddress()) + ">";
      case SPILL_ACCESS:
        return "<mem loc: spill FP " + spillOffset + ">";
      case ALENGTH_ACCESS:
        return "<mem loc: array length>";
    }
    return "<mem loc: no aliases>";
  }
}
