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

import org.mmtk.plan.Plan;
import org.mmtk.policy.Space;
import org.mmtk.utility.Constants;
import org.mmtk.utility.options.ProtectOnRelease;
import org.mmtk.utility.options.Options;

import org.mmtk.vm.Lock;
import org.mmtk.vm.VM;

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

/**
 * This class manages the allocation of pages for a space.  When a
 * page is requested by the space both a page budget and the use of
 * virtual address space are checked.  If the request for space can't
 * be satisfied (for either reason) a GC may be triggered.<p>
 *
 * This class is abstract, and is subclassed with monotone and
 * freelist variants, which reflect monotonic and ad hoc space usage
 * respectively.  Monotonic use is easier to manage, but is obviously
 * more restrictive (useful for copying collectors which allocate
 * monotonically before freeing the entire space and starting over).
 */
@Uninterruptible
public abstract class PageResource implements Constants {

  /****************************************************************************
   *
   * Class variables
   */
  protected static final boolean ZERO_ON_RELEASE = false; // debugging

  private static final Lock classLock;
  private static long cumulativeCommitted = 0;


  /****************************************************************************
   *
   * Instance variables
   */

  // page budgeting
  protected int reserved;
  protected int committed;
  protected int required;
  private final int pageBudget;

  protected final boolean contiguous;
  protected final Space space;
  protected Address start; // only for contiguous

  // locking
  private final Lock gcLock; // used during GC
  private final Lock mutatorLock; // used by mutators

  /****************************************************************************
   *
   * Initialization
   */
  static {
    classLock = VM.newLock("PageResource");
    Options.protectOnRelease = new ProtectOnRelease();
  }

  /**
   * Constructor
   *
   * @param pageBudget The budget of pages available to this memory
   * manager before it must poll the collector.
   * @param space The space to which this resource is attached
   */
  private PageResource(int pageBudget, Space space, boolean contiguous) {
    this.pageBudget = pageBudget;
    this.contiguous = contiguous;
    this.space = space;
    gcLock = VM.newLock(space.getName() + ".gcLock");
    mutatorLock = VM.newLock(space.getName() + ".mutatorLock");
  }

  /**
   * Constructor for discontiguous spaces
   *
   * @param pageBudget The budget of pages available to this memory
   * manager before it must poll the collector.
   * @param space The space to which this resource is attached
   */
  PageResource(int pageBudget, Space space) {
    this(pageBudget, space, false);
  }

  /**
   * Constructor for contiguous spaces
   *
   * @param pageBudget The budget of pages available to this memory
   * manager before it must poll the collector.
   * @param space The space to which this resource is attached
   */
  PageResource(int pageBudget, Space space, Address start) {
    this(pageBudget, space, true);
    this.start = start;
  }

  /**
   * Return the number of available physical pages for this resource.
   *
   * Note: This just considers physical pages (ie virtual memory pages
   * allocated for use by this resource). This calculation is orthogonal
   * to and does not consider any restrictions on the number of pages
   * this resource may actually use at any time (ie the number of
   * committed and reserved pages).<p>
   *
   * Note: The calculation is made on the assumption that all space that
   * could be assigned to this resource would be assigned to this resource
   * (ie the unused discontiguous space could just as likely be assigned
   * to another competing resource).
   *
   * @return The number of available physical pages for this resource.
   */
   public abstract int getAvailablePhysicalPages();

  /**
   * Reserve pages.<p>
   *
   * The role of reserving pages is that it allows the request to be
   * noted as pending (the difference between committed and reserved
   * indicates pending requests).  If the request would exceed the
   * page budget then the caller must poll in case a GC is necessary.
   *
   * @param pages The number of pages requested
   * @return True if the page budget could satisfy the request.
   */
  @Inline
  public final boolean reservePages(int pages) {
    lock();
    required += adjustForMetaData(pages);
    reserved = committed + required;
    boolean satisfied = reserved <= pageBudget;
    unlock();
    return satisfied;
  }

  /**
   * Remove a request to the space.
   *
   * @param pages The number of pages in the request.
   */
  @Inline
  public final void clearRequest(int pages) {
    lock();
    required -= adjustForMetaData(pages);
    unlock();
  }

  /**
   * Reserve pages unconditionally.<p>
   *
   * An example of where this is useful is in cases where it is
   * desirable to put some space aside as head-room.  By
   * unconditionally reserving the pages, the pages are counted
   * against the collectors budget.  When the space is actually
   * needed, the pages can be unconditionally released, freeing
   * the pages for other purposes.
   *
   * @param pages The number of pages to be unconditionally
   * reserved.
   */
  public final void unconditionallyReservePages(int pages) {
    lock();
    committed += pages;
    reserved += pages;
    unlock();
  }

  /**
   * Release pages unconditionally.<p>
   *
   * This call allows pages to be unconditionally removed from
   * the collectors page budget.
   *
   * @see #unconditionallyReservePages
   * @param pages The number of pages to be unconditionally
   * released.
   */
  public final void unconditionallyReleasePages(int pages) {
    lock();
    committed -= pages;
    reserved -= pages;
    unlock();
  }

  abstract Address allocPages(int pages);

  /**
   * Adjust a page request to include metadata requirements for a request
   * of the given size. This must be a pure function, that is it does not
   * depend on the state of the PageResource.
   *
   * @param pages The size of the pending allocation in pages
   * @return The number of required pages, inclusive of any metadata
   */
  public abstract int adjustForMetaData(int pages);

  /**
   * Allocate pages in virtual memory, returning zero on failure.<p>
   *
   * If the request cannot be satisfied, zero is returned and it
   * falls to the caller to trigger the GC.
   *
   * Call <code>allocPages</code> (subclass) to find the pages in
   * virtual memory.  If successful then commit the pending page
   * request and return the address of the first page.
   *
   * @param pages The number of pages requested
   * @return The address of the first of <code>pages</code> pages, or
   * zero on failure.
   */
  @Inline
  public final Address getNewPages(int pages) {
    return allocPages(pages);
  }

  /**
   * Commit pages to the page budget.  This is called after
   * successfully determining that the request can be satisfied by
   * both the page budget and virtual memory.  This simply accounts
   * for the descrepency between <code>committed</code> and
   * <code>reserved</code> while the request was pending.
   *
   * This *MUST* be called by each PageResource during the
   * allocPages, and the caller must hold the lock.
   *
   * @param requestedPages The number of pages from this request
   * @param totalPages The number of pages
   */
  protected void commitPages(int requestedPages, int totalPages) {
    int predictedPages = adjustForMetaData(requestedPages);
    int delta = totalPages - predictedPages;
    required -= predictedPages;
    reserved += delta;
    committed += totalPages;
    if (!Plan.gcInProgress())
      addToCommitted(totalPages); // only count mutator pages
  }

  /**
   * Return the number of reserved pages
   *
   * @return The number of reserved pages.
   */
  public final int reservedPages() { return reserved; }

  /**
   * Return the number of committed pages
   *
   * @return The number of committed pages.
   */
  public final int committedPages() { return committed; }

  /**
   * Return the number of required pages
   *
   * @return The number of required pages.
   */
  public final int requiredPages() { return required; }

  /**
   * Return the cumulative number of committed pages
   *
   * @return The cumulative number of committed pages.
   */
  public static long cumulativeCommittedPages() { return cumulativeCommitted; }

  /**
   * Add to the total cumulative committed page count.
   *
   * @param pages The number of pages to be added.
   */
  private static void addToCommitted(int pages) {
    classLock.acquire();
    cumulativeCommitted += pages;
    classLock.release();
  }

  /**
   * Acquire the appropriate lock depending on whether the context is
   * GC or mutator.
   */
  protected final void lock() {
    if (Plan.gcInProgress())
      gcLock.acquire();
    else
      mutatorLock.acquire();
  }

  /**
   * Release the appropriate lock depending on whether the context is
   * GC or mutator.
   */
  protected final void unlock() {
    if (Plan.gcInProgress())
      gcLock.release();
    else
      mutatorLock.release();
  }
}
