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

import org.mmtk.plan.Plan;
import org.mmtk.plan.TransitiveClosure;
import org.mmtk.utility.heap.MonotonePageResource;
import org.mmtk.utility.heap.VMRequest;
import org.mmtk.utility.Constants;

import org.mmtk.vm.VM;

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

/**
 * This class implements tracing for a simple immortal collection
 * policy.  Under this policy all that is required is for the
 * "collector" to propogate marks in a liveness trace.  It does not
 * actually collect.  This class does not hold any state, all methods
 * are static.
 */
@Uninterruptible public final class ImmortalSpace extends Space
  implements Constants {

  /****************************************************************************
   *
   * Class variables
   */
  static final Word GC_MARK_BIT_MASK = Word.one();
  private static final int META_DATA_PAGES_PER_REGION = CARD_META_PAGES_PER_REGION;

  /****************************************************************************
   *
   * Instance variables
   */
  private Word markState = Word.zero(); // when GC off, the initialization value

  /****************************************************************************
   *
   * Initialization
   */
  /**
   * The caller specifies the region of virtual memory to be used for
   * this space.  If this region conflicts with an existing space,
   * then the constructor will fail.
   *
   * @param name The name of this space (used when printing error messages etc)
   * @param pageBudget The number of pages this space may consume
   * before consulting the plan
   * @param vmRequest An object describing the virtual memory requested.
   */
  public ImmortalSpace(String name, int pageBudget, VMRequest vmRequest) {
    super(name, false, true, vmRequest);
    if (vmRequest.isDiscontiguous()) {
      pr = new MonotonePageResource(pageBudget, this, META_DATA_PAGES_PER_REGION);
    } else {
      pr = new MonotonePageResource(pageBudget, this, start, extent, META_DATA_PAGES_PER_REGION);
    }
  }

  /** @return the current mark state */
  @Inline
  public Word getMarkState() { return markState; }

  /****************************************************************************
   *
   * Object header manipulations
   */

  /**
   * Initialize the object header post-allocation.  We need to set the mark state
   * correctly and set the logged bit if necessary.
   *
   * @param object The newly allocated object instance whose header we are initializing
   */
  public void initializeHeader(ObjectReference object) {
    Word oldValue = VM.objectModel.readAvailableBitsWord(object);
    Word newValue = oldValue.and(GC_MARK_BIT_MASK.not()).or(markState);
    if (Plan.NEEDS_LOG_BIT_IN_HEADER) newValue = newValue.or(Plan.UNLOGGED_BIT);
    VM.objectModel.writeAvailableBitsWord(object, newValue);
  }

  /**
   * Used to mark boot image objects during a parallel scan of objects during GC
   * Returns true if marking was done.
   */
  @Inline
  private static boolean testAndMark(ObjectReference object, Word value) {
    Word oldValue;
    do {
      oldValue = VM.objectModel.prepareAvailableBits(object);
      Word markBit = oldValue.and(GC_MARK_BIT_MASK);
      if (markBit.EQ(value)) return false;
    } while (!VM.objectModel.attemptAvailableBits(object, oldValue,
                                               oldValue.xor(GC_MARK_BIT_MASK)));
    return true;
  }

  /**
   * Trace a reference to an object under an immortal collection
   * policy.  If the object is not already marked, enqueue the object
   * for subsequent processing. The object is marked as (an atomic)
   * side-effect of checking whether already marked.
   *
   * @param trace The trace being conducted.
   * @param object The object to be traced.
   */
  @Inline
  public ObjectReference traceObject(TransitiveClosure trace, ObjectReference object) {
    if (testAndMark(object, markState))
      trace.processNode(object);
    return object;
  }

  /**
   * Prepare for a new collection increment.  For the immortal
   * collector we must flip the state of the mark bit between
   * collections.
   */
  public void prepare() {
    markState = GC_MARK_BIT_MASK.minus(markState);
  }

  public void release() {}

  /**
   * Release an allocated page or pages.  In this case we do nothing
   * because we only release pages enmasse.
   *
   * @param start The address of the start of the page or pages
   */
  @Inline
  public void release(Address start) {
    if (VM.VERIFY_ASSERTIONS)
      VM.assertions._assert(false); // this policy only releases pages enmasse
  }

  @Inline
  public boolean isLive(ObjectReference object) {
    return true;
  }

  /**
   * Returns if the object in question is currently thought to be reachable.
   * This is done by comparing the mark bit to the current mark state. For the
   * immortal collector reachable and live are different, making this method
   * necessary.
   *
   * @param object The address of an object in immortal space to test
   * @return True if <code>ref</code> may be a reachable object (e.g., having
   *         the current mark state).  While all immortal objects are live,
   *         some may be unreachable.
   */
  public boolean isReachable(ObjectReference object) {
    if (Plan.SCAN_BOOT_IMAGE && this == Plan.vmSpace)
      return true;  // ignore boot image "reachabilty" if we're not tracing it
    else
      return (VM.objectModel.readAvailableBitsWord(object).and(GC_MARK_BIT_MASK).EQ(markState));
  }
}
