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

import org.mmtk.utility.Constants;

import org.mmtk.vm.VM;

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

/**
 * This class implements a local (<i>unsynchronized</i>) queue.
 * A queue is strictly FIFO.<p>
 *
 * Each instance stores word-sized values into a local buffer.  When
 * the buffer is full, or if the <code>flushLocal()</code> method is
 * called, the buffer enqueued at the tail of a
 * <code>SharedDeque</code>.
 *
 * The implementation is intended to be as efficient as possible, in
 * time and space, and is the basis for the <code>TraceBuffer</code> used by
 * heap trace generation. Each instance adds a single field to those inherited
 * from the SSB: a bump pointer.
 *
 * Preconditions: Buffers are always aligned on buffer-size address
 * boundaries.<p>
 *
 * Invariants: Buffers are filled such that tuples (of the specified
 * arity) are packed to the low end of the buffer.  Thus buffer
 * underflows will always arise when then cursor is buffer-size aligned.
 */
@Uninterruptible class LocalQueue extends LocalSSB implements Constants {

  /**
   * Constructor
   *
   * @param queue The shared queue to which this local ssb will append
   * its buffers (when full or flushed).
   */
  LocalQueue(SharedDeque queue) {
    super(queue);
  }

 /****************************************************************************
   *
   * Protected instance methods and fields
   */
  @Entrypoint
  protected Address head; // the start of the buffer

  /**
   * Reset the local buffer (throwing away any local entries).
   */
  public void resetLocal() {
    super.resetLocal();
    head = Deque.HEAD_INITIAL_VALUE;
  }

  /**
   * Check whether there are values in the buffer for a pending dequeue.
   * If there is not data, grab the first buffer on the shared queue
   * (after freeing the buffer).
   *
   * @param arity The arity of the values stored in this queue: the
   * buffer must contain enough space for this many words.
   */
  @Inline
  protected final boolean checkDequeue(int arity) {
    if (bufferOffset(head).isZero()) {
      return dequeueUnderflow(arity);
    } else {
      if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(bufferOffset(head).sGE(Word.fromIntZeroExtend(arity).lsh(LOG_BYTES_IN_ADDRESS).toOffset()));
      return true;
    }
  }

  /**
   * Dequeue a value from the buffer.  This is <i>unchecked</i>.  The
   * caller must first call <code>checkDequeue()</code> to ensure the
   * buffer has and entry to be removed.
   *
   * @return The first entry on the queue.
   */
  @Inline
  protected final Address uncheckedDequeue(){
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(bufferOffset(head).sGE(Offset.fromIntZeroExtend(BYTES_IN_ADDRESS)));
    head = head.minus(BYTES_IN_ADDRESS);
    return head.loadAddress();
  }

  /**
   * The head is empty (or null), and the shared queue has no buffers
   * available.  If the tail has sufficient entries, consume the tail.
   * Otherwise try wait on the global queue until either all other
   * clients of the queue reach exhaustion or a buffer becomes
   * available.
   *
   * @param arity The arity of this buffer
   * @return True if the consumer has eaten all the entries
   */
  protected final boolean headStarved(int arity) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(arity == queue.getArity());

    // If the tail has entries...
    if (tail.NE(tailBufferEnd)) {
      head = normalizeTail(arity).plus(BYTES_IN_ADDRESS);
      tail = Deque.TAIL_INITIAL_VALUE;
      tailBufferEnd = Deque.TAIL_INITIAL_VALUE;
      // Return that we acquired more entries
      return false;
    }
    // Wait for another entry to materialize...
    head = queue.dequeueAndWait(arity);
    // return true if a) there is a head buffer, and b) it is non-empty
    return (head.EQ(Deque.HEAD_INITIAL_VALUE) || bufferOffset(head).isZero());
  }

  /****************************************************************************
   *
   * Private instance methods
   */

  /**
   * There are not sufficient entries in the head buffer for a pending
   * dequeue.  Acquire a new head buffer.  If the shared queue has no
   * buffers available, consume the tail if necessary.  Return false
   * if entries cannot be acquired.
   *
   * @param arity The arity of this buffer (used for sanity test only).
   * @return True if there the head buffer has been successfully
   * replenished.
   */
  @NoInline
  private boolean dequeueUnderflow(int arity) {
    if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(arity == queue.getArity());
    do {
      if (head.NE(Deque.HEAD_INITIAL_VALUE))
        queue.free(head);
      head = queue.dequeue(arity);
    } while (head.NE(Deque.HEAD_INITIAL_VALUE) && bufferOffset(head).isZero());

    if (head.EQ(Deque.HEAD_INITIAL_VALUE))
      return !headStarved(arity);

    return true;
  }
}
