/*
 *  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 org.jikesrvm.VM;
import org.jikesrvm.classloader.RVMField;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.driver.CompilerPhase;
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.PutField;
import org.jikesrvm.compilers.opt.ir.PutStatic;
import org.jikesrvm.compilers.opt.ir.operand.LocationOperand;
import org.jikesrvm.compilers.opt.ir.operand.Operand;

/**
 * Flow-insensitive, context-insensitive, interprocedural analysis
 * of fields.
 *
 * <ul>
 * <li> TODO: handle more than just private fields
 * <li> TODO: make this re-entrant
 * <li> TODO: better class hierarchy analysis
 * <li> TODO: context-sensitive or flow-sensitive summaries.
 * <li> TODO: force eager analysis of methods
 * </ul>
 */
public final class FieldAnalysis extends CompilerPhase {
  private static final boolean DEBUG = false;

  /**
   * Return this instance of this phase. This phase contains no
   * per-compilation instance fields.
   * @param ir not used
   * @return this
   */
  public CompilerPhase newExecution(IR ir) {
    return this;
  }

  public boolean shouldPerform(OptOptions options) {
    return options.FIELD_ANALYSIS;
  }

  public String getName() {
    return "Field Analysis";
  }

  /**
   * Is a type a candidate for type analysis?
   * <p> NO iff:
   * <ul>
   *    <li> it's a primitive
   *    <li> it's final
   *    <li> it's an array of primitive
   *    <li> it's an array of final
   * </ul>
   */
  private static boolean isCandidate(TypeReference tref) {
    RVMType t = tref.peekType();
    if (t == null) return false;
    if (t.isPrimitiveType()) {
      return false;
    }
    if (t.isClassType() && t.asClass().isFinal()) {
      return false;
    }
    if (t.isArrayType()) {
      return isCandidate(tref.getInnermostElementType());
    }
    return true;
  }

  /**
   * Have we determined a single concrete type for a field? If so,
   * return the concrete type.  Else, return null.
   */
  public static TypeReference getConcreteType(RVMField f) {
    // don't bother for primitives and arrays of primitives
    // and friends
    if (!isCandidate(f.getType())) {
      return f.getType();
    }
    // for some special classes, the flow-insensitive summaries
    // are INCORRECT due to using the wrong implementation
    // during boot image writing.  For these special cases,
    // give up.
    if (isTrouble(f)) {
      return null;
    }
    if (DEBUG) {
      TypeReference t = db.getConcreteType(f);
      if (t != null) VM.sysWriteln("CONCRETE TYPE " + f + " IS " + t);
    }
    return db.getConcreteType(f);
  }

  /**
   * Record field analysis information for an IR.
   *
   * @param ir the governing IR
   */
  public void perform(IR ir) {
    // walk over each instructions.  For each putfield or putstatic,
    // record the concrete type assigned to a field; or, record
    // BOTTOM if the concrete type is unknown.
    for (InstructionEnumeration e = ir.forwardInstrEnumerator(); e.hasMoreElements();) {
      Instruction s = e.next();
      if (PutField.conforms(s)) {
        LocationOperand l = PutField.getLocation(s);
        RVMField f = l.getFieldRef().peekResolvedField();
        if (f == null) continue;
        if (!isCandidate(f.getType())) continue;
        // a little tricky: we cannot draw any conclusions from inlined
        // method bodies, since we cannot assume what information,
        // gleaned from context, does not hold everywhere
        if (s.position.getMethod() != ir.method) {
          continue;
        }
        Operand value = PutField.getValue(s);
        if (value.isRegister()) {
          if (value.asRegister().isPreciseType()) {
            TypeReference type = value.asRegister().getType();
            recordConcreteType(ir.method, f, type);
          } else {
            recordBottom(ir.method, f);
          }
        }
      } else if (PutStatic.conforms(s)) {
        LocationOperand l = PutStatic.getLocation(s);
        RVMField f = l.getFieldRef().peekResolvedField();
        if (f == null) continue;
        if (!isCandidate(f.getType())) continue;
        // a little tricky: we cannot draw any conclusions from inlined
        // method bodies, since we cannot assume what information,
        // gleaned from context, does not hold everywhere
        if (s.position.getMethod() != ir.method) {
          continue;
        }
        Operand value = PutStatic.getValue(s);
        if (value.isRegister()) {
          if (value.asRegister().isPreciseType()) {
            TypeReference type = value.asRegister().getType();
            recordConcreteType(ir.method, f, type);
          } else {
            recordBottom(ir.method, f);
          }
        }
      }
    }
  }

  /**
   * The backing store
   */
  private static final FieldDatabase db = new FieldDatabase();

  /**
   * Record that a method writes an unknown concrete type to a field.
   */
  private static synchronized void recordBottom(RVMMethod m, RVMField f) {
    // for now, only track private fields
    if (!f.isPrivate()) {
      return;
    }
    if (isTrouble(f)) {
      return;
    }
    FieldDatabase.FieldDatabaseEntry entry = db.findOrCreateEntry(f);
    FieldDatabase.FieldWriterInfo info = entry.findMethodInfo(m);
    if (VM.VerifyAssertions) {
      if (info == null) {
        VM.sysWrite("ERROR recordBottom: method " + m + " field " + f);
      }
      VM._assert(info != null);
    }
    info.setBottom();
    info.setAnalyzed();
  }

  /**
   * Record that a method stores an object of a particular concrete type
   * to a field.
   */
  private static synchronized void recordConcreteType(RVMMethod m, RVMField f, TypeReference t) {
    // for now, only track private fields
    if (!f.isPrivate()) {
      return;
    }
    FieldDatabase.FieldDatabaseEntry entry = db.findOrCreateEntry(f);
    FieldDatabase.FieldWriterInfo info = entry.findMethodInfo(m);
    info.setAnalyzed();
    if (info.isBottom()) {
      return;
    }
    TypeReference oldType = info.concreteType;
    if (oldType == null) {
      // set a new concrete type for this field.
      info.concreteType = t;
    } else if (oldType != t) {
      // we've previously determined a DIFFERENT! concrete type.
      // meet the two types: ie., change it to bottom.
      info.setBottom();
    }
  }

  /**
   * For some special classes, the flow-insensitive summaries
   * are INCORRECT due to using the wrong implementation
   * during boot image writing.  For these special cases,
   * give up. TODO: work around this by recomputing the summary at
   * runtime?
   */
  private static boolean isTrouble(RVMField f) {
    return f.getDeclaringClass() == RVMType.JavaLangStringType;
  }
}
