/*
* 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;
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 abstract class GenericFreeList {
/****************************************************************************
*
* Public instance methods
*/
/**
* Allocate <code>size</code> units. Return the unit ID
*
* @param size The number of units to be allocated
* @return The index of the first of the <code>size</code>
* contiguous units, or -1 if the request can't be satisfied
*/
public int alloc(int size) {
// Note: -1 is both the default return value *and* the start sentinel index
int unit = head; // HEAD = -1
int s = 0;
while (((unit = getNext(unit)) != head) && ((s = getSize(unit)) < size));
return (unit == head) ? FAILURE : alloc(size, unit, s);
}
/**
* Would an allocation of <code>size</code> units succeed?
*
* @param size The number of units to test for
* @return True if such a request could be satisfied.
*/
public boolean couldAlloc(int size) {
// Note: -1 is both the default return value *and* the start sentinel index
int unit = head; // HEAD = -1
while (((unit = getNext(unit)) != head) && (getSize(unit) < size));
return (unit != head);
}
/**
* Allocate <code>size</code> units. Return the unit ID
*
* @param size The number of units to be allocated
* @param unit First unit to consider
* @return The index of the first of the <code>size</code>
* contiguous units, or -1 if the request can't be satisfied
*/
public final int alloc(int size, int unit) {
int s = 0;
if (getFree(unit) && (s = getSize(unit)) >= size)
return alloc(size, unit, s);
else
return FAILURE;
}
/**
* Allocate <code>size</code> units. Return the unit ID
*
* @param size The number of units to be allocated
* @param unit TODO needs documentation
* @param unitSize TODO needs documentation
* @return The index of the first of the <code>size</code>
* contiguous units, or -1 if the request can't be satisfied
*/
private int alloc(int size, int unit, int unitSize) {
if (unitSize >= size) {
if (unitSize > size)
split(unit, size);
removeFromFree(unit);
setFree(unit, false);
}
if (DEBUG) dbgPrintFree();
return unit;
}
/**
* Free a previously allocated contiguous lump of units.
*
* @param unit The index of the first unit.
* @return return the size of the unit which was freed.
*/
public final int free(int unit) {
return free(unit, false);
}
/**
* Free a previously allocated contiguous lump of units.
*
* @param unit The index of the first unit.
* @param returnCoalescedSize if true, return the coalesced size
* @return The number of units freed. if returnCoalescedSize is
* false, return the size of the unit which was freed. Otherwise
* return the size of the unit now available (the coalesced size)
*/
public final int free(int unit, boolean returnCoalescedSize) {
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!getFree(unit));
int freed = getSize(unit);
int left = getLeft(unit);
int start = isCoalescable(unit) && getFree(left) ? left : unit;
int right = getRight(unit);
int end = isCoalescable(right) && getFree(right) ? right : unit;
if (start != end)
coalesce(start, end);
if (returnCoalescedSize)
freed = getSize(start);
addToFree(start);
if (DEBUG) dbgPrintFree();
return freed;
}
/**
* Return the size of the specified lump of units
*
* @param unit The index of the first unit in the lump.
* @return The size of the lump, in units.
*/
public final int size(int unit) {
return getSize(unit);
}
/****************************************************************************
*
* Private fields and methods
*/
/**
* Initialize a new heap. Fabricate a free list entry containing
* everything
*
* @param units The number of units in the heap
*/
protected final void initializeHeap(int units) {
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(units <= (1L << 32) - 1);
initializeHeap(units, units);
}
/**
* Initialize a new heap. Fabricate a free list entry containing
* everything
*
* @param units The number of units in the heap
* @param grain TODO needs documentation
*/
protected final void initializeHeap(int units, int grain) {
// Initialize the sentinels
for (int i = 1; i <= heads; i++)
setSentinel(-i);
setSentinel(units);
// create the free list item
int offset = units % grain;
int cursor = units - offset;
if (offset > 0) {
setSize(cursor, offset);
addToFree(cursor);
}
cursor -= grain;
while (cursor >= 0) {
setSize(cursor, grain);
addToFree(cursor);
cursor -= grain;
}
if (DEBUG) dbgPrintFree();
}
/**
* Reduce a lump of units to size, freeing any excess.
*
* @param unit The index of the first unit
* @param size The size of the first part
*/
private void split(int unit, int size) {
int basesize = getSize(unit);
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(basesize > size);
setSize(unit, size);
setSize(unit + size, basesize - size);
addToFree(unit + size);
if (DEBUG) dbgPrintFree();
}
/**
* Coalesce two or three contiguous lumps of units, removing start
* and end lumps from the free list as necessary.
* @param start The index of the start of the first lump
* @param end The index of the start of the last lump
*/
private void coalesce(int start, int end) {
if (getFree(end))
removeFromFree(end);
if (getFree(start))
removeFromFree(start);
setSize(start, end - start + getSize(end));
}
/**
* Add a lump of units to the free list
*
* @param unit The first unit in the lump of units to be added
*/
protected void addToFree(int unit) {
setFree(unit, true);
int next = getNext(head);
setNext(unit, next);
setNext(head, unit);
setPrev(unit, head);
setPrev(next, unit);
}
/**
* Remove a lump of units from the free list
*
* @param unit The first unit in the lump of units to be removed
*/
private void removeFromFree(int unit) {
int next = getNext(unit);
int prev = getPrev(unit);
setNext(prev, next);
setPrev(next, prev);
if (DEBUG) dbgPrintFree();
}
/**
* Get the lump to the "right" of the current lump (i.e. "below" 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
* "right"/"below" the lump in question.
*/
private int getRight(int unit) {
return unit + getSize(unit);
}
/**
* Print the free list (for debugging purposes)
*/
public void dbgPrintFree() {
Log.write("FL[");
int i = head;
while ((i = getNext(i)) != head) {
boolean f = getFree(i);
int s = getSize(i);
if (!f)
Log.write("->");
Log.write(i);
if (!f)
Log.write("<-");
Log.write("(");
Log.write(s);
Log.write(")");
Log.write(" ");
Log.flush();
}
Log.writeln("]FL");
}
/**
* Print one entry in the free list (for debugging purposes)
*/
protected void dbgPrintEntry(int i) {
boolean multi = isMulti(i);
boolean free = isFree(i);
boolean coalesc = isCoalescable(i);
int prev = getPrev(i);
int next = getNext(i);
Log.write(i);
Log.write(" : ");
Log.write(multi ? 'M' : ' ');
Log.write(free ? 'F' : 'A');
Log.write(coalesc ? 'C' : ' ');
Log.write(" <", prev);
Log.write("< >", next);
Log.writeln(">");
}
/**
* 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 {@code 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 {@code 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);
}
/**
* 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;
}
/**
* Return true if this unit may be coalesced with the unit below it.
*
* @param unit The unit in question
* @return {@code true} if this unit may be coalesced with the unit below it.
*/
public boolean isCoalescable(int unit) {
return (getLoEntry(unit) & COALESC_MASK) == 0;
}
/**
* Clear the Uncoalescable flag associated with a unit.
* @param unit The unit in question
*/
public void clearUncoalescable(int unit) {
setLoEntry(unit, getLoEntry(unit) & ~COALESC_MASK);
}
/**
* Mark a unit as uncoalescable
* @param unit The unit in question
*/
public void setUncoalescable(int unit) {
setLoEntry(unit, getLoEntry(unit) | COALESC_MASK);
}
@Interruptible
public abstract void resizeFreeList();
@Interruptible
public abstract void resizeFreeList(int units, int heads);
public abstract void dbgPrintDetail();
public abstract void dbgPrintSummary();
protected boolean isMulti(int i) {
int hi = getHiEntry(i);
boolean multi = (hi & MULTI_MASK) == MULTI_MASK;
return multi;
}
protected boolean isFree(int i) {
int lo = getLoEntry(i);
boolean free = (lo & FREE_MASK) == FREE_MASK;
return free;
}
/**
* Fetch the value at the given index into the table.
* @param index Index of the value to fetch. Note this is
* a table index, not a unit number.
* @return Contents of the given index
*/
protected abstract int getEntry(int index);
/**
* Store the given value at an index into the table
* @param index Index of the entry to fetch. Note this is
* a table index, not a unit number.
* @param value The value to store.
*/
protected abstract void setEntry(int index, int value);
/**
* Get the (low) contents of an entry
*
* @param unit The index of the unit
* @return The contents of the unit
*/
protected int getLoEntry(int unit) {
return getEntry((unit + heads) << 1);
}
/**
* Get the (high) contents of an entry
*
* @param unit The index of the unit
* @return The contents of the unit
*/
protected int getHiEntry(int unit) {
return getEntry(((unit + heads) << 1) + 1);
}
/**
* Set the (low) contents of an entry
*
* @param unit The index of the unit
* @param value The contents of the unit
*/
protected void setLoEntry(int unit, int value) {
setEntry((unit + heads) << 1, value);
}
/**
* Set the (high) contents of an entry
*
* @param unit The index of the unit
* @param value The contents of the unit
*/
protected void setHiEntry(int unit, int value) {
setEntry(((unit + heads) << 1) + 1, value);
}
protected static final boolean DEBUG = false;
protected static final boolean VERBOSE = DEBUG || false;
public static final int FAILURE = -1;
protected static final int MAX_HEADS = 128; // somewhat arbitrary
protected static final int TOTAL_BITS = 32;
protected static final int UNIT_BITS = (TOTAL_BITS - 2);
public static final int MAX_UNITS = (int) (((1L << UNIT_BITS) - 1) - MAX_HEADS - 1);
protected static final int NEXT_MASK = (1 << UNIT_BITS) - 1;
protected static final int PREV_MASK = (1 << UNIT_BITS) - 1;
protected static final int FREE_MASK = 1 << (TOTAL_BITS - 1);
protected static final int MULTI_MASK = 1 << (TOTAL_BITS - 1);
protected static final int COALESC_MASK = 1 << (TOTAL_BITS - 2);
protected static final int SIZE_MASK = (1 << UNIT_BITS) - 1;
protected int heads = 1;
protected int head = -1;
}