/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* 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/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.mmtk.utility.deque;
import static 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>) sequential
* store buffer. An SSB is strictly FIFO (although this class does
* not implement dequeuing).<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>. This class provides no mechanism for
* dequeing.<p>
*
* The implementation is intended to be as efficient as possible, in
* time and space, as it is used in critical code such as the GC work
* queue and the write buffer used by many "remembering"
* collectors. Each instance has just two fields: a bump pointer and a
* pointer to the <code>SharedDeque</code><p>
*
* 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
* overflows on inserts and pops (underflow actually) will always arise
* when then cursor is buffer-size aligned.
*/
@Uninterruptible class LocalSSB extends Deque {
/****************************************************************************
*
* Public instance methods
*/
/**
* Constructor
*
* @param queue The shared queue to which this local ssb will append
* its buffers (when full or flushed).
*/
LocalSSB(SharedDeque queue) {
this.queue = queue;
resetLocal();
}
/**
* Flush the buffer and add it to the shared queue (this will
* make any entries in the buffer visible to any consumer associated
* with the shared queue).
*/
public void flushLocal() {
if (tail.NE(Deque.TAIL_INITIAL_VALUE)) {
closeAndEnqueueTail(queue.getArity());
tail = Deque.TAIL_INITIAL_VALUE;
tailBufferEnd = Deque.TAIL_INITIAL_VALUE;
}
}
public void reset() {
resetLocal();
}
/****************************************************************************
*
* Protected instance methods and fields
*/
/** the location in the buffer */
@Entrypoint
protected Address tail;
/** the end of the buffer */
protected Address tailBufferEnd;
/** the shared queue */
protected final SharedDeque queue;
/**
* Reset the local buffer (throwing away any local entries).
*/
public void resetLocal() {
tail = Deque.TAIL_INITIAL_VALUE;
tailBufferEnd = Deque.TAIL_INITIAL_VALUE;
}
/**
* Check whether there is space in the buffer for a pending insert.
* If there is not sufficient space, allocate a new buffer
* (dispatching the full buffer to the shared queue if not null).
*
* @param arity The arity of the values stored in this SSB: the
* buffer must contain enough space for this many words.
*/
@Inline(value = Inline.When.AssertionsDisabled)
protected final void checkTailInsert(int arity) {
if (bufferOffset(tail).isZero())
tailOverflow(arity);
else if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(bufferOffset(tail).sGE(Word.fromIntZeroExtend(arity).lsh(LOG_BYTES_IN_ADDRESS).toOffset()));
}
/**
* Insert a value into the buffer. This is <i>unchecked</i>. The
* caller must first call <code>checkInsert()</code> to ensure the
* buffer can accommodate the insertion.
*
* @param value the value to be inserted.
*/
@Inline(value = Inline.When.AssertionsDisabled)
protected final void uncheckedTailInsert(Address value) {
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(bufferOffset(tail).sGE(Offset.fromIntZeroExtend(BYTES_IN_ADDRESS)));
tail = tail.minus(BYTES_IN_ADDRESS);
tail.store(value);
// if (Interface.VerifyAssertions) enqueued++;
}
/**
* In the case where a buffer must be flushed before being
* filled (either to the queue or to the head), the entries must be
* slid to the base of the buffer in order to preserve the invariant
* that all non-tail buffers will have entries starting at the base
* (which allows a simple test against the base to be used when
* popping entries). This is <i>expensive</i>, so should be
* avoided.
*
* @param arity The arity of the buffer in question
* @return The last slot in the normalized buffer that contains an entry
*/
protected final Address normalizeTail(int arity) {
Address src = tail;
Address tgt = bufferFirst(tail);
Address last = tgt.plus(bufferLastOffset(arity).minus(bufferOffset(tail)));
while (tgt.LE(last)) {
tgt.store(src.loadAddress());
src = src.plus(BYTES_IN_ADDRESS);
tgt = tgt.plus(BYTES_IN_ADDRESS);
}
return last;
}
/**
* Return the sentinel offset for a buffer of a given arity. This is used
* both to compute the address at the end of the buffer.
*
* @param arity The arity of this buffer
* @return The sentinel offset value for a buffer of the given arity.
*/
@Inline
protected final Offset bufferSentinel(int arity) {
return bufferLastOffset(arity).plus(BYTES_IN_ADDRESS);
}
/****************************************************************************
*
* Private instance methods
*/
/**
* Buffer space has been exhausted, allocate a new buffer and enqueue
* the existing buffer (if any).
*
* @param arity The arity of this buffer (used for sanity test only).
*/
private void tailOverflow(int arity) {
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(arity == queue.getArity());
if (tail.NE(Deque.TAIL_INITIAL_VALUE)) {
closeAndEnqueueTail(arity);
}
tail = queue.alloc().plus(bufferSentinel(arity));
tailBufferEnd = tail;
}
/**
* Close the tail buffer (normalizing if necessary), and enqueue it
* at the tail of the shared buffer queue.
*
* @param arity The arity of this buffer.
*/
@NoInline
private void closeAndEnqueueTail(int arity) {
Address last;
if (!bufferOffset(tail).isZero()) {
// prematurely closed
last = normalizeTail(arity);
} else {
// a full tail buffer
last = tailBufferEnd.minus(BYTES_IN_ADDRESS);
}
queue.enqueue(last.plus(BYTES_IN_ADDRESS), arity, true);
}
/**
* Return true if this SSB is locally empty
*
* @return true if this SSB is locally empty
*/
public final boolean isFlushed() {
return tail.EQ(Deque.TAIL_INITIAL_VALUE);
}
}