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

import java.util.LinkedList;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.BytecodeConstants;
import org.jikesrvm.classloader.BytecodeStream;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.jikesrvm.osr.bytecodes.AConstNull;
import org.jikesrvm.osr.bytecodes.DoubleStore;
import org.jikesrvm.osr.bytecodes.FloatStore;
import org.jikesrvm.osr.bytecodes.Goto;
import org.jikesrvm.osr.bytecodes.IntStore;
import org.jikesrvm.osr.bytecodes.InvokeCompiledMethod;
import org.jikesrvm.osr.bytecodes.InvokeStatic;
import org.jikesrvm.osr.bytecodes.LoadDoubleConst;
import org.jikesrvm.osr.bytecodes.LoadFloatConst;
import org.jikesrvm.osr.bytecodes.LoadIntConst;
import org.jikesrvm.osr.bytecodes.LoadLongConst;
import org.jikesrvm.osr.bytecodes.LoadRetAddrConst;
import org.jikesrvm.osr.bytecodes.LoadWordConst;
import org.jikesrvm.osr.bytecodes.LongStore;
import org.jikesrvm.osr.bytecodes.Nop;
import org.jikesrvm.osr.bytecodes.ParamInitEnd;
import org.jikesrvm.osr.bytecodes.Pop;
import org.jikesrvm.osr.bytecodes.RefStore;
import org.jikesrvm.osr.bytecodes.PseudoBytecode;
import org.jikesrvm.scheduler.RVMThread;
import org.vmmagic.unboxed.Offset;

public class ExecutionState implements OSRConstants, BytecodeConstants {

  /** the caller's state if this method is an inlinee */
  public ExecutionState callerState = null;

  /** callee's compiled method id */
  public int callee_cmid = -1;

  /** the method of which the execution state belongs to */
  public NormalMethod meth;

  /** the program pointer (bytecode index) */
  public int bcIndex;

  /**
   * runtime values of locals and stack expressions at the bytecode index Each
   * element is an object of VariableElement.
   */
  public LinkedList<VariableElement> varElms;

  /** the thread on which the activation is running */
  public RVMThread thread;

  /** the offset of frame pointer of the activation. */
  public Offset fpOffset;

  /** the callee (threadSwitch)'s frame pointer of this activation. */
  public Offset tsFPOffset;

  /**
   * the compiled method id of the activation (a Java method may have multiple
   * version of compiled method
   */
  public int cmid;

  /**
   * Initializer
   * @param whichThread
   * @param framePointerOffset
   * @param compiledMethodID
   * @param pc
   * @param tsFPOffset
   */
  public ExecutionState(RVMThread whichThread, Offset framePointerOffset, int compiledMethodID, int pc,
                            Offset tsFPOffset) {
    this.thread = whichThread;
    this.fpOffset = framePointerOffset;
    this.cmid = compiledMethodID;
    this.bcIndex = pc;
    this.tsFPOffset = tsFPOffset;

    this.varElms = new LinkedList<VariableElement>();
    this.meth = (NormalMethod) CompiledMethods.getCompiledMethod(cmid).getMethod();
  }

  /////////////////////////////
  // instance methods for construction
  ////////////////////////////

  /** add a VariableElement */

  public void add(VariableElement elm) {
    this.varElms.add(elm);
  }

  /** insert as the first element, for convinience. */
  public void addFirst(VariableElement elm) {
    this.varElms.addFirst(elm);
  }

  /** returns thread. */
  public RVMThread getThread() {
    return this.thread;
  }

  public Offset getFPOffset() {
    return this.fpOffset;
  }

  public void setMethod(NormalMethod m) {
    this.meth = m;
  }

  public NormalMethod getMethod() {
    return this.meth;
  }

  public Offset getTSFPOffset() {
    return this.tsFPOffset;
  }

  /** print the current state for debugging */
  public void printState() {
    VM.sysWriteln("Execution state of " + meth);
    VM.sysWriteln("    thread index : ", thread.getIndex());
    VM.sysWriteln("       FP offset : ", fpOffset);
    VM.sysWriteln("            cmid : ", cmid);
    VM.sysWriteln("         bcIndex : ", bcIndex);

    for (VariableElement var : varElms) {
      VM.sysWrite("  " + var + "\n");
    }
  }

  //////////////////////////////////////
  // interface to recompilation
  /////////////////////////////////////

  private Object[] objs;
  private int objnum;
  private int rid;

  /**
   * Goes through variable elements and produces specialized
   * prologue using pseudo-bytecode.
   */
  public byte[] generatePrologue() {

    int size = varElms.size();

    this.objs = new Object[size];
    this.objnum = 0;
    this.rid = ObjectHolder.handinRefs(this.objs);

    PseudoBytecode head = new Nop();
    PseudoBytecode tail = head;

    int elmcount = 0;
    // restore parameters first;
    // restore "this"
    if (!this.meth.isStatic()) {
      VariableElement var = varElms.get(elmcount);
      tail = processElement(var, tail, elmcount);
      elmcount++;

      if (VM.VerifyAssertions) {
        VM._assert(var.isLocal() && (var.getNumber() == 0));
      }
    }
    // restore other parameters,
    int paranum = this.meth.getParameterTypes().length;
    for (int i = 0; i < paranum; i++) {
      VariableElement var = varElms.get(elmcount);
      tail = processElement(var, tail, elmcount);
      elmcount++;
      if (VM.VerifyAssertions) {
        VM._assert(var.isLocal());
        // the number may not match because of long and double type
      }
    }
    // ok, ready to indicate param initialized, thread switch
    // and stack overflow check happens here
    tail.next = new ParamInitEnd();
    tail = tail.next;

    // restore other locals and stack slots, assuming stack element
    // were sorted
    for (; elmcount < size; elmcount++) {
      VariableElement var = varElms.get(elmcount);
      tail = processElement(var, tail, elmcount);
    }// end of for loop

    if (this.objnum != 0) {
      tail.next = new LoadIntConst(this.rid);
      tail = tail.next;

      tail.next = new InvokeStatic(CLEANREFS);
      tail = tail.next;
    } else {
      ObjectHolder.cleanRefs(this.rid);
    }

    // default situation
    int branchTarget = this.bcIndex;

    /* when this method must start with a call of callee,
     * we are using invokeCompiledMethod,
     */
    if (callee_cmid != -1) {
      // remember the callee's cmid, and the index of original index
      tail.next = new InvokeCompiledMethod(callee_cmid, this.bcIndex);
      tail = tail.next;

      // if this method needs a call, than we must jump to
      // the instruction after the call.
      BytecodeStream bcodes = this.meth.getBytecodes();
      bcodes.reset(this.bcIndex);

      int code = bcodes.nextInstruction();

      switch (code) {
        case JBC_invokeinterface: {
          branchTarget = this.bcIndex + 5;
          break;
        }
        case JBC_invokespecial:
        case JBC_invokestatic:
        case JBC_invokevirtual: {
          branchTarget = this.bcIndex + 3;
          break;
        }
        default: {
          if (VM.VerifyAssertions) {
            VM._assert(VM.NOT_REACHED,
                       "ExecutionState: unknown bytecode " + code + " at " + this.bcIndex + "@" + this.meth);
          }
          break;
        }
      }
    }

    // add goto statement, be careful, after goto
    // there may be several pop instructions
    int pops = computeStackHeight(head);
    branchTarget += pops;  // preserve space
    {
      Goto togo = new Goto(branchTarget);
      int osize = togo.getSize();
      togo.patch(branchTarget + osize);
      int nsize = togo.getSize();
      if (nsize != osize) {
        togo.patch(branchTarget + nsize);
      }

      tail.next = togo;
      tail = tail.next;
    }

    // compute stack heights and padding pops
    tail = adjustStackHeight(tail, pops);

    int bsize = paddingBytecode(head);
    byte[] prologue = generateBinaries(head, bsize);

    // clean fields
    this.objs = null;
    this.objnum = 0;

    return prologue;
  }// end of method

  private PseudoBytecode processElement(VariableElement var, PseudoBytecode tail, int i) {
    switch (var.getTypeCode()) {
      case INT: {
        tail.next = new LoadIntConst(var.getIntBits());
        tail = tail.next;

        if (var.isLocal()) {
          tail.next = new IntStore(var.getNumber());
          tail = tail.next;
        }
        break;
      }
      case FLOAT: {
        tail.next = new LoadFloatConst(var.getIntBits());
        tail = tail.next;

        if (var.isLocal()) {
          tail.next = new FloatStore(var.getNumber());
          tail = tail.next;
        }
        break;
      }
      case LONG: {
        tail.next = new LoadLongConst(var.getLongBits());
        tail = tail.next;

        if (var.isLocal()) {
          tail.next = new LongStore(var.getNumber());
          tail = tail.next;
        }
        break;
      }
      case DOUBLE: {
        tail.next = new LoadDoubleConst(var.getLongBits());
        tail = tail.next;

        if (var.isLocal()) {
          tail.next = new DoubleStore(var.getNumber());
          tail = tail.next;
        }
        break;
      }
      case RET_ADDR: {
        tail.next = new LoadRetAddrConst(var.getIntBits());
        tail = tail.next;

        if (var.isLocal()) {
          tail.next = new RefStore(var.getNumber());
          tail = tail.next;
        }
        break;
      }
      case REF: {
        this.objs[i] = var.getObject();

        if (this.objs[i] != null) {

          tail.next = new LoadIntConst(this.rid);
          tail = tail.next;

          tail.next = new LoadIntConst(i);
          tail = tail.next;

          // the opt compiler will adjust the type of
          // return value to the real type of object
          // when it sees the invoke target is GETREFAT
          tail.next = new InvokeStatic(GETREFAT);
          tail = tail.next;
        } else {
          // just give an aconst_null
          tail.next = new AConstNull();
          tail = tail.next;
        }

        if (var.isLocal()) {
          tail.next = new RefStore(var.getNumber());
          tail = tail.next;
        }

        this.objnum++;

        break;
      }
      case WORD: {
        tail.next = new LoadWordConst(var.getWord());
        tail = tail.next;

        if (var.isLocal()) {
          tail.next = new RefStore(var.getNumber());
          tail = tail.next;
        }
        break;
      }
      default:
        if (VM.VerifyAssertions) {
          VM._assert(VM.NOT_REACHED);
        }
        break;
    } // end of switch

    return tail;
  }

  private short maxStackHeight = 0;

  public short getMaxStackHeight() {
    return this.maxStackHeight;
  }

  private int computeStackHeight(PseudoBytecode head) {
    /* skip the first Nop */
    PseudoBytecode bcode = head.next;
    short height = 0;
    while (bcode != null) {
      height += bcode.stackChanges();
      if (height > this.maxStackHeight) {
        this.maxStackHeight = height;
      }
      bcode = bcode.next;
    }

    if (VM.VerifyAssertions) VM._assert(height >= 0);
    return height;
  }

  private static PseudoBytecode adjustStackHeight(PseudoBytecode last, int height) {
    // append pop
    for (int i = 0; i < height; i++) {
      last.next = new Pop();
      last = last.next;
    }

    return last;
  }

  /* add padding (NOP) at the beginning of pseudo bytecode
   * to make the new bytecode size dividable by 4, then no branch
   * target adjustment is needed in the original code.
   */
  private static int paddingBytecode(PseudoBytecode head) {
    /* skip the first Nop. */
    PseudoBytecode bcode = head.next;

    /* count the total size of prologue code. */
    int bsize = 0;
    while (bcode != null) {
      bsize += bcode.getSize();
      bcode = bcode.next;
    }

    /* insert Nop at the beginning to make the code size of x4. */
    int padding = 3 - (bsize + 3) & 0x03;

    for (int i = 0; i < padding; i++) {
      bcode = new Nop();
      bcode.next = head.next;
      head.next = bcode;
    }

    bsize += padding;

    return bsize;
  }

  /* generating binary code from pseudo code, the size and the code
   * list are padded and well calculated.
   */
  private static byte[] generateBinaries(PseudoBytecode bhead, int bsize) {

    /* patch the LoalAddrConst instruction, and generate codes. */
    byte[] codes = new byte[bsize];

    /* skip the first NOP */
    PseudoBytecode bcode = bhead.next;
    int pos = 0;
    while (bcode != null) {

      int size = bcode.getSize();

      if (bcode instanceof LoadRetAddrConst) {
        LoadRetAddrConst laddr = (LoadRetAddrConst) bcode;

        /* CAUTION: path relative offset only. */
        laddr.patch(laddr.getOffset() + bsize);
      }

      if (VM.TraceOnStackReplacement) VM.sysWriteln(pos + " : " + bcode.toString());

      System.arraycopy(bcode.getBytes(), 0, codes, pos, size);

      pos += size;
      bcode = bcode.next;
    }

    return codes;
  }

  public String toString() {
    StringBuffer buf = new StringBuffer("Execution state " + this.bcIndex + "@" + this.meth + " " + this.thread);
    for (int i = 0, n = varElms.size(); i < n; i++) {
      VariableElement var = varElms.get(i);
      buf.append("\n  ");
      buf.append(var);
    }

    return new String(buf);
  }
}
