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

import org.jikesrvm.VM;
import org.jikesrvm.PrintLN;
import org.jikesrvm.ArchitectureSpecific.BaselineConstants;
import org.jikesrvm.ArchitectureSpecific.BaselineExceptionDeliverer;
import org.jikesrvm.ArchitectureSpecific.BaselineCompilerImpl;
import org.jikesrvm.classloader.RVMArray;
import org.jikesrvm.classloader.ExceptionHandlerMap;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.common.ExceptionTable;
import org.jikesrvm.runtime.DynamicLink;
import org.jikesrvm.runtime.ExceptionDeliverer;
import org.jikesrvm.runtime.StackBrowser;
import org.vmmagic.pragma.SynchronizedObject;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.pragma.Unpreemptible;
import org.vmmagic.unboxed.Offset;

/**
 * Compiler-specific information associated with a method's machine
 * instructions.
 */
@SynchronizedObject
public final class BaselineCompiledMethod extends CompiledMethod implements BaselineConstants {

  /** Does the baseline compiled method have a counters array? */
  private boolean hasCounters;
  /**
   * The lock acquistion offset for synchronized methods.  For
   * synchronized methods, the offset (in the method prologue) after
   * which the monitor has been obtained.  At, or before, this point,
   * the method does not own the lock.  Used by deliverException to
   * determine whether the lock needs to be released.  Note: for this
   * scheme to work, Lock must not allow a yield after it has been
   * obtained.
   */
  private char lockOffset;

  /**
   * Baseline exception deliverer object
   */
  private static final ExceptionDeliverer exceptionDeliverer = new BaselineExceptionDeliverer();

  /**
   * Stack-slot reference maps for the compiled method.
   */
  public ReferenceMaps referenceMaps;

  /*
   * Currently needed to support dynamic bridge magic;
   * Consider integrating with GC maps
   */
  private byte[] bytecodeMap;

  /**
   * Exception table, null if not present.
   */
  private int[] eTable;

  /* To make a compiled method's stack offset independ of
   * original method, we move 'getEmptyStackOffset'
   * here.
   *
   * TODO: redesign this.  There has to be a cleaner way!
   */
  //private int startLocalOffset;
  private final int emptyStackOffset;
  private int lastFixedStackRegister;
  private int lastFloatStackRegister;

  private final int[] localFixedLocations;
  private final int[] localFloatLocations;

//  public int getStartLocalOffset() {
//    return startLocalOffset;
//  }

  public int getEmptyStackOffset() {
    return emptyStackOffset;
  }

  //These Locations are positioned at the top of the stackslot that contains the value
  //before accessing, substract size of value you want to access
  //e.g. to load int: load at BaselineCompilerImpl.locationToOffset(location) - BYTES_IN_INT
  //e.g. to load double: load at BaselineCompilerImpl.locationToOffset(location) - BYTES_IN_DOUBLE
  @Uninterruptible
  public int getGeneralLocalLocation(int localIndex) {
    return BaselineCompilerImpl.getGeneralLocalLocation(localIndex, localFixedLocations, (NormalMethod) method);
  }

  @Uninterruptible
  public int getFloatLocalLocation(int localIndex) {
    return BaselineCompilerImpl.getFloatLocalLocation(localIndex, localFloatLocations, (NormalMethod) method);
  }

  @Uninterruptible
  public int getGeneralStackLocation(int stackIndex) {
    return BaselineCompilerImpl.offsetToLocation(emptyStackOffset - (stackIndex << LOG_BYTES_IN_ADDRESS));
  }

  @Uninterruptible
  public int getFloatStackLocation(int stackIndex) { //for now same implementation as getGeneralStackLocation, todo
    return BaselineCompilerImpl.offsetToLocation(emptyStackOffset - (stackIndex << LOG_BYTES_IN_ADDRESS));
  }

  @Uninterruptible
  public int getLastFixedStackRegister() {
    return lastFixedStackRegister;
  }

  @Uninterruptible
  public int getLastFloatStackRegister() {
    return lastFloatStackRegister;
  }

  public BaselineCompiledMethod(int id, RVMMethod m) {
    super(id, m);
    NormalMethod nm = (NormalMethod) m;
    //this.startLocalOffset = BaselineCompilerImpl.getStartLocalOffset(nm);
    this.emptyStackOffset = BaselineCompilerImpl.getEmptyStackOffset(nm);
    this.localFixedLocations = new int[nm.getLocalWords()];
    this.localFloatLocations = new int[nm.getLocalWords()];
    this.lastFixedStackRegister = -1;
    this.lastFloatStackRegister = -1;
  }

  public void compile() {
    BaselineCompilerImpl comp = new BaselineCompilerImpl(this, localFixedLocations, localFloatLocations);
    comp.compile();
    this.lastFixedStackRegister = comp.getLastFixedStackRegister();
    this.lastFloatStackRegister = comp.getLastFloatStackRegister();
  }

  @Uninterruptible
  public int getCompilerType() {
    return BASELINE;
  }

  public String getCompilerName() {
    return "baseline compiler";
  }

  @Uninterruptible
  public ExceptionDeliverer getExceptionDeliverer() {
    return exceptionDeliverer;
  }

  @Unpreemptible
  public int findCatchBlockForInstruction(Offset instructionOffset, RVMType exceptionType) {
    if (eTable == null) {
      return -1;
    } else {
      return ExceptionTable.findCatchBlockForInstruction(eTable, instructionOffset, exceptionType);
    }
  }

  @Uninterruptible
  public void getDynamicLink(DynamicLink dynamicLink, Offset instructionOffset) {
    int bytecodeIndex = findBytecodeIndexForInstruction(instructionOffset);
    ((NormalMethod) method).getDynamicLink(dynamicLink, bytecodeIndex);
  }

  /**
   * @return The line number, a positive integer.  Zero means unable to find.
   */
  @Uninterruptible
  public int findLineNumberForInstruction(Offset instructionOffset) {
    int bci = findBytecodeIndexForInstruction(instructionOffset);
    if (bci == -1) return 0;
    return ((NormalMethod) method).getLineNumberForBCIndex(bci);
  }

  /**
   * Return whether or not the instruction offset corresponds to an uninterruptible context.
   *
   * @param instructionOffset of addr from start of instructions in bytes
   * @return true if the IP is within an Uninterruptible method, false otherwise.
   */
  public boolean isWithinUninterruptibleCode(Offset instructionOffset) {
    return method.isUninterruptible();
  }

  /**
   * Find bytecode index corresponding to one of this method's
   * machine instructions.
   *
   * Note: This method expects the instructionIndex to refer to the machine
   *         instruction immediately FOLLOWING the bytecode in question.
   *         just like findLineNumberForInstruction. See CompiledMethod
   *         for rationale
   * NOTE: instructionIndex is in units of instructions, not bytes (different from
   *       all the other methods in this interface!!)
   *
   * @return the bytecode index for the machine instruction, -1 if
   *            not available or not found.
   */
  @Uninterruptible
  public int findBytecodeIndexForInstruction(Offset instructionOffset) {
    Offset instructionIndex = instructionOffset.toWord().rsha(LG_INSTRUCTION_WIDTH).toOffset();
    int candidateIndex = -1;
    int bcIndex = 0;
    Offset instrIndex = Offset.zero();
    for (int i = 0; i < bytecodeMap.length;) {
      int b0 = ((int) bytecodeMap[i++]) & 255;  // unsign-extend
      int deltaBC, deltaIns;
      if (b0 != 255) {
        deltaBC = b0 >> 5;
        deltaIns = b0 & 31;
      } else {
        int b1 = ((int) bytecodeMap[i++]) & 255;  // unsign-extend
        int b2 = ((int) bytecodeMap[i++]) & 255;  // unsign-extend
        int b3 = ((int) bytecodeMap[i++]) & 255;  // unsign-extend
        int b4 = ((int) bytecodeMap[i++]) & 255;  // unsign-extend
        deltaBC = (b1 << 8) | b2;
        deltaIns = (b3 << 8) | b4;
      }
      bcIndex += deltaBC;
      instrIndex = instrIndex.plus(deltaIns);
      if (instrIndex.sGE(instructionIndex)) {
        break;
      }
      candidateIndex = bcIndex;
    }
    return candidateIndex;
  }

  /**
   * Set the stack browser to the innermost logical stack frame of this method
   */
  public void set(StackBrowser browser, Offset instr) {
    browser.setMethod(method);
    browser.setCompiledMethod(this);
    browser.setBytecodeIndex(findBytecodeIndexForInstruction(instr));

    if (VM.TraceStackTrace) {
      VM.sysWrite("setting stack to frame (base): ");
      VM.sysWrite(browser.getMethod());
      VM.sysWrite(browser.getBytecodeIndex());
      VM.sysWrite("\n");
    }
  }

  /**
   * Advance the StackBrowser up one internal stack frame, if possible
   */
  public boolean up(StackBrowser browser) {
    return false;
  }

  // Print this compiled method's portion of a stack trace
  // Taken:   offset of machine instruction from start of method
  //          the PrintLN to print the stack trace to.
  public void printStackTrace(Offset instructionOffset, PrintLN out) {
    out.print("\tat ");
    out.print(method.getDeclaringClass()); // RVMClass
    out.print('.');
    out.print(method.getName()); // a Atom, returned via MemberReference.getName().
    out.print("(");
    out.print(method.getDeclaringClass().getSourceName()); // a Atom
    int lineNumber = findLineNumberForInstruction(instructionOffset);
    if (lineNumber <= 0) {      // unknown line
      out.print("; machine code offset: ");
      out.printHex(instructionOffset.toInt());
    } else {
      out.print(':');
      out.print(lineNumber);
    }
    out.print(')');
    out.println();
  }

  /**
   * Print the eTable
   */
  public void printExceptionTable() {
    if (eTable != null) ExceptionTable.printExceptionTable(eTable);
  }

  /** Set the lock acquisition offset for synchronized methods */
  public void setLockAcquisitionOffset(int off) {
    if (VM.VerifyAssertions) VM._assert((off & 0xFFFF) == off);
    lockOffset = (char) off;
  }

  /** Get the lock acquisition offset */
  @Uninterruptible
  public Offset getLockAcquisitionOffset() {
    return Offset.fromIntZeroExtend(lockOffset);
  }

  /** Set the method has a counters array */
  void setHasCounterArray() {
    hasCounters = true;
  }

  /** Does the method have a counters array? */
  @Uninterruptible
  public boolean hasCounterArray() {
    return hasCounters;
  }

  // Taken: method that was compiled
  //        bytecode-index to machine-instruction-index map for method
  //        number of instructions for method
  //
  public void encodeMappingInfo(ReferenceMaps referenceMaps, int[] bcMap, int numInstructions) {
    int count = 0;
    int lastBC = 0, lastIns = 0;
    for (int i = 0; i < bcMap.length; i++) {
      if (bcMap[i] != 0) {
        int deltaBC = i - lastBC;
        int deltaIns = bcMap[i] - lastIns;
        if (VM.VerifyAssertions) {
          VM._assert(deltaBC >= 0 && deltaIns >= 0);
        }
        if (deltaBC <= 6 && deltaIns <= 31) {
          count++;
        } else {
          if (deltaBC > 65535 || deltaIns > 65535) {
            VM.sysFail("BaselineCompiledMethod: a fancier encoding is needed");
          }
          count += 5;
        }
        lastBC = i;
        lastIns = bcMap[i];
      }
    }
    bytecodeMap = new byte[count];
    count = lastBC = lastIns = 0;
    for (int i = 0; i < bcMap.length; i++) {
      if (bcMap[i] != 0) {
        int deltaBC = i - lastBC;
        int deltaIns = bcMap[i] - lastIns;
        if (VM.VerifyAssertions) {
          VM._assert(deltaBC >= 0 && deltaIns >= 0);
        }
        if (deltaBC <= 6 && deltaIns <= 31) {
          bytecodeMap[count++] = (byte) ((deltaBC << 5) | deltaIns);
        } else { // From before, we know that deltaBC <= 65535 and deltaIns <= 65535
          bytecodeMap[count++] = (byte) 255;
          bytecodeMap[count++] = (byte) (deltaBC >> 8);
          bytecodeMap[count++] = (byte) (deltaBC & 255);
          bytecodeMap[count++] = (byte) (deltaIns >> 8);
          bytecodeMap[count++] = (byte) (deltaIns & 255);
        }
        lastBC = i;
        lastIns = bcMap[i];
      }
    }
    referenceMaps.translateByte2Machine(bcMap);
    this.referenceMaps = referenceMaps;
    ExceptionHandlerMap emap = ((NormalMethod) method).getExceptionHandlerMap();
    if (emap != null) {
      eTable = BaselineExceptionTable.encode(emap, bcMap);
    }
  }

  private static final TypeReference TYPE = TypeReference.findOrCreate(BaselineCompiledMethod.class);

  public int size() {
    int size = TYPE.peekType().asClass().getInstanceSize();
    if (bytecodeMap != null) size += RVMArray.ByteArray.getInstanceSize(bytecodeMap.length);
    if (eTable != null) size += RVMArray.IntArray.getInstanceSize(eTable.length);
    if (referenceMaps != null) size += referenceMaps.size();
    return size;
  }
}
