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

import org.mmtk.plan.Plan;
import org.mmtk.plan.Trace;
import org.mmtk.plan.Simple;
import org.mmtk.plan.TraceLocal;
import org.mmtk.policy.Space;
import org.mmtk.utility.Constants;
import org.mmtk.utility.Log;

import org.mmtk.vm.VM;

import org.vmmagic.pragma.*;
import org.vmmagic.unboxed.*;

/**
 * This class performs sanity checks for Simple collectors.
 */
@Uninterruptible
public final class SanityChecker implements Constants {

  /* Counters */
  public static long referenceCount;
  public static long rootReferenceCount;
  public static long danglingReferenceCount;
  public static long nullReferenceCount;
  public static long liveObjectCount;

  public static final int DEAD = -2;
  public static final int ALIVE = -1;
  public static final int UNSURE = 0;

  public static final int LOG_SANITY_DATA_SIZE = 24;

  /* Tracing */
  public Trace rootTrace;
  public Trace checkTrace;
  private final SanityDataTable sanityTable;
  private boolean preGCSanity;

  /* Local, but we only run the check trace single-threaded. */
  final SanityTraceLocal checkTraceLocal;

  /* Linear scanning */
  private final SanityLinearScan scanner = new SanityLinearScan(this);

  /****************************************************************************
   * Constants
   */
  public SanityChecker() {
    sanityTable = new SanityDataTable(Plan.sanitySpace, LOG_SANITY_DATA_SIZE);
    checkTrace = new Trace(Plan.sanitySpace);
    rootTrace = new Trace(Plan.sanitySpace);
    checkTraceLocal = new SanityTraceLocal(checkTrace, this);
  }

  /**
   * Perform any sanity checking collection phases.
   *
   * @param phaseId The id to proces
   * @return True if the phase was handled.
   */
  @NoInline
  public boolean collectionPhase(int phaseId) {
    if (phaseId == Simple.SANITY_SET_PREGC) {
      preGCSanity = true;
      return true;
    }

    if (phaseId == Simple.SANITY_SET_POSTGC) {
      preGCSanity = false;
      return true;
    }

    if (phaseId == Simple.SANITY_PREPARE) {
      Log.writeln("");
      Log.write("============================== GC Sanity Checking ");
      Log.writeln("==============================");
      Log.writeln(preGCSanity ? "Performing Pre-GC Sanity Checks..." : "Performing Post-GC Sanity Checks...");

      // Reset counters
      referenceCount = 0;
      nullReferenceCount = 0;
      liveObjectCount = 0;
      danglingReferenceCount = 0;
      rootReferenceCount = 0;

      // Clear data space
      sanityTable.acquireTable();

      // Root trace
      rootTrace.prepareNonBlocking();

      // Checking trace
      checkTrace.prepareNonBlocking();
      checkTraceLocal.prepare();
      return true;
    }

    if (phaseId == Simple.SANITY_ROOTS) {
      VM.scanning.resetThreadCounter();
      return true;
    }

    if (phaseId == Simple.SANITY_BUILD_TABLE) {
      // Trace, checking for dangling pointers
      checkTraceLocal.completeTrace();
      return true;
    }

    if (phaseId == Simple.SANITY_CHECK_TABLE) {
      // Iterate over the reachable objects.
      Address curr = sanityTable.getFirst();
      while (!curr.isZero()) {
        ObjectReference ref = SanityDataTable.getObjectReference(curr);
        int normalRC = SanityDataTable.getNormalRC(curr);
        int rootRC = SanityDataTable.getRootRC(curr);
        if (!preGCSanity) {
          int expectedRC = VM.activePlan.global().sanityExpectedRC(ref, rootRC);
          switch (expectedRC) {
          case SanityChecker.ALIVE:
          case SanityChecker.UNSURE:
            // Always ok.
            break;
          case SanityChecker.DEAD:
            // Never ok.
            Log.write("ERROR: SanityRC = ");
            Log.write(normalRC);
            Log.write(", SpaceRC = 0 ");
            SanityChecker.dumpObjectInformation(ref);
            break;
          default:
            // A mismatch in an RC space
            if (normalRC != expectedRC && VM.activePlan.global().lastCollectionFullHeap()) {
              Log.write("WARNING: SanityRC = ");
              Log.write(normalRC);
              Log.write(", SpaceRC = ");
              Log.write(expectedRC);
              Log.write(" ");
              SanityChecker.dumpObjectInformation(ref);
              break;
            }
          }
        }
        curr = sanityTable.getNext(curr);
      }

      if (!preGCSanity && VM.activePlan.global().lastCollectionFullHeap()) {
        VM.activePlan.global().sanityLinearScan(scanner);
      }
      return true;
    }

    if (phaseId == Simple.SANITY_RELEASE) {
      checkTrace.release();
      sanityTable.releaseTable();
      checkTraceLocal.release();

      Log.writeln("roots\tobjects\trefs\tnull");
      Log.write(rootReferenceCount);Log.write("\t");
      Log.write(liveObjectCount);Log.write("\t");
      Log.write(referenceCount);Log.write("\t");
      Log.writeln(nullReferenceCount);

      Log.write("========================================");
      Log.writeln("========================================");

      return true;
    }

    return false;
  }

  /**
   * Process an object during a linear scan of the heap. We have already checked
   * all objects in the table. So we are only interested in objects that are not in
   * the sanity table here. We are therefore only looking for leaks here.
   *
   * @param object The object being scanned.
   */
  public void scanProcessObject(ObjectReference object) {
    if (sanityTable.getEntry(object, false).isZero()) {
      // Is this a leak?
      int expectedRC = VM.activePlan.global().sanityExpectedRC(object, 0);

      if (expectedRC == SanityChecker.UNSURE) {
        // Probably not.
        return;
      }

      // Possibly
      Log.write("WARNING: Possible leak, SpaceRC = ");
      Log.write(expectedRC);
      Log.write(" ");
      SanityChecker.dumpObjectInformation(object);
    }
  }

  /**
   * Process an object during sanity checking, validating data,
   * incrementing counters and enqueuing if this is the first
   * visit to the object.
   *
   * @param object The object to mark.
   * @param root True If the object is a root.
   */
  public void processObject(TraceLocal trace, ObjectReference object, boolean root) {
    SanityChecker.referenceCount++;
    if (root) SanityChecker.rootReferenceCount++;

    if (object.isNull()) {
      SanityChecker.nullReferenceCount++;
      return;
    }

    if (Plan.SCAN_BOOT_IMAGE && Space.isInSpace(Plan.VM_SPACE, object)) {
      return;
    }

    // Get the table entry.
    Address tableEntry = sanityTable.getEntry(object, true);

    if (SanityDataTable.incRC(tableEntry, root)) {
      SanityChecker.liveObjectCount++;
      trace.processNode(object);
    }
  }

  /**
   * Print out object information (used for warning and error messages)
   *
   * @param object The object to dump info for.
   */
  public static void dumpObjectInformation(ObjectReference object) {
    Log.write(object);
    Log.write(" [");
    Log.write(Space.getSpaceForObject(object).getName());
    Log.write("] ");
    Log.writeln(VM.objectModel.getTypeDescriptor(object));
  }
}
