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

import org.jikesrvm.ArchitectureSpecific;
import org.jikesrvm.VM;
import org.jikesrvm.Services;
import org.jikesrvm.classloader.MemberReference;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.compilers.baseline.BaselineCompiledMethod;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod;
import org.jikesrvm.compilers.opt.runtimesupport.OptEncodedCallSiteTree;
import org.jikesrvm.compilers.opt.runtimesupport.OptMachineCodeMap;
import org.jikesrvm.objectmodel.MiscHeader;
import org.jikesrvm.objectmodel.ObjectModel;
import org.jikesrvm.objectmodel.TIB;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.scheduler.Scheduler;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.NoInline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.ObjectReference;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Word;

/**
 * Class that supports scanning Objects or Arrays for references
 * during tracing, handling those references, and computing death times
 */
@Uninterruptible public final class TraceInterface extends org.mmtk.vm.TraceInterface implements ArchitectureSpecific.ArchConstants {

  /***********************************************************************
   *
   * Class variables
   */
  private static byte[][] allocCallMethods;

  static {
    /* Build the list of "own methods" */
    allocCallMethods = new byte[13][];
    allocCallMethods[0] = "postAlloc".getBytes();
    allocCallMethods[1] = "traceAlloc".getBytes();
    allocCallMethods[2] = "allocateScalar".getBytes();
    allocCallMethods[3] = "allocateArray".getBytes();
    allocCallMethods[4] = "clone".getBytes();
    allocCallMethods[5] = "alloc".getBytes();
    allocCallMethods[6] = "buildMultiDimensionalArray".getBytes();
    allocCallMethods[7] = "resolvedNewScalar".getBytes();
    allocCallMethods[8] = "resolvedNewArray".getBytes();
    allocCallMethods[9] = "unresolvedNewScalar".getBytes();
    allocCallMethods[10] = "unresolvedNewArray".getBytes();
    allocCallMethods[11] = "cloneScalar".getBytes();
    allocCallMethods[12] = "cloneArray".getBytes();
  }

  /***********************************************************************
   *
   * Public Methods
   */

  /**
   * Returns if the VM is ready for a garbage collection.
   *
   * @return True if the RVM is ready for GC, false otherwise.
   */
  public boolean gcEnabled() {
    return Scheduler.gcEnabled();
  }

  /**
   * Given a method name, determine if it is a "real" method or one
   * used for allocation/tracing.
   *
   * @param name The method name to test as an array of bytes
   * @return True if the method is a "real" method, false otherwise.
   */
  private boolean isAllocCall(byte[] name) {
    for (int i = 0; i < allocCallMethods.length; i++) {
      byte[] funcName = Services.getArrayNoBarrier(allocCallMethods, i);
      if (Magic.getArrayLength(name) == Magic.getArrayLength(funcName)) {
        /* Compare the letters in the allocCallMethod */
        int j = Magic.getArrayLength(funcName) - 1;
        while (j >= 0) {
          if (Services.getArrayNoBarrier(name, j) !=
              Services.getArrayNoBarrier(funcName, j))
            break;
          j--;
        }
        if (j == -1)
          return true;
      }
    }
    return false;
  }

  /**
   * This adjusts the offset into an object to reflect what it would look like
   * if the fields were laid out in memory space immediately after the object
   * pointer.
   *
   * @param isScalar If this is a pointer store to a scalar object
   * @param src The address of the source object
   * @param slot The address within <code>src</code> into which
   * the update will be stored
   * @return The easy to understand offset of the slot
   */
  public Offset adjustSlotOffset(boolean isScalar,
                                              ObjectReference src,
                                              Address slot) {
    /* Offset scalar objects so that the fields appear to begin at offset 0
       of the object. */
    Offset offset = slot.diff(src.toAddress());
    if (isScalar)
      return offset.minus(getHeaderEndOffset());
    else
      return offset;
  }

  /**
   * This skips over the frames added by the tracing algorithm, outputs
   * information identifying the method the containts the "new" call triggering
   * the allocation, and returns the address of the first non-trace, non-alloc
   * stack frame.
   *
   * @param typeRef The type reference (tib) of the object just allocated
   * @return The frame pointer address for the method that allocated the object
   */
  @NoInline
  @Interruptible // This can't be uninterruptible --- it is an IO routine
  public Address skipOwnFramesAndDump(ObjectReference typeRef) {
    TIB tib = Magic.addressAsTIB(typeRef.toAddress());
    RVMMethod m = null;
    int bci = -1;
    int compiledMethodID = 0;
    Offset ipOffset = Offset.zero();
    Address fp = Magic.getFramePointer();
    Address ip = Magic.getReturnAddress(fp);
    fp = Magic.getCallerFramePointer(fp);
    // This code borrows heavily from Scheduler.dumpStack
    while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) {
      compiledMethodID = Magic.getCompiledMethodID(fp);
      if (compiledMethodID != INVISIBLE_METHOD_ID) {
        // normal java frame(s)
        CompiledMethod compiledMethod =
          CompiledMethods.getCompiledMethod(compiledMethodID);
        if (compiledMethod.getCompilerType() != CompiledMethod.TRAP) {
          ipOffset = compiledMethod.getInstructionOffset(ip);
          m = compiledMethod.getMethod();
          if (VM.BuildForOptCompiler && compiledMethod.getCompilerType() == CompiledMethod.OPT) {
            OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod;
            /* Opt stack frames may contain multiple inlined methods. */
            OptMachineCodeMap map = optInfo.getMCMap();
            int iei = map.getInlineEncodingForMCOffset(ipOffset);
            if (iei >= 0) {
              int[] inlineEncoding = map.inlineEncoding;
              boolean allocCall = true;
              bci = map.getBytecodeIndexForMCOffset(ipOffset);
              for (int j = iei; j >= 0 && allocCall;
                   j = OptEncodedCallSiteTree.getParent(j,inlineEncoding)) {
                int mid = OptEncodedCallSiteTree.getMethodID(j, inlineEncoding);
                m = MemberReference.getMemberRef(mid).asMethodReference().getResolvedMember();
                if (!isAllocCall(m.getName().getBytes()))
                  allocCall = false;
                if (j > 0)
                  bci = OptEncodedCallSiteTree.getByteCodeOffset(j,
                                                                    inlineEncoding);
              }
              if (!allocCall)
                break;
            }
          } else {
            if (!isAllocCall(m.getName().getBytes())) {
              BaselineCompiledMethod baseInfo =
                (BaselineCompiledMethod)compiledMethod;
              bci = baseInfo.findBytecodeIndexForInstruction(ipOffset.toWord().lsh(INSTRUCTION_WIDTH).toOffset());
              break;
            }
          }
        }
      }
      ip = Magic.getReturnAddress(fp);
      fp = Magic.getCallerFramePointer(fp);
    }
    if (m != null) {
      int allocid = (((compiledMethodID & 0x0000ffff) << 15) ^
                     ((compiledMethodID & 0xffff0000) >> 16) ^
                     ipOffset.toInt()) & ~0x80000000;

      /* Now print the location string. */
      VM.sysWrite('\n');
      VM.writeHex(allocid);
      VM.sysWrite('-');
      VM.sysWrite('>');
      VM.sysWrite('[');
      VM.writeHex(compiledMethodID);
      VM.sysWrite(']');
      m.getDeclaringClass().getDescriptor().sysWrite();
      VM.sysWrite(':');
      m.getName().sysWrite();
      m.getDescriptor().sysWrite();
      VM.sysWrite(':');
      VM.writeHex(bci);
      VM.sysWrite('\t');
      RVMType type = tib.getType();
      type.getDescriptor().sysWrite();
      VM.sysWrite('\n');
    }
    return fp;
  }

  /***********************************************************************
   *
   * Wrapper methods
   */

  @Inline
  public void updateDeathTime(ObjectReference obj) {
    MiscHeader.updateDeathTime(obj.toObject());
  }

  @Inline
  public void setDeathTime(ObjectReference ref, Word time_) {
    MiscHeader.setDeathTime(ref.toObject(), time_);
  }

  @Inline
  public void setLink(ObjectReference ref, ObjectReference link) {
    MiscHeader.setLink(ref.toObject(), link);
  }

  @Inline
  public void updateTime(Word time_) {
    MiscHeader.updateTime(time_);
  }

  @Inline
  public Word getOID(ObjectReference ref) {
    return MiscHeader.getOID(ref.toObject());
  }

  @Inline
  public Word getDeathTime(ObjectReference ref) {
    return MiscHeader.getDeathTime(ref.toObject());
  }

  @Inline
  public ObjectReference getLink(ObjectReference ref) {
    return MiscHeader.getLink(ref.toObject());
  }

  @Inline
  public Address getBootImageLink() {
    return MiscHeader.getBootImageLink();
  }

  @Inline
  public Word getOID() {
    return MiscHeader.getOID();
  }

  @Inline
  public void setOID(Word oid) {
    MiscHeader.setOID(oid);
  }

  @Inline
  public int getHeaderSize() {
    return MiscHeader.getHeaderSize();
  }

  @Inline
  public int getHeaderEndOffset() {
    return ObjectModel.getHeaderEndOffset();
  }
}
