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

import org.mmtk.plan.Plan;
import org.mmtk.vm.VM;

import org.vmmagic.pragma.*;

/**
 * This is a very simple, generic malloc-free allocator.  It works
 * abstractly, in "units", which the user may associate with some
 * other allocatable resource (e.g. heap blocks).  The user issues
 * requests for N units and the allocator returns the index of the
 * first of a contiguous set of N units or fails, returning -1.  The
 * user frees the block of N units by calling <code>free()</code> with
 * the index of the first unit as the argument.<p>
 *
 * Properties/Constraints:<ul>
 *   <li> The allocator consumes one word per allocatable unit (plus
 *   a fixed overhead of about 128 words).</li>
 *   <li> The allocator can only deal with MAX_UNITS units (see below for
 *   the value).</li>
 * </ul>
 *
 * The basic data structure used by the algorithm is a large table,
 * with one word per allocatable unit.  Each word is used in a
 * number of different ways, some combination of "undefined" (32),
 * "free/used" (1), "multi/single" (1), "prev" (15), "next" (15) &
 * "size" (15) where field sizes in bits are in parenthesis.
 * <pre>
 *                       +-+-+-----------+-----------+
 *                       |f|m|    prev   | next/size |
 *                       +-+-+-----------+-----------+
 *
 *   - single free unit: "free", "single", "prev", "next"
 *   - single used unit: "used", "single"
 *    - contiguous free units
 *     . first unit: "free", "multi", "prev", "next"
 *     . second unit: "free", "multi", "size"
 *     . last unit: "free", "multi", "size"
 *    - contiguous used units
 *     . first unit: "used", "multi", "prev", "next"
 *     . second unit: "used", "multi", "size"
 *     . last unit: "used", "multi", "size"
 *    - any other unit: undefined
 *
 *                       +-+-+-----------+-----------+
 *   top sentinel        |0|0|    tail   |   head    |  [-1]
 *                       +-+-+-----------+-----------+
 *                                     ....
 *            /--------  +-+-+-----------+-----------+
 *            |          |1|1|   prev    |   next    |  [j]
 *            |          +-+-+-----------+-----------+
 *            |          |1|1|           |   size    |  [j+1]
 *         free multi    +-+-+-----------+-----------+
 *         unit block    |              ...          |  ...
 *            |          +-+-+-----------+-----------+
 *            |          |1|1|           |   size    |
 *           >--------  +-+-+-----------+-----------+
 *   single free unit    |1|0|   prev    |   next    |
 *           >--------  +-+-+-----------+-----------+
 *   single used unit    |0|0|                       |
 *           >--------  +-+-+-----------------------+
 *            |          |0|1|                       |
 *            |          +-+-+-----------+-----------+
 *            |          |0|1|           |   size    |
 *         used multi    +-+-+-----------+-----------+
 *         unit block    |              ...          |
 *            |          +-+-+-----------+-----------+
 *            |          |0|1|           |   size    |
 *            \--------  +-+-+-----------+-----------+
 *                                     ....
 *                       +-+-+-----------------------+
 *   bottom sentinel     |0|0|                       |  [N]
 *                       +-+-+-----------------------+
 * </pre>
 * The sentinels serve as guards against out of range coalescing
 * because they both appear as "used" blocks and so will never
 * coalesce.  The top sentinel also serves as the head and tail of
 * the doubly linked list of free blocks.
 */
@Uninterruptible
public final class GenericFreeList extends BaseGenericFreeList implements Constants {

  /****************************************************************************
   *
   * Public instance methods
   */

  /**
   * Constructor
   *
   * @param units The number of allocatable units for this free list
   */
  public GenericFreeList(int units) {
    this(units, units);
  }

  /**
   * Constructor
   *
   * @param units The number of allocatable units for this free list
   * @param grain Units are allocated such that they will never cross this granularity boundary
   */
  public GenericFreeList(int units, int grain) {
    this(units, grain, 1);
  }

  /**
   * Constructor
   *
   * @param units The number of allocatable units for this free list
   * @param grain Units are allocated such that they will never cross this granularity boundary
   * @param heads The number of free lists which will share this instance
   */
  public GenericFreeList(int units, int grain, int heads) {
    this.parent = null;
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(units <= MAX_UNITS && heads <= MAX_HEADS);
    this.heads = heads;
    head = -1;

    // allocate the data structure, including space for top & bottom sentinels
    table = new int[(units + 1 + heads) << 1];
    initializeHeap(units, grain);
  }

  /**
   * Resize the free list for a parent free list.
   * This must not be called dynamically (ie not after bootstrap).
   *
   * @param units The number of allocatable units for this free list
   * @param grain Units are allocated such that they will never cross this granularity boundary
   */
  @Interruptible
  public void resizeFreeList(int units, int grain) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(parent == null && !Plan.isInitialized());
    table = new int[(units + 1 + heads) << 1];
    initializeHeap(units, grain);
  }

  /**
   * Resize the free list for a child free list.
   * This must not be called dynamically (ie not after bootstrap).
   */
  @Interruptible
  public void resizeFreeList() {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(parent != null && !Plan.isInitialized());
    table = parent.getTable();
  }

  /**
   * Constructor
   *
   * @param parent The parent, owning the data structures this instance will share
   * @param ordinal The ordinal number of this child
   */
  public GenericFreeList(GenericFreeList parent, int ordinal) {
    this.parent = parent;
    this.table = parent.getTable();
    this.heads = parent.getHeads();
    this.head = -(1 + ordinal);
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(-this.head <= this.heads);
  }

  /** Getter */
  int[] getTable() { return table; }
  int getHeads() { return heads; }

  /**
   * Initialize a unit as a sentinel
   *
   * @param unit The unit to be initialized
   */
  protected void setSentinel(int unit) {
    setLoEntry(unit, NEXT_MASK & unit);
    setHiEntry(unit, PREV_MASK & unit);
  }

  /**
   * Get the size of a lump of units
   *
   * @param unit The first unit in the lump of units
   * @return The size of the lump of units
   */
  protected int getSize(int unit) {
    if ((getHiEntry(unit) & MULTI_MASK) == MULTI_MASK)
      return (getHiEntry(unit + 1) & SIZE_MASK);
    else
      return 1;
  }

  /**
   * Set the size of lump of units
   *
   * @param unit The first unit in the lump of units
   * @param size The size of the lump of units
   */
  protected void setSize(int unit, int size) {
    if (size > 1) {
      setHiEntry(unit, getHiEntry(unit) | MULTI_MASK);
      setHiEntry(unit + 1, MULTI_MASK | size);
      setHiEntry(unit + size - 1, MULTI_MASK | size);
    } else
      setHiEntry(unit, getHiEntry(unit) & ~MULTI_MASK);
  }

  /**
   * Establish whether a lump of units is free
   *
   * @param unit The first or last unit in the lump
   * @return True if the lump is free
   */
  protected boolean getFree(int unit) {
    return ((getLoEntry(unit) & FREE_MASK) == FREE_MASK);
  }

  /**
   * Set the "free" flag for a lump of units (both the first and last
   * units in the lump are set.
   *
   * @param unit The first unit in the lump
   * @param isFree True if the lump is to be marked as free
   */
  protected void setFree(int unit, boolean isFree) {
    int size;
    if (isFree) {
      setLoEntry(unit, getLoEntry(unit) | FREE_MASK);
      if ((size = getSize(unit)) > 1)
        setLoEntry(unit + size - 1, getLoEntry(unit + size - 1) | FREE_MASK);
    } else {
      setLoEntry(unit, getLoEntry(unit) & ~FREE_MASK);
      if ((size = getSize(unit)) > 1)
        setLoEntry(unit + size - 1, getLoEntry(unit + size - 1) & ~FREE_MASK);
    }
  }

  /**
   * Get the next lump in the doubly linked free list
   *
   * @param unit The index of the first unit in the current lump
   * @return The index of the first unit of the next lump of units in the list
   */
  protected int getNext(int unit) {
    int next = getHiEntry(unit) & NEXT_MASK;
    return (next <= MAX_UNITS) ? next : head;
  }

  /**
   * Set the next lump in the doubly linked free list
   *
   * @param unit The index of the first unit in the lump to be set
   * @param next The value to be set.
   */
  protected void setNext(int unit, int next) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert((next >= -heads) && (next <= MAX_UNITS));
    int oldValue = getHiEntry(unit);
    int newValue = (oldValue & ~NEXT_MASK) | (next & NEXT_MASK);
    setHiEntry(unit, newValue);
  }

  /**
   * Get the previous lump in the doubly linked free list
   *
   * @param unit The index of the first unit in the current lump
   * @return The index of the first unit of the previous lump of units
   * in the list
   */
  protected int getPrev(int unit) {
    int prev = getLoEntry(unit) & PREV_MASK;
    return (prev <= MAX_UNITS) ? prev : head;
  }

  /**
   * Set the previous lump in the doubly linked free list
   *
   * @param unit The index of the first unit in the lump to be set
   * @param prev The value to be set.
   */
  protected void setPrev(int unit, int prev) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert((prev >= -heads) && (prev <= MAX_UNITS));
    int oldValue = getLoEntry(unit);
    int newValue = (oldValue & ~PREV_MASK) | (prev & PREV_MASK);
    setLoEntry(unit, newValue);
  }

  /**
   * Set the uncoalescable bit associated with this unit.
   * This ensures this unit cannot be coalesced with units below
   * it.
   *
   * @param unit The unit whose uncoalescable bit is to be set
   */
  public void setUncoalescable(int unit) {
    setLoEntry(unit, getLoEntry(unit) | COALESC_MASK);
  }

  /**
   * Clear the uncoalescable bit associated with this unit.
   * This allows this unit to be coalesced with units below
   * it.
   *
   * @param unit The unit whose uncoalescable bit is to be cleared
   */
  public void clearUncoalescable(int unit) {
    setLoEntry(unit, getLoEntry(unit) & ~COALESC_MASK);
  }

  /**
   * Return true if this unit may be coalesced with the unit below it.
   *
   * @param unit The unit in question
   * @return True if this unit may be coalesced with the unit below it.
   */
  @Override
  public boolean isCoalescable(int unit) {
    return (getLoEntry(unit) & COALESC_MASK) == 0;
  }

 /**
   * Get the lump to the "left" of the current lump (i.e. "above" it)
   *
   * @param unit The index of the first unit in the lump in question
   * @return The index of the first unit in the lump to the
   * "left"/"above" the lump in question.
   */
  protected int getLeft(int unit) {
    if ((getHiEntry(unit - 1) & MULTI_MASK) == MULTI_MASK)
      return unit - (getHiEntry(unit - 1) & SIZE_MASK);
    else
      return unit - 1;
  }


  /**
   * Get the contents of an entry
   *
   * @param unit The index of the unit
   * @return The contents of the unit
   */
  private int getLoEntry(int unit) {
    return table[(unit + heads) << 1];
  }
  private int getHiEntry(int unit) {
    return table[((unit + heads) << 1) + 1];
  }

  /**
   * Set the contents of an entry
   *
   * @param unit The index of the unit
   * @param value The contents of the unit
   */
  private void setLoEntry(int unit, int value) {
    table[(unit + heads) << 1] = value;
  }
  private void setHiEntry(int unit, int value) {
    table[((unit + heads) << 1) + 1] = value;
  }

  private static final int TOTAL_BITS = 32;
  private static final int UNIT_BITS = (TOTAL_BITS - 2);
  public static final int MAX_UNITS = (int) (((((long) 1) << UNIT_BITS) - 1) - MAX_HEADS - 1);
  private static final int NEXT_MASK = (int) ((((long) 1) << UNIT_BITS) - 1);
  private static final int PREV_MASK = (int) ((((long) 1) << UNIT_BITS) - 1);
  private static final int FREE_MASK = 1 << (TOTAL_BITS - 1);
  private static final int MULTI_MASK = 1 << (TOTAL_BITS - 1);
  private static final int COALESC_MASK = 1 << (TOTAL_BITS - 2);
  private static final int SIZE_MASK = (int) ((((long) 1) << UNIT_BITS) - 1);

  private int[] table;
  private final GenericFreeList parent;
}
