/*
 *  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.Constants;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.jikesrvm.mm.mminterface.Selected;
import org.jikesrvm.mm.mminterface.DebugUtil;
import org.jikesrvm.mm.mminterface.GCMapIterator;
import org.jikesrvm.mm.mminterface.GCMapIteratorGroup;
import org.jikesrvm.mm.mminterface.MemoryManager;
import org.jikesrvm.runtime.Entrypoints;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.runtime.RuntimeEntrypoints;
import org.jikesrvm.scheduler.Scheduler;
import org.jikesrvm.scheduler.RVMThread;
import org.mmtk.plan.TraceLocal;
import org.mmtk.utility.Log;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.ObjectReference;
import org.vmmagic.unboxed.Offset;

/**
 * Class that supports scanning thread stacks for references during
 * collections. References are located using GCMapIterators and are
 * inserted into a set of root locations.  Optionally, a set of
 * interior pointer locations associated with the object is created.<p>
 *
 * Threads, stacks, jni environments, and register objects have a
 * complex interaction in terms of scanning.  The operation of
 * scanning the stack reveals not only roots inside the stack but also
 * the state of the register objects's gprs and the JNI refs array.
 * They are all associated via the thread object, making it natural
 * for scanThread to be considered a single operation with the method
 * directly accessing these objects via the thread object's
 * fields. <p>
 *
 * One pitfall occurs when scanning the thread object (plus
 * dependents) when not all of the objects have been copied.  Then it
 * may be that the innards of the register object has not been copied
 * while the stack object has.  The result is that an inconsistent set
 * of slots is reported.  In this case, the copied register object may
 * not be correct if the copy occurs after the root locations are
 * discovered but before those locations are processed. In essence,
 * all of these objects form one logical unit but are physically
 * separated so that sometimes only part of it has been copied causing
 * the scan to be incorrect. <p>
 *
 * The caller of the stack scanning routine must ensure that all of
 * these components's descendants are consistent (all copied) when
 * this method is called. <p>
 *
 * <i>Code locations:</i> Identifying pointers <i>into</i> code
 * objects is essential if code objects are allowed to move (and if
 * the code objects were not otherwise kept alive, it would be
 * necessary to ensure the liveness of the code objects). A code
 * pointer is the only case in which we have interior pointers
 * (pointers into the inside of objects).  For such pointers, two
 * things must occur: first the pointed to object must be kept alive,
 * and second, if the pointed to object is moved by a copying
 * collector, the pointer into the object must be adjusted so it now
 * points into the newly copied object.<p>
 */
@Uninterruptible public final class ScanThread implements Constants {

  /***********************************************************************
   *
   * Class variables
   */
  /** quietly validates each ref reported by map iterators */
  static final boolean VALIDATE_REFS = VM.VerifyAssertions;

  /**
   * debugging options to produce printout during scanStack
   * MULTIPLE GC THREADS WILL PRODUCE SCRAMBLED OUTPUT so only
   * use these when running with PROCESSORS=1
   */
  static final int DEFAULT_VERBOSITY = 0;
  static final int FAILURE_VERBOSITY = 3;

  /***********************************************************************
   *
   * Instance variables
   */
  private final GCMapIteratorGroup iteratorGroup = new GCMapIteratorGroup();
  private GCMapIterator iterator;
  private TraceLocal trace;
  private boolean processCodeLocations;
  private RVMThread thread;
  private Address ip, fp, prevFp, initialIPLoc, topFrame;
  private CompiledMethod compiledMethod;
  private int compiledMethodType;
  private boolean failed;

  /***********************************************************************
   *
   * Thread scanning
   */

  /**
   * Scan a thread, placing the addresses of pointers into supplied buffers.
   *
   * @param thread The thread to be scanned
   * @param trace The trace instance to use for reporting references.
   * @param processCodeLocations Should code locations be processed?
   */
  public static void scanThread(RVMThread thread, TraceLocal trace,
                                boolean processCodeLocations) {
    /* get the gprs associated with this thread */
    Address gprs = Magic.objectAsAddress(thread.getContextRegisters().gprs);
    scanThread(thread, trace, processCodeLocations, gprs, Address.zero());
  }

  /**
   * Wrapper for {@link TraceLocal#reportDelayedRootEdge(Address)} that allows
   * sanity checking of the address.
   */
  private static void reportDelayedRootEdge(TraceLocal trace, Address addr) {
    if (VALIDATE_REFS) checkReference(addr);
    trace.reportDelayedRootEdge(addr);
  }

  /**
   * A more general interface to thread scanning, which permits the
   * scanning of stack segments which are dislocated from the thread
   * structure.
   *
   * @param thread The thread to be scanned
   * @param trace The trace instance to use for reporting references.
   * @param processCodeLocations Should code locations be processed?
   * @param gprs The general purpose registers associated with the
   * stack being scanned (normally extracted from the thread).
   * @param topFrame The top frame of the stack being scanned, or zero
   * if this is to be inferred from the thread (normally the case).
   */
  public static void scanThread(RVMThread thread, TraceLocal trace,
                                boolean processCodeLocations,
                                Address gprs, Address topFrame) {
    /* establish ip and fp for the stack to be scanned */
    Address ip, fp, initialIPLoc;
    if (topFrame.isZero()) { /* implicit top of stack, inferred from thread */
      ip = thread.getContextRegisters().getInnermostInstructionAddress();
      fp = thread.getContextRegisters().getInnermostFramePointer();
      initialIPLoc = thread.getContextRegisters().getIPLocation();
    } else {                 /* top frame explicitly defined */
      ip = Magic.getReturnAddress(topFrame);
      fp = Magic.getCallerFramePointer(topFrame);
      initialIPLoc = thread.getContextRegisters().getIPLocation(); // FIXME
    }

    /* Registers */
    reportDelayedRootEdge(trace, Magic.objectAsAddress(thread).plus(Entrypoints.threadContextRegistersField.getOffset()));
    reportDelayedRootEdge(trace, Magic.objectAsAddress(thread).plus(Entrypoints.threadExceptionRegistersField.getOffset()));

    /* Scan the JNI Env field */
    if (thread.getJNIEnv() != null) {
      reportDelayedRootEdge(trace, Magic.objectAsAddress(thread).plus(Entrypoints.jniEnvField.getOffset()));
      reportDelayedRootEdge(trace, Magic.objectAsAddress(thread.getJNIEnv()).plus(Entrypoints.JNIRefsField.getOffset()));
      reportDelayedRootEdge(trace, Magic.objectAsAddress(thread.getJNIEnv()).plus(Entrypoints.JNIEnvSavedPRField.getOffset()));
      reportDelayedRootEdge(trace, Magic.objectAsAddress(thread.getJNIEnv()).plus(Entrypoints.JNIPendingExceptionField.getOffset()));
    }

    /* Grab the ScanThread instance associated with this thread */
    ScanThread scanner = Magic.threadAsCollectorThread(Scheduler.getCurrentThread()).getThreadScanner();

    /* scan the stack */
    scanner.startScan(trace, processCodeLocations, thread, gprs, ip, fp, initialIPLoc, topFrame);
  }

  /**
   * Initializes a ScanThread instance, and then scans a stack
   * associated with a thread, and places references in deques (one for
   * object pointers, one for interior code pointers).  If the thread
   * scan fails, the thread is rescanned verbosely and a failure
   * occurs.<p>
   *
   * The various state associated with stack scanning is captured by
   * instance variables of this type, which are initialized here.
   *
   * @param trace The trace instance to use for reporting locations.
   * @param thread Thread for the thread whose stack is being scanned
   * @param gprs The general purpose registers associated with the
   * stack being scanned (normally extracted from the thread).
   * @param ip The instruction pointer for the top frame of the stack
   * we're about to scan.
   * @param fp The frame pointer for the top frame of the stack we're
   * about to scan.
   */
  private void startScan(TraceLocal trace,
                         boolean processCodeLocations,
                         RVMThread thread, Address gprs, Address ip,
                         Address fp, Address initialIPLoc, Address topFrame) {
    this.trace = trace;
    this.processCodeLocations = processCodeLocations;
    this.thread = thread;
    this.failed = false;
    this.ip = ip;
    this.fp = fp;
    this.initialIPLoc = initialIPLoc;
    this.topFrame = topFrame;
    scanThreadInternal(gprs, DEFAULT_VERBOSITY);
    if (failed) {
       /* reinitialize and rescan verbosly on failure */
      this.ip = ip;
      this.fp = fp;
      this.topFrame = topFrame;
      scanThreadInternal(gprs, FAILURE_VERBOSITY);
      VM.sysFail("Error encountered while scanning stack");
    }
  }

  /**
   * The main stack scanning loop.<p>
   *
   * Walk the stack one frame at a time, top (lo) to bottom (hi),<p>
   *
   * @param gprs The general purpose registers associated with the
   * stack being scanned (normally extracted from the thread).
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private void scanThreadInternal(Address gprs, int verbosity) {
    if (verbosity >= 1) Log.writeln("--- Start Of Stack Scan ---\n");
    if (VM.VerifyAssertions) assertImmovableInCurrentCollection();

    /* first find any references to exception handlers in the registers */
    getHWExceptionRegisters();

    /* reinitialize the stack iterator group */
    iteratorGroup.newStackWalk(thread, gprs);

    if (verbosity >= 1) dumpTopFrameInfo(verbosity);

    /* scan each frame if a non-empty stack */
    if (fp.NE(ArchitectureSpecific.StackframeLayoutConstants.STACKFRAME_SENTINEL_FP)) {
      prevFp = Address.zero();
      /* At start of loop:
         fp -> frame for method invocation being processed
         ip -> instruction pointer in the method (normally a call site) */
      while (Magic.getCallerFramePointer(fp).NE(ArchitectureSpecific.StackframeLayoutConstants.STACKFRAME_SENTINEL_FP)) {
        prevFp = scanFrame(verbosity);
        ip = Magic.getReturnAddress(fp);
        fp = Magic.getCallerFramePointer(fp);
      }
    }

    /* If a thread started via createVM or attachVM, base may need scaning */
    checkJNIBase();

    if (verbosity >= 1) Log.writeln("--- End Of Stack Scan ---\n");
  }

  /**
   * When an exception occurs, registers are saved temporarily.  If
   * the stack being scanned is in this state, we need to scan those
   * registers for code pointers.  If the codeLocations deque is null,
   * then scanning for code pointers is not required, so we don't need
   * to do anything. (SB: Why only code pointers?).
   *
   * Dave G:  The contents of the GPRs of the exceptionRegisters
   * are handled during normal stack scanning
   * (@see org.jikesrvm.runtime.compilers.common.HardwareTrapCompiledMethod.
   * It looks to me like the main goal of this method is to ensure that the
   * method in which the trap happened isn't treated as dead code and collected
   * (if it's been marked as obsolete, we are setting its activeOnStackFlag below).
   *
   */
  private void getHWExceptionRegisters() {
    ArchitectureSpecific.Registers exReg = thread.getExceptionRegisters();
    if (processCodeLocations && exReg.inuse) {
      Address ip = exReg.ip;
      CompiledMethod compiledMethod = CompiledMethods.findMethodForInstruction(ip);
      if (VM.VerifyAssertions) {
        VM._assert(compiledMethod != null);
        VM._assert(compiledMethod.containsReturnAddress(ip));
      }
      compiledMethod.setActiveOnStack();
      ObjectReference code = ObjectReference.fromObject(compiledMethod.getEntryCodeArray());
      Address ipLoc = exReg.getIPLocation();
      if (VM.VerifyAssertions) VM._assert(ip == ipLoc.loadAddress());
      processCodeLocation(code, ipLoc);
    }
  }

  /**
   * Push a code pointer location onto the code locations deque,
   * optionally performing a sanity check first.<p>
   *
   * @param code The code object into which this interior pointer points
   * @param ipLoc The location of the pointer into this code object
   */
  @Inline
  private void processCodeLocation(ObjectReference code, Address ipLoc) {
    if (VALIDATE_REFS) {
      Address ip = ipLoc.loadAddress();
      Offset offset = ip.diff(code.toAddress());

      if (offset.sLT(Offset.zero()) ||
          offset.sGT(Offset.fromIntZeroExtend(ObjectModel.getObjectSize(code)))) {
        Log.writeln("ERROR: Suspiciously large offset of interior pointer from object base");
        Log.write("       object base = "); Log.writeln(code);
        Log.write("       interior reference = "); Log.writeln(ip);
        Log.write("       offset = "); Log.writeln(offset);
        Log.write("       interior ref loc = "); Log.writeln(ipLoc);
        if (!failed) failed = true;
      }
    }
    trace.processInteriorEdge(code, ipLoc, true);
  }

  /***********************************************************************
   *
   * Frame scanning methods
   */

  /**
   * Scan the current stack frame.<p>
   *
   * First the various iterators are set up, then the frame is scanned
   * for regular pointers, before scanning for code pointers.  The
   * iterators are then cleaned up, and native frames skipped if
   * necessary.
   *
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private Address scanFrame(int verbosity) {
    /* set up iterators etc, and skip the frame if appropriate */
    if (!setUpFrame(verbosity)) return fp;

    /* scan the frame for object pointers */
    scanFrameForObjects(verbosity);

    /* scan the frame for pointers to code */
    if (processCodeLocations && compiledMethodType != CompiledMethod.TRAP)
      processFrameForCode(verbosity);

    iterator.cleanupPointers();

    /* skip preceeding native frames if this frame is a native bridge */
    if (compiledMethodType != CompiledMethod.TRAP &&
        compiledMethod.getMethod().getDeclaringClass().hasBridgeFromNativeAnnotation()) {
      fp = RuntimeEntrypoints.unwindNativeStackFrameForGC(fp);
      if (verbosity >= 1) Log.write("scanFrame skipping native C frames\n");
    }
    return fp;
  }

  /**
   * Set up to scan the current stack frame.  This means examining the
   * frame to discover the method being invoked and then retrieving
   * the associated metadata (stack maps etc).  Certain frames should
   * not be scanned---these are identified and skipped.
   *
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   * @return True if the frame should be scanned, false if it should
   * be skipped.
   */
  private boolean setUpFrame(int verbosity) {
    /* get the compiled method ID for this frame */
    int compiledMethodId = Magic.getCompiledMethodID(fp);

    /* skip "invisible" transition frames generated by reflection and JNI) */
    if (compiledMethodId == ArchitectureSpecific.ArchConstants.INVISIBLE_METHOD_ID) {
      if (verbosity >= 1) Log.writeln("\n--- METHOD <invisible method>");
      return false;
    }

    /* establish the compiled method */
    compiledMethod = CompiledMethods.getCompiledMethod(compiledMethodId);
    compiledMethod.setActiveOnStack();  // prevents code from being collected

    compiledMethodType = compiledMethod.getCompilerType();

    if (verbosity >= 1) printMethodHeader();

    /* get the code associated with this frame */
    Offset offset = compiledMethod.getInstructionOffset(ip);

    /* initialize MapIterator for this frame */
    iterator = iteratorGroup.selectIterator(compiledMethod);
    iterator.setupIterator(compiledMethod, offset, fp);

    if (verbosity >= 2) dumpStackFrame(verbosity);
    if (verbosity >= 3) Log.writeln("--- Refs Reported By GCMap Iterator ---");

    return true;
  }

  /**
   * Identify all the object pointers stored as local variables
   * associated with (though not necessarily strictly within!) the
   * current frame.  Loop through the GC map iterator, getting the
   * address of each object pointer, adding them to the root locations
   * deque.<p>
   *
   * NOTE: Because of the callee save policy of the optimizing
   * compiler, references associated with a given frame may be in
   * callee stack frames (lower memory), <i>outside</i> the current
   * frame.  So the iterator may return locations that are outside the
   * frame being scanned.
   *
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private void scanFrameForObjects(int verbosity) {
    for (Address refaddr = iterator.getNextReferenceAddress();
         !refaddr.isZero();
         refaddr = iterator.getNextReferenceAddress()) {
      if (VALIDATE_REFS) checkReference(refaddr, verbosity);
      if (verbosity >= 3) dumpRef(refaddr, verbosity);
      reportDelayedRootEdge(trace, refaddr);
    }
  }

  /**
   * Identify all pointers into code pointers associated with a frame.
   * There are two cases to be considered: a) the instruction pointer
   * associated with each frame (stored in the thread's metadata for
   * the top frame and as a return address for all subsequent frames),
   * and b) local variables on the stack which happen to be pointers
   * to code.<p>
   *
   * FIXME: SB: Why is it that JNI frames are skipped when considering
   * top of stack frames, while boot image frames are skipped when
   * considering other frames.  Shouldn't they both be considered in
   * both cases?
   *
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private void processFrameForCode(int verbosity) {
    /* get the code object associated with this frame */
    ObjectReference code = ObjectReference.fromObject(compiledMethod.getEntryCodeArray());

    pushFrameIP(code, verbosity);
    scanFrameForCode(code);
  }

  /**
   * Push the instruction pointer associated with this frame onto the
   * code locations deque.<p>
   *
   * A stack frame represents an execution context, and thus has an
   * instruction pointer associated with it.  In the case of the top
   * frame, the instruction pointer is captured by the IP register,
   * which is preserved in the thread data structure at thread switch
   * time.  In the case of all non-top frames, the next instruction
   * pointer is stored as the return address for the <i>previous</i>
   * frame.<p>
   *
   * The address of the code pointer is pushed onto the code locations
   * deque along with the address of the code object into which it
   * points (both are required since the former is an internal
   * pointer).<p>
   *
   * The code pointers are updated later (after stack scanning) when
   * the code locations deque is processed. The pointer from RVMMethod
   * to the code object is not updated until after stack scanning, so
   * the pointer to the (uncopied) code object is available throughout
   * the stack scanning process, which enables interior pointer
   * offsets to be correctly computed.
   *
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private void pushFrameIP(ObjectReference code, int verbosity) {
    if (prevFp.isZero()) {  /* top of stack: IP in thread state */
      if (verbosity >= 2) {
        Log.write(" t.contextRegisters.ip    = ");
        Log.writeln(thread.getContextRegisters().ip);
        Log.write("*t.contextRegisters.iploc = ");
        Log.writeln(thread.getContextRegisters().getIPLocation().loadAddress());
      }
      /* skip native code, as it is not (cannot be) moved */
      if (compiledMethodType != CompiledMethod.JNI)
        processCodeLocation(code, initialIPLoc);
      else if (verbosity >=3) {
        Log.writeln("GC Warning: SKIPPING return address for JNI code");
      }
    } else {  /* below top of stack: IP is return address, in prev frame */
      Address returnAddressLoc = Magic.getReturnAddressLocation(prevFp);
      Address returnAddress = returnAddressLoc.loadAddress();
      if (verbosity >= 3) {
        Log.write("--- Processing return address "); Log.write(returnAddress);
        Log.write(" located at "); Log.writeln(returnAddressLoc);
      }
      /* skip boot image code, as it is not (cannot be) moved */
      if (!DebugUtil.addrInBootImage(returnAddress))
        processCodeLocation(code, returnAddressLoc);
    }
  }

  /**
   * Scan this frame for internal code pointers.  The GC map iterator
   * is used to identify any local variables (stored on the stack)
   * which happen to be pointers into code.<p>
   *
   * @param code The code object associated with this frame.
   */
  private void scanFrameForCode(ObjectReference code) {
    iterator.reset();
    for (Address retaddrLoc = iterator.getNextReturnAddressAddress();
         !retaddrLoc.isZero();
         retaddrLoc = iterator.getNextReturnAddressAddress())
      processCodeLocation(code, retaddrLoc);
  }


  /**
   * AIX-specific code.<p>
   *
   * If we are scanning the stack of a thread that entered the VM via
   * a createVM or attachVM then the "bottom" of the stack had native
   * C frames instead of the usual java frames.  The JNIEnv for the
   * thread may still contain jniRefs that have been returned to the
   * native C code, but have not been reported for GC.  calling
   * getNextReferenceAddress without first calling setup... will
   * report the remaining jniRefs in the current "frame" of the
   * jniRefs stack.  (this should be the bottom frame)
   *
   * FIXME: SB: Why is this AIX specific?  Why depend on the
   * preprocessor?
   *
   */
  private void checkJNIBase() {
    if (VM.BuildForAix) {
      GCMapIterator iterator = iteratorGroup.getJniIterator();
      Address refaddr =  iterator.getNextReferenceAddress();
      while(!refaddr.isZero()) {
        reportDelayedRootEdge(trace, refaddr);
        refaddr = iterator.getNextReferenceAddress();
      }
    }
  }


  /***********************************************************************
   *
   * Debugging etc
   */

  /**
   * Assert that the stack is immovable.<p>
   *
   * Currently we do not allow stacks to be moved within the heap.  If
   * a stack contains native stack frames, then it is impossible for
   * us to safely move it.  Prior to the implementation of JNI, Jikes
   * RVM did allow the GC system to move thread stacks, and called a
   * special fixup routine, thread.fixupMovedStack to adjust all of
   * the special interior pointers (SP, FP).  If we implement split C
   * & Java stacks then we could allow the Java stacks to be moved,
   * but we can't move the native stack.
   */
  private void assertImmovableInCurrentCollection() {
    VM._assert(trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread.getStack())));
    VM._assert(trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread)));
    VM._assert(trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread.getStack())));
    VM._assert(thread.getJNIEnv() == null || trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread.getJNIEnv())));
    VM._assert(thread.getJNIEnv() == null || thread.getJNIEnv().refsArray() == null || trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread.getJNIEnv().refsArray())));
    VM._assert(trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread.getContextRegisters())));
    VM._assert(trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread.getContextRegisters().gprs)));
    VM._assert(trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread.getExceptionRegisters())));
    VM._assert(trace.willNotMoveInCurrentCollection(ObjectReference.fromObject(thread.getExceptionRegisters().gprs)));
  }

  /**
   * Print out the basic information associated with the top frame on
   * the stack.
   *
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private void dumpTopFrameInfo(int verbosity) {
    Log.write("   topFrame = "); Log.writeln(topFrame);
    Log.write("         ip = "); Log.writeln(ip);
    Log.write("         fp = "); Log.writeln(fp);
    Log.write("  registers.ip = "); Log.writeln(thread.getContextRegisters().ip);
    if (verbosity >= 2 && thread.getJNIEnv() != null)
      thread.getJNIEnv().dumpJniRefsStack();
  }

  /**
   * Print out information associated with a reference.
   *
   * @param refaddr The address of the reference in question.
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private void dumpRef(Address refaddr, int verbosity) {
    ObjectReference ref = refaddr.loadObjectReference();
    VM.sysWrite(refaddr);
    if (verbosity >= 4) {
      VM.sysWrite(":"); MemoryManager.dumpRef(ref);
    } else
      VM.sysWriteln();
  }

  /**
   * Check that a reference encountered during scanning is valid.  If
   * the reference is invalid, dump stack and die.
   *
   * @param refaddr The address of the reference in question.
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private void checkReference(Address refaddr, int verbosity) {
    ObjectReference ref = refaddr.loadObjectReference();
    if (!MemoryManager.validRef(ref)) {
      Log.writeln();
      Log.writeln("Invalid ref reported while scanning stack");
      printMethodHeader();
      Log.write(refaddr); Log.write(":"); Log.flush(); MemoryManager.dumpRef(ref);
      dumpStackFrame(verbosity);
      Log.writeln();
      Log.writeln("Dumping stack starting at frame with bad ref:");
      Scheduler.dumpStack(ip, fp);
      /* dump stack starting at top */
      Address top_ip = thread.getContextRegisters().getInnermostInstructionAddress();
      Address top_fp = thread.getContextRegisters().getInnermostFramePointer();
      Scheduler.dumpStack(top_ip, top_fp);
      VM.sysFail("\n\nScanStack: Detected bad GC map; exiting RVM with fatal error");
    }
  }

  /**
   * Check that a reference encountered during scanning is valid.  If
   * the reference is invalid, dump stack and die.
   *
   * @param refaddr The address of the reference in question.
   */
  private static void checkReference(Address refaddr) {
    ObjectReference ref = refaddr.loadObjectReference();
    if (!MemoryManager.validRef(ref)) {
      Log.writeln();
      Log.writeln("Invalid ref reported while scanning stack");
      Log.write(refaddr); Log.write(":"); Log.flush(); MemoryManager.dumpRef(ref);
      Log.writeln();
      Log.writeln("Dumping stack:");
      Scheduler.dumpStack();
      VM.sysFail("\n\nScanStack: Detected bad GC map; exiting RVM with fatal error");
    }
  }

  /**
   * Print out the name of a method
   *
   * @param m The method to be printed
   */
  private void printMethod(RVMMethod m) {
    Log.write(m.getMemberRef().getType().getName().toByteArray()); Log.write(".");
    Log.write(m.getMemberRef().getName().toByteArray()); Log.write(" ");
    Log.write(m.getMemberRef().getDescriptor().toByteArray());
  }

  /**
   * Print out the method header for the method associated with the
   * current frame
   */
  private void printMethodHeader() {
    RVMMethod method = compiledMethod.getMethod();

    Log.write("\n--- METHOD (");
    Log.write(CompiledMethod.compilerTypeToString(compiledMethodType));
    Log.write(") ");
    if (method == null)
        Log.write("null method");
    else
        printMethod(method);
    Log.writeln();
    Log.write("--- fp = ");
    Log.write(fp);
    if (compiledMethod.isCompiled()) {
        ObjectReference codeBase = ObjectReference.fromObject(compiledMethod.getEntryCodeArray());
        Log.write("     code base = ");
        Log.write(codeBase);
        Log.write("     code offset = ");
        Log.writeln(ip.diff(codeBase.toAddress()));
        Log.write("     line number = ");
        Log.writeln(compiledMethod.findLineNumberForInstruction(ip.diff(codeBase.toAddress())));
    } else {
      Log.write("   Method is uncompiled - ip = ");
      Log.writeln(ip);
    }
  }

  /**
   * Dump the contents of a stack frame. Attempts to interpret each
   * word as an object reference
   *
   * @param verbosity The level of verbosity to be used when
   * performing the scan.
   */
  private void dumpStackFrame(int verbosity) {
    Address start,end;
    if (VM.BuildForIA32) {
      if (prevFp.isZero()) {
        start = fp.minus(20*BYTES_IN_ADDRESS);
        Log.writeln("--- 20 words of stack frame with fp = ", fp);
      } else {
        start = prevFp;    // start at callee fp
      }
      end = fp;            // end at fp
    } else {
      start = fp;                         // start at fp
      end = fp.loadAddress();   // stop at callers fp
    }

    for (Address loc = start; loc.LT(end); loc = loc.plus(BYTES_IN_ADDRESS)) {
      Log.write(loc); Log.write(" (");
      Log.write(loc.diff(start));
      Log.write("):   ");
      ObjectReference value = Selected.Collector.get().loadObjectReference(loc);
      Log.write(value);
      Log.write(" ");
      Log.flush();
      if (verbosity >= 3 && MemoryManager.objectInVM(value) && loc.NE(start) && loc.NE(end))
        MemoryManager.dumpRef(value);
      else
        Log.writeln();
    }
    Log.writeln();
  }
}
