/*
 *  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.TraceLocal;
import org.mmtk.plan.TransitiveClosure;
import org.mmtk.utility.heap.*;
import org.mmtk.utility.Constants;

import org.mmtk.vm.VM;

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

/**
 * This class implements tracing functionality for a simple copying
 * space.  Since no state needs to be held globally or locally, all
 * methods are static.
 */
@Uninterruptible public final class CopySpace extends Space
  implements Constants {

  /****************************************************************************
   *
   * Class variables
   */
  public static final int LOCAL_GC_BITS_REQUIRED = 2;
  public static final int GLOBAL_GC_BITS_REQUIRED = 0;
  public static final int GC_HEADER_WORDS_REQUIRED = 0;

  private static final int META_DATA_PAGES_PER_REGION = CARD_META_PAGES_PER_REGION;

  /*
   *  The forwarding process uses three states to deal with a GC race:
   *  1.      !GC_FORWARDED: Unforwarded
   *  2. GC_BEING_FORWARDED: Being forwarded (forwarding is underway)
   *  3.       GC_FORWARDED: Forwarded
   */
  /** If this bit is set, then forwarding of this object has commenced */
  private static final Word GC_FORWARDED = Word.one().lsh(1); // ...10
  /** If this bit is set, then forwarding of this object is incomplete */
  private static final Word GC_BEING_FORWARDED  = Word.one().lsh(2).minus(Word.one());  // ...11
  /** This mask is used to reveal which state this object is in with respect to forwarding */
  private static final Word GC_FORWARDING_MASK  = GC_FORWARDED.or(GC_BEING_FORWARDED);

  /** A single bit is used to indicate a mark when tracing (but not copying) the space */
  private static final Word GC_MARK_BIT_MASK = Word.one();

  /****************************************************************************
   *
   * Instance variables
   */
  private boolean fromSpace = true;

  /****************************************************************************
   *
   * 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 fromSpace The does this instance start life as from-space
   * (or to-space)?
   * @param vmRequest An object describing the virtual memory requested.
   */
  public CopySpace(String name, int pageBudget, boolean fromSpace, VMRequest vmRequest) {
    super(name, true, false, vmRequest);
    this.fromSpace = fromSpace;
    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);
    }
  }

  /****************************************************************************
   *
   * Prepare and release
   */

  /**
   * Prepare this space instance for a collection.  Set the
   * "fromSpace" field according to whether this space is the
   * source or target of the collection.
   *
   * @param fromSpace Set the fromSpace field to this value
   */
  public void prepare(boolean fromSpace) { this.fromSpace = fromSpace; }

  /**
   * Release this copy space after a collection.  This means releasing
   * all pages associated with this (now empty) space.
   */
  public void release() {
    ((MonotonePageResource) pr).reset();
    lastDiscontiguousRegion = Address.zero();
  }

  /**
   * 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
  }

  /****************************************************************************
   *
   * Tracing and forwarding
   */

  /**
   * Trace an object under a copying collection policy.
   *
   * We use a tri-state algorithm to deal with races to forward
   * the object.  The tracer must wait if the object is concurrently
   * being forwarded by another thread.
   *
   * If the object is already forwarded, the copy is returned.
   * Otherwise, the object is forwarded and the copy is returned.
   *
   * @param trace The trace being conducted.
   * @param object The object to be forwarded.
   * @return The forwarded object.
   */
  @Inline
  public ObjectReference traceObject(TransitiveClosure trace, ObjectReference object) {
    VM.assertions.fail("CopySpace.traceLocal called without allocator");
    return ObjectReference.nullReference();
  }

  /**
   * Trace an object under a copying collection policy.
   *
   * We use a tri-state algorithm to deal with races to forward
   * the object.  The tracer must wait if the object is concurrently
   * being forwarded by another thread.
   *
   * If the object is already forwarded, the copy is returned.
   * Otherwise, the object is forwarded and the copy is returned.
   *
   * @param trace The trace being conducted.
   * @param object The object to be forwarded.
   * @param allocator The allocator to use when copying.
   * @return The forwarded object.
   */
  @Inline
  public ObjectReference traceObject(TransitiveClosure trace, ObjectReference object, int allocator) {
    /* If the object in question is already in to-space, then do nothing */
    if (!fromSpace) return object;

    /* Try to forward the object */
    Word forwardingPtr = attemptToForward(object);

    if (stateIsForwardedOrBeingForwarded(forwardingPtr)) {
      /* Somebody else got to it first. */

      /* We must wait (spin) if the object is not yet fully forwarded */
      while (stateIsBeingForwarded(forwardingPtr))
        forwardingPtr = getForwardingWord(object);

      /* Now extract the object reference from the forwarding word and return it */
      return forwardingPtr.and(GC_FORWARDING_MASK.not()).toAddress().toObjectReference();
    } else {
      /* We are the designated copier, so forward it and enqueue it */

      ObjectReference newObject = VM.objectModel.copy(object, allocator);
      setForwardingPointer(object, newObject);
      trace.processNode(newObject); // Scan it later

      return newObject;
    }
  }

  /**
   * Return true if this object is live in this GC
   *
   * @param object The object in question
   * @return True if this object is live in this GC (has it been forwarded?)
   */
  public boolean isLive(ObjectReference object) {
    return isForwarded(object);
  }

  /**
   * Has the object in this space been reached during the current collection.
   * This is used for GC Tracing.
   *
   * @param object The object reference.
   * @return True if the object is reachable.
   */
  public boolean isReachable(ObjectReference object) {
    return !fromSpace || isForwarded(object);
  }

  /****************************************************************************
   *
   * Non-copying tracing (just mark, don't forward)
   */

  /**
   * Mark an object as having been traversed, *WITHOUT* forwarding the object.
   * This is only used when
   *
   * @param object The object to be marked
   * @param markState The sense of the mark bit (flips from 0 to 1)
   */
  @Inline
  public static void markObject(TraceLocal trace, ObjectReference object,
      Word markState) {
    if (testAndMark(object, markState))
      trace.processNode(object);
  }

  /****************************************************************************
   *
   * Header manipulation
   */

  /**
   * Perform any required post-allocation initialization
   *
   * <i>Nothing to be done in this case</i>
   *
   * @param object the object ref to the storage to be initialized
   */
   @Inline
   public void postAlloc(ObjectReference object) {}

  /**
   * Clear the GC portion of the header for an object.
   *
   * @param object the object ref to the storage to be initialized
   */
  @Inline
  public static void clearGCBits(ObjectReference object) {
    Word header = VM.objectModel.readAvailableBitsWord(object);
    VM.objectModel.writeAvailableBitsWord(object, header.and(GC_FORWARDING_MASK.not()));
  }

  /**
   * Has an object been forwarded?
   *
   * @param object The object to be checked
   * @return True if the object has been forwarded
   */
  @Inline
  public static boolean isForwarded(ObjectReference object) {
    return stateIsForwarded(getForwardingWord(object));
  }

  /**
   * Has an object been forwarded or being forwarded?
   *
   * @param object The object to be checked
   * @return True if the object has been forwarded or is being forwarded
   */
  @Inline
  public static boolean isForwardedOrBeingForwarded(ObjectReference object) {
    return stateIsForwardedOrBeingForwarded(getForwardingWord(object));
  }

  /**
   * Non-atomic read of forwarding pointer word
   *
   * @param object The object whose forwarding word is to be read
   * @return The forwarding word stored in <code>object</code>'s
   * header.
   */
  @Inline
  private static Word getForwardingWord(ObjectReference object) {
    return VM.objectModel.readAvailableBitsWord(object);
  }

  /**
   * Non-atomic read of forwarding pointer
   *
   * @param object The object whose forwarding pointer is to be read
   * @return The forwarding pointer stored in <code>object</code>'s
   * header.
   */
  @Inline
  public static ObjectReference getForwardingPointer(ObjectReference object) {
    return getForwardingWord(object).and(GC_FORWARDING_MASK.not()).toAddress().toObjectReference();
  }

  /**
   * Used to mark boot image objects during a parallel scan of objects
   * during GC Returns true if marking was done.
   *
   * @param object The object to be marked
   * @param value The value to store in the mark bit
   */
  @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;
  }

  /**
   * Either return the forwarding pointer if the object is already
   * forwarded (or being forwarded) or write the bit pattern that
   * indicates that the object is being forwarded
   *
   * @param object The object to be forwarded
   * @return The forwarding pointer for the object if it has already
   * been forwarded.
   */
  @Inline
  private static Word attemptToForward(ObjectReference object) {
    Word oldValue;
    do {
      oldValue = VM.objectModel.prepareAvailableBits(object);
      if (oldValue.and(GC_FORWARDING_MASK).EQ(GC_FORWARDED)) return oldValue;
    } while (!VM.objectModel.attemptAvailableBits(object, oldValue,
                                                oldValue.or(GC_BEING_FORWARDED)));
    return oldValue;
  }

  /**
   * Is the state of the forwarding word being forwarded?
   *
   * @param fword A forwarding word.
   * @return True if the forwarding word's state is being forwarded.
   */
  @Inline
  private static boolean stateIsBeingForwarded(Word fword) {
    return fword.and(GC_FORWARDING_MASK).EQ(GC_BEING_FORWARDED);
  }

  /**
   * Is the state of the forwarding word forwarded?
   *
   * @param fword A forwarding word.
   * @return True if the forwarding word's state is forwarded.
   */
  @Inline
  private static boolean stateIsForwarded(Word fword) {
    return fword.and(GC_FORWARDING_MASK).EQ(GC_FORWARDED);
  }

  /**
   * Is the state of the forwarding word forwarded or being forwarded?
   *
   * @param fword A forwarding word.
   * @return True if the forwarding word's state is forwarded or being
   *         forwarded.
   */
  @Inline
  public static boolean stateIsForwardedOrBeingForwarded(Word fword) {
    return !(fword.and(GC_FORWARDED).isZero());
  }

  /**
   * Non-atomic write of forwarding pointer word (assumption, thread
   * doing the set has done attempt to forward and owns the right to
   * copy the object)
   *
   * @param object The object whose forwarding pointer is to be set
   * @param ptr The forwarding pointer to be stored in the object's
   * forwarding word
   */
  @Inline
  private static void setForwardingPointer(ObjectReference object,
                                           ObjectReference ptr) {
    VM.objectModel.writeAvailableBitsWord(object, ptr.toAddress().toWord().or(GC_FORWARDED));
  }
}
