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

import org.jikesrvm.VM;
import org.jikesrvm.Constants;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.compilers.baseline.BaselineCompiledMethod;
import org.jikesrvm.compilers.baseline.ppc.BaselineCompilerImpl;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod;
import org.jikesrvm.compilers.opt.regalloc.ppc.PhysicalRegisterConstants;
import org.jikesrvm.osr.BytecodeTraverser;
import org.jikesrvm.osr.OSRConstants;
import org.jikesrvm.osr.ExecutionStateExtractor;
import org.jikesrvm.osr.ExecutionState;
import org.jikesrvm.osr.VariableElement;
import org.jikesrvm.ppc.BaselineConstants;
import org.jikesrvm.ppc.Registers;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.scheduler.RVMThread;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Word;
import org.vmmagic.unboxed.WordArray;

/**
 * BaselineExecutionStateExtractor retrieves the runtime state from a suspended
 * thread whose top method was compiled by a baseline compiler.
 */

public abstract class BaselineExecutionStateExtractor extends ExecutionStateExtractor
    implements Constants, OSRConstants, BaselineConstants, PhysicalRegisterConstants {

  /**
   * Implements ExecutionStateExtractor.extractState.
   */
  public ExecutionState extractState(RVMThread thread, Offset tsFromFPoff, Offset methFPoff, int cmid) {

    /* performs architecture and compiler dependent operations here
    *
    * When a thread is hung called from baseline compiled code,
    * the hierarchy of calls on stack looks like follows
    * ( starting from FP in the FP register ):
    *
    *           morph
    *           yield
    *           threadSwitch
    *           threadSwitchFrom[Prologue|Backedge|Epilong]
    *           foo ( real method ).
    *
    * The returned ExecutionState should have following
    *
    *     current thread
    *     compiled method ID of "foo"
    *     fp of foo's stack frame
    *     bytecode index of foo's next instruction
    *     the list of variable,value of foo at that point
    *     which method (foo)
    */

    if (VM.TraceOnStackReplacement) {
      VM.sysWriteln("BASE execStateExtractor starting ...");
    }

    Registers contextRegisters = (Registers)thread.getContextRegisters();
    byte[] stack = thread.getStack();

    if (VM.VerifyAssertions) {
      int fooCmid = Magic.getIntAtOffset(stack, methFPoff.plus(STACKFRAME_METHOD_ID_OFFSET));
      VM._assert(fooCmid == cmid);
    }

    BaselineCompiledMethod fooCM = (BaselineCompiledMethod) CompiledMethods.getCompiledMethod(cmid);

    NormalMethod fooM = (NormalMethod) fooCM.getMethod();

    // get the next bc index
    VM.disableGC();
    Address rowIP = Magic.objectAsAddress(stack).loadAddress(methFPoff.plus(STACKFRAME_NEXT_INSTRUCTION_OFFSET));
    Offset ipOffset = fooCM.getInstructionOffset(rowIP);
    VM.enableGC();

    // CAUTION: IP Offset should point to next instruction
    int bcIndex = fooCM.findBytecodeIndexForInstruction(ipOffset.plus(INSTRUCTION_WIDTH));

    // assertions
    if (VM.VerifyAssertions) VM._assert(bcIndex != -1);

    // create execution state object
    ExecutionState state = new ExecutionState(thread, methFPoff, cmid, bcIndex, tsFromFPoff);

    /* extract values for local and stack, but first of all
     * we need to get type information for current PC.
     */
    BytecodeTraverser typer = new BytecodeTraverser();
    typer.computeLocalStackTypes(fooM, bcIndex);
    byte[] localTypes = typer.getLocalTypes();
    byte[] stackTypes = typer.getStackTypes();

    // consult GC reference map again since the type matcher does not complete
    // the flow analysis, it can not distinguish reference or non-reference
    // type. We should remove non-reference type
    for (int i = 0, n = localTypes.length; i < n; i++) {
      // if typer reports a local is reference type, but the GC map says no
      // then set the localType to uninitialized, see VM spec, bytecode verifier
      // CAUTION: gc map uses mc offset in bytes!!!
      boolean gcref = fooCM.referenceMaps.isLocalRefType(fooM, ipOffset.plus(INSTRUCTION_WIDTH), i);
      if (!gcref && (localTypes[i] == ClassTypeCode)) {
        localTypes[i] = VoidTypeCode;   // use gc map as reference
        if (VM.TraceOnStackReplacement) {
          VM.sysWriteln("GC maps disgrees with type matcher at " + i + "th local\n");
        }
      }
    }

    if (VM.TraceOnStackReplacement) {
      Offset ipIndex = ipOffset.toWord().rsha(LG_INSTRUCTION_WIDTH).toOffset();
      VM.sysWrite("BC Index : " + bcIndex + "\n");
      VM.sysWrite("IP Index : ", ipIndex.plus(1), "\n");
      VM.sysWrite("MC Offset : ", ipOffset.plus(INSTRUCTION_WIDTH), "\n");
      VM.sysWrite("Local Types :");
      for (byte localType : localTypes) {
        VM.sysWrite(" " + (char) localType);
      }

      VM.sysWrite("\nStack Types :");
      for (byte stackType : stackTypes) {
        VM.sysWrite(" " + (char) stackType);
      }
      VM.sysWriteln();
    }

    // go through the stack frame and extract values
    // In the variable value list, we keep the order as follows:
    // L0, L1, ..., S0, S1, ....

    // adjust local offset and stack offset
    // NOTE: donot call BaselineCompilerImpl.getFirstLocalOffset(method)
    int bufCMID = Magic.getIntAtOffset(stack, tsFromFPoff.plus(STACKFRAME_METHOD_ID_OFFSET));
    CompiledMethod bufCM = CompiledMethods.getCompiledMethod(bufCMID);
    int cType = bufCM.getCompilerType();

    //restore non-volatile registers that could contain locals; saved by yieldpointfrom methods
    //for the moment disabled OPT compilation of yieldpointfrom, because here we assume baselinecompilation !! TODO
    TempRegisters registers = new TempRegisters(contextRegisters);
    WordArray gprs = registers.gprs;
    double[] fprs = registers.fprs;
    Object[] objs = registers.objs;

    VM.disableGC();
    // method fooCM is always baseline, otherwise we wouldn't be in this code.
    // the threadswitchfrom... method on the other hand can be baseline or opt!
    if (cType == CompiledMethod.BASELINE) {
      if (VM.VerifyAssertions) {
        VM._assert(bufCM.getMethod().hasBaselineSaveLSRegistersAnnotation());
        VM._assert(methFPoff.EQ(tsFromFPoff.plus(BaselineCompilerImpl.getFrameSize((BaselineCompiledMethod) bufCM))));
      }

      Offset currentRegisterLocation = tsFromFPoff.plus(BaselineCompilerImpl.getFrameSize((BaselineCompiledMethod) bufCM));

      for (int i = LAST_FLOAT_STACK_REGISTER; i >= FIRST_FLOAT_LOCAL_REGISTER; --i) {
        currentRegisterLocation = currentRegisterLocation.minus(BYTES_IN_DOUBLE);
        long lbits = Magic.getLongAtOffset(stack, currentRegisterLocation);
        fprs[i] = Magic.longBitsAsDouble(lbits);
      }
      for (int i = LAST_FIXED_STACK_REGISTER; i >= FIRST_FIXED_LOCAL_REGISTER; --i) {
        currentRegisterLocation = currentRegisterLocation.minus(BYTES_IN_ADDRESS);
        Word w = Magic.objectAsAddress(stack).loadWord(currentRegisterLocation);
        gprs.set(i, w);
      }

    } else { //(cType == CompiledMethod.OPT)
      //KV: this code needs to be modified. We need the tsFrom methods to save all NON-VOLATILES in their prolog (as is the case for baseline)
      //This is because we don't know at compile time which registers might be in use and wich not by the caller method at runtime!!
      //For now we disallow tsFrom methods to be opt compiled when the caller is baseline compiled
      //todo: fix this together with the SaveVolatile rewrite
      OptCompiledMethod fooOpt = (OptCompiledMethod) bufCM;
      // foo definitely not save volatile.
      if (VM.VerifyAssertions) {
        boolean saveVolatile = fooOpt.isSaveVolatile();
        VM._assert(!saveVolatile);
      }

      Offset offset = tsFromFPoff.plus(fooOpt.getUnsignedNonVolatileOffset());

      // recover nonvolatile GPRs
      int firstGPR = fooOpt.getFirstNonVolatileGPR();
      if (firstGPR != -1) {
        for (int i = firstGPR; i <= LAST_NONVOLATILE_GPR; i++) {
          Word w = Magic.objectAsAddress(stack).loadWord(offset);
          gprs.set(i, w);
          offset = offset.plus(BYTES_IN_ADDRESS);
        }
      }

      // recover nonvolatile FPRs
      int firstFPR = fooOpt.getFirstNonVolatileFPR();
      if (firstFPR != -1) {
        for (int i = firstFPR; i <= LAST_NONVOLATILE_FPR; i++) {
          long lbits = Magic.getLongAtOffset(stack, offset);
          fprs[i] = Magic.longBitsAsDouble(lbits);
          offset = offset.plus(BYTES_IN_DOUBLE);
        }
      }
    }

    //save objects in registers in register object array
    int size = localTypes.length;
    for (int i = 0; i < size; i++) {
      if ((localTypes[i] == ClassTypeCode) || (localTypes[i] == ArrayTypeCode)) {
        int loc = fooCM.getGeneralLocalLocation(i);
        if (BaselineCompilerImpl.isRegister(loc)) {
          objs[loc] = Magic.addressAsObject(gprs.get(loc).toAddress());
        }
      }
    }

    VM.enableGC();

    // for locals
    getVariableValueFromLocations(stack, methFPoff, localTypes, fooCM, LOCAL, registers, state);

    // for stacks
    Offset stackOffset = methFPoff.plus(fooCM.getEmptyStackOffset());
    getVariableValue(stack, stackOffset, stackTypes, fooCM, STACK, state);

    if (VM.TraceOnStackReplacement) {
      state.printState();
    }

    if (VM.TraceOnStackReplacement) {
      VM.sysWriteln("BASE executionStateExtractor done ");
    }
    return state;
  }

  /** go over local/stack array, and build VariableElement. */
  private static void getVariableValueFromLocations(byte[] stack, Offset methFPoff, byte[] types,
                                                    BaselineCompiledMethod compiledMethod, boolean kind,
                                                    TempRegisters registers, ExecutionState state) {
    int start = 0;
    if (kind == LOCAL) {
      start = 0;
    } else {
      VM._assert(false); //implement me for stack
    }
    int size = types.length;
    for (int i = start; i < size; i++) {
      switch (types[i]) {
        case VoidTypeCode:
          break;

        case BooleanTypeCode:
        case ByteTypeCode:
        case ShortTypeCode:
        case CharTypeCode:
        case IntTypeCode: {
          int loc = compiledMethod.getGeneralLocalLocation(i);
          int value;
          if (BaselineCompilerImpl.isRegister(loc)) {
            value = registers.gprs.get(loc).toInt();
          } else {
            value = Magic.getIntAtOffset(stack, methFPoff.plus(BaselineCompilerImpl.locationToOffset(loc) - BYTES_IN_INT));
          }

          state.add(new VariableElement(kind, i, INT, value));
          break;
        }
        case LongTypeCode: {
          int loc = compiledMethod.getGeneralLocalLocation(i);
          long lvalue;
          if (BaselineCompilerImpl.isRegister(loc)) {
            if (VM.BuildFor32Addr) {
              lvalue =
                  ((((long) registers.gprs.get(loc).toInt()) << 32) |
                   (((long) registers.gprs.get(loc + 1).toInt()) & 0x0FFFFFFFFL));
            } else {
              lvalue = registers.gprs.get(loc).toLong();
            }
          } else {
            lvalue = Magic.getLongAtOffset(stack, methFPoff.plus(BaselineCompilerImpl.locationToOffset(loc) - BYTES_IN_LONG));
          }

          state.add(new VariableElement(kind, i, LONG, lvalue));

          if (kind == LOCAL) { //KV:VoidTypeCode is next
            i++; //KV:skip VoidTypeCode
          }
          break;
        }
        case FloatTypeCode: {
          int loc = compiledMethod.getFloatLocalLocation(i);
          int value;
          if (BaselineCompilerImpl.isRegister(loc)) {
            value = Magic.floatAsIntBits((float) registers.fprs[loc]);
          } else {
            value = Magic.getIntAtOffset(stack, methFPoff.plus(BaselineCompilerImpl.locationToOffset(loc) - BYTES_IN_FLOAT));
          }

          state.add(new VariableElement(kind, i, FLOAT, value));
          break;
        }
        case DoubleTypeCode: {
          int loc = compiledMethod.getFloatLocalLocation(i);
          long lvalue;
          if (BaselineCompilerImpl.isRegister(loc)) {
            lvalue = Magic.doubleAsLongBits(registers.fprs[loc]);
          } else {
            lvalue =
                Magic.getLongAtOffset(stack, methFPoff.plus(BaselineCompilerImpl.locationToOffset(loc) - BYTES_IN_DOUBLE));
          }

          state.add(new VariableElement(kind, i, DOUBLE, lvalue));

          if (kind == LOCAL) { //KV:VoidTypeCode is next
            i++; //KV:skip VoidTypeCode
          }
          break;
        }
        case ReturnAddressTypeCode: {
          int loc = compiledMethod.getGeneralLocalLocation(i);
          VM.disableGC();
          Address rowIP;
          if (BaselineCompilerImpl.isRegister(loc)) {
            rowIP = registers.gprs.get(loc).toAddress();
          } else {
            rowIP =
                Magic.objectAsAddress(stack).loadAddress(methFPoff.plus(BaselineCompilerImpl.locationToOffset(loc) -
                                                                           BYTES_IN_ADDRESS));
          }
          Offset ipOffset = compiledMethod.getInstructionOffset(rowIP);
          VM.enableGC();

          if (VM.TraceOnStackReplacement) {
            Offset ipIndex = ipOffset.toWord().rsha(LG_INSTRUCTION_WIDTH).toOffset();
            VM.sysWrite("baseline ret_addr ip ", ipIndex, " --> ");
          }

          int bcIndex = compiledMethod.findBytecodeIndexForInstruction(ipOffset.plus(INSTRUCTION_WIDTH));

          if (VM.TraceOnStackReplacement) {
            VM.sysWrite(" bc " + bcIndex + "\n");
          }

          state.add(new VariableElement(kind, i, RET_ADDR, bcIndex));
          break;
        }

        case ClassTypeCode:
        case ArrayTypeCode: {
          int loc = compiledMethod.getGeneralLocalLocation(i);
          Object ref;
          if (BaselineCompilerImpl.isRegister(loc)) {
            ref = registers.objs[loc];
          } else {
            ref =
                Magic.getObjectAtOffset(stack, methFPoff.plus(BaselineCompilerImpl.locationToOffset(loc) - BYTES_IN_ADDRESS));
          }

          state.add(new VariableElement(kind, i, REF, ref));
          break;
        }
        case WordTypeCode: {
          int loc = compiledMethod.getGeneralLocalLocation(i);
          Word value;
          if (BaselineCompilerImpl.isRegister(loc)) {
            value = registers.gprs.get(loc);
          } else {
            value =
                Magic.getWordAtOffset(stack, methFPoff.plus(BaselineCompilerImpl.locationToOffset(loc) - BYTES_IN_ADDRESS));
          }

          state.add(new VariableElement(kind, i, WORD, value));
          break;
        }
        default:
          if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED);
          break;
      } // switch
    } // for loop
  }

  /* go over local/stack array, and build VariableElement. */
  private static void getVariableValue(byte[] stack, Offset offset, byte[] types,
                                       BaselineCompiledMethod compiledMethod, boolean kind, ExecutionState state) {
    int size = types.length;
    Offset vOffset = offset;
    for (int i = 0; i < size; i++) {
      switch (types[i]) {
        case VoidTypeCode:
          vOffset = vOffset.minus(BYTES_IN_STACKSLOT);
          break;

        case BooleanTypeCode:
        case ByteTypeCode:
        case ShortTypeCode:
        case CharTypeCode:
        case IntTypeCode:
        case FloatTypeCode: {
          int value = Magic.getIntAtOffset(stack, vOffset.minus(BYTES_IN_INT));
          vOffset = vOffset.minus(BYTES_IN_STACKSLOT);

          byte tcode = (types[i] == FloatTypeCode) ? FLOAT : INT;

          state.add(new VariableElement(kind, i, tcode, value));
          break;
        }
        case LongTypeCode:
        case DoubleTypeCode: {
          //KV: this code would be nicer if VoidTypeCode would always follow a 64-bit value. Rigth now for LOCAL it follows, for STACK it proceeds
          Offset memoff =
              (kind == LOCAL) ? vOffset.minus(BYTES_IN_DOUBLE) : VM.BuildFor64Addr ? vOffset : vOffset.minus(
                  BYTES_IN_STACKSLOT);
          long value = Magic.getLongAtOffset(stack, memoff);

          byte tcode = (types[i] == LongTypeCode) ? LONG : DOUBLE;

          state.add(new VariableElement(kind, i, tcode, value));

          if (kind == LOCAL) { //KV:VoidTypeCode is next
            vOffset = vOffset.minus(2 * BYTES_IN_STACKSLOT);
            i++; //KV:skip VoidTypeCode
          } else {
            vOffset = vOffset.minus(BYTES_IN_STACKSLOT); //KV:VoidTypeCode was already in front
          }

          break;
        }
        case ReturnAddressTypeCode: {
          VM.disableGC();
          Address rowIP = Magic.objectAsAddress(stack).loadAddress(vOffset.minus(BYTES_IN_ADDRESS));
          Offset ipOffset = compiledMethod.getInstructionOffset(rowIP);
          VM.enableGC();

          vOffset = vOffset.minus(BYTES_IN_STACKSLOT);

          if (VM.TraceOnStackReplacement) {
            Offset ipIndex = ipOffset.toWord().rsha(LG_INSTRUCTION_WIDTH).toOffset();
            VM.sysWrite("baseline ret_addr ip ", ipIndex, " --> ");
          }

          int bcIndex = compiledMethod.findBytecodeIndexForInstruction(ipOffset.plus(INSTRUCTION_WIDTH));

          if (VM.TraceOnStackReplacement) {
            VM.sysWrite(" bc " + bcIndex + "\n");
          }

          state.add(new VariableElement(kind, i, RET_ADDR, bcIndex));
          break;
        }

        case ClassTypeCode:
        case ArrayTypeCode: {
          VM.disableGC();
          Object ref = Magic.getObjectAtOffset(stack, vOffset.minus(BYTES_IN_ADDRESS));
          VM.enableGC();

          vOffset = vOffset.minus(BYTES_IN_STACKSLOT);

          state.add(new VariableElement(kind, i, REF, ref));
          break;
        }
        case WordTypeCode: {
          Word value = Magic.getWordAtOffset(stack, vOffset.minus(BYTES_IN_ADDRESS));
          vOffset = vOffset.minus(BYTES_IN_STACKSLOT);

          state.add(new VariableElement(kind, i, WORD, value));
          break;
        }
        default:
          if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED);
          break;
      } // switch
    } // for loop
  }
}
