/*
* 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 static org.mmtk.utility.Constants.MAX_INT;
import static org.mmtk.utility.Constants.LOG_BITS_IN_INT;
import static org.mmtk.utility.Constants.LOG_BITS_IN_BYTE;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Extent;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Word;
/**
* Implementation of GenericFreeList backed by raw memory, allocated
* on demand direct from the OS (via mmap).
* <p>
* The method growFreeList(int) must be called to grow the list. Where
* the list has a grain smaller than the maximum size of the list, it
* can only grow in increments of whole grains.
* <p>
* Unlike its sibling IntArrayFreeList, it is not possible for a RawMemoryFreeList
* to have multiple sub-lists threaded through the same list.
*/
@Uninterruptible
public final class RawMemoryFreeList extends GenericFreeList {
/*
* Size of a unit.
*
* Assuming that each unit represents a 4096-byte page, the maximum size
* addressable by this map is ~ 2^40 bits. Exceeding this requires increasing
* the page size or changing the interface to the map from into to Word or long.
*/
/** log2 of the number of bits used by a free list entry (two entries per unit) */
private static final int LOG_ENTRY_BITS = LOG_BITS_IN_INT;
/** log2 of the number of bytes used by a free list entry (two entries per unit) */
private static final int LOG_BYTES_IN_ENTRY = LOG_ENTRY_BITS - LOG_BITS_IN_BYTE;
/** log2 of the number of bytes used by a free list unit */
private static final int LOG_BYTES_IN_UNIT = LOG_BYTES_IN_ENTRY + 1;
/** Lowest address occupied by this list */
private final Address base;
/**
* Highest address this list is allowed to occupy. Attempts to grow the
* list beyond this will fail.
*/
private final Address limit;
/**
* Memory addresses between base (inclusive) and highWater (exclusive) are already
* allocated to the map.
*/
private Address highWater;
/** Maximum configured size of list in units */
private final int maxUnits;
/** Maximum configured size of list in units */
private final int grain;
/**
* Current formatted size of list in units
*/
private int currentUnits;
/** The free list allocates VM in blocks of this many pages */
private final int pagesPerBlock;
/** Local version of Math.min(int, int) */
private static int min(int a, int b) {
return a > b ? b : a;
}
/** @return the number of units held in a block (excluding list heads and sentinels) */
protected int unitsPerBlock() {
return Conversions.pagesToBytes(pagesPerBlock).toInt() >> LOG_BYTES_IN_UNIT;
}
/** @return the number of units held in the first block (including list heads and sentinels) */
private int unitsInFirstBlock() {
return unitsPerBlock() - heads - 1;
}
public static int defaultBlockSize(int units, int heads) {
return min(sizeInPages(units, heads), 16);
}
/****************************************************************************
*
* Public static methods
*/
/**
* Calculate the size (in pages) of a map for a given number of units
* and heads
* @param units Number of units in the map
* @param heads Number of independent heads
* @return The size in pages of the map
*/
public static int sizeInPages(int units, int heads) {
Extent mapSize = Word.fromIntZeroExtend(units + heads + 1).lsh(LOG_BYTES_IN_UNIT).toExtent();
return Conversions.bytesToPagesUp(mapSize);
}
/**
* Constructor
*
* @param units The number of allocatable units for this free list
*
* Keeping grain as a unit restricts the maximum space size of a page-per-unit
* allocator to 8TB. Not terribly restrictive in 2016, but ...
*/
public RawMemoryFreeList(Address base, Address limit, int units) {
this(base, limit, units, min(units,MAX_INT));
}
/**
* 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 RawMemoryFreeList(Address base, Address limit, int units, int grain) {
this(base, limit, units, grain, 1);
}
RawMemoryFreeList(Address base, Address limit, int units, int grain, int heads) {
this(base, limit, defaultBlockSize(units,heads), units, grain, heads);
}
/**
* 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
*/
RawMemoryFreeList(Address base, Address limit, int pagesPerBlock, int units, int grain, int heads) {
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(units <= MAX_UNITS && heads <= MAX_HEADS);
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(base.plus(Conversions.pagesToBytes(sizeInPages(units,heads))).LE(limit));
this.maxUnits = units;
this.grain = grain;
this.heads = heads;
head = -1;
this.pagesPerBlock = pagesPerBlock;
this.base = base;
this.limit = limit;
this.highWater = base;
this.currentUnits = 0;
if (VERBOSE) {
dbgPrintSummary();
}
}
/****************************************************************************
*
* Public instance methods
*/
/** {@inheritDoc} */
@Override
@Interruptible
public void resizeFreeList() {
// Nothing to do.
}
@Override
public void resizeFreeList(int units, int heads) {
if (VM.VERIFY_ASSERTIONS) VM.assertions.fail("Unimplemented method");
}
@Override
public int alloc(int size) {
if (currentUnits == 0) {
return FAILURE;
}
return super.alloc(size);
}
@Override
protected int getEntry(int index) {
Offset offset = Offset.fromIntZeroExtend(index << LOG_BYTES_IN_ENTRY);
if (VM.VERIFY_ASSERTIONS) {
if (base.plus(offset).LT(base) || base.plus(offset).GE(highWater)) {
Log.write("getEntry(");
Log.write(index);
Log.write(") : addr=");
Log.write(base.plus(offset));
Log.write(", hwm=");
Log.writeln(highWater);
VM.assertions.fail("RawMemoryFreeList attempted to read an address outside the list");
}
}
return base.loadInt(offset);
}
@Override
protected void setEntry(int index, int value) {
Offset offset = Offset.fromIntZeroExtend(index << LOG_BYTES_IN_ENTRY);
if (VM.VERIFY_ASSERTIONS) {
if (base.plus(offset).LT(base) || base.plus(offset).GE(highWater)) {
Log.write("setEntry(");
Log.write(index);
Log.write(",");
Log.write(value);
Log.write(") : addr=");
Log.write(base.plus(offset));
Log.write(", hwm=");
Log.writeln(highWater);
VM.assertions.fail("RawMemoryFreeList attempted to write an address outside the list");
}
}
base.store(value, offset);
}
/**
* @return The current capacity (units) of the allocated blocks, allowing
* one unit for the bottom sentinel.
*/
private int currentCapacity() {
int listBlocks = Conversions.bytesToPages(highWater.diff(base)) / pagesPerBlock;
return unitsInFirstBlock() + (listBlocks - 1) * unitsPerBlock();
}
/**
* Grow the free list by a specified number of units.
*/
public boolean growFreeList(int units) {
int requiredUnits = units + currentUnits;
if (requiredUnits > maxUnits) {
return false;
}
int blocks = 0;
if (requiredUnits > currentCapacity()) {
int unitsReqd = requiredUnits - currentCapacity();
blocks = (unitsReqd + unitsPerBlock() - 1) / unitsPerBlock();
}
growListByBlocks(blocks, requiredUnits);
return true;
}
/**
* Grow the list and initialize the new portion.
*
* @param blocks Number of map blocks to add to the map (may be zero)
* @param newMax The new capacity of the list in units
*/
private void growListByBlocks(int blocks, int newMax) {
if (VM.VERIFY_ASSERTIONS) {
if ((newMax > grain) && ((newMax / grain) * grain != newMax)) {
Log.write("growListByBlocks: newMax=", newMax);
Log.write(", grain=", grain);
Log.write(", (newMax / grain) * grain=");
Log.writeln((newMax / grain) * grain);
}
VM.assertions._assert((newMax <= grain) || (((newMax / grain) * grain) == newMax));
}
if (VERBOSE) {
dbgPrintSummary();
Log.write("Growing free list by ", blocks);
Log.write(" blocks to ", newMax);
Log.writeln(" units.");
}
if (blocks > 0) {
// Allocate more VM from the OS
raiseHighWater(blocks);
}
int oldMax = currentUnits;
if (newMax > currentCapacity()) {
Log.write("newMax = ", newMax);
Log.writeln(", currentCapacity() = ", currentCapacity());
VM.assertions.fail("blocks and new max are inconsistent: need more blocks for the requested capacity");
}
if (newMax > maxUnits) {
VM.assertions.fail("Requested list to grow larger than the configured maximum");
}
currentUnits = newMax;
if (oldMax == 0) {
// First allocation of capacity: initialize the sentinels.
for (int i = 1; i <= heads; i++) {
setSentinel(-i);
}
} else {
// Turn the old top-of-heap sentinel into a single used block
setSize(oldMax, 1);
}
if (newMax == 0) {
return;
}
// Set a sentinel at the top of the new range
setSentinel(newMax);
int cursor = newMax;
/* A series of grain size regions in the middle */
int grain = min(this.grain, newMax - oldMax);
cursor -= grain;
while (cursor >= oldMax) {
if (VERBOSE) {
Log.write("Adding free block ");
Log.write(cursor);
Log.write("(", grain);
Log.writeln(")");
}
setSize(cursor, grain);
addToFree(cursor);
cursor -= grain;
}
if (VERBOSE) dbgPrintSummary();
if (DEBUG) dbgPrintFree();
}
/**
* Raise the high water mark by requesting more pages from the OS
* @param blocks
*/
private void raiseHighWater(int blocks) {
Extent growExtent = Conversions.pagesToBytes(pagesPerBlock * blocks);
if (highWater.EQ(limit)) {
Log.write("limit=", limit);
Log.write(", highWater=", highWater);
Log.writeln(", growExtent=", growExtent);
VM.assertions.fail("Attempt to grow FreeList beyond limit");
}
if (highWater.plus(growExtent).GT(limit)) {
growExtent = highWater.diff(limit).toWord().toExtent();
}
mmap(highWater, growExtent);
highWater = highWater.plus(growExtent);
}
private void mmap(Address start, Extent bytes) {
int errno = VM.memory.dzmmap(start, bytes.toInt());
if (errno != 0) {
Log.write("mmap failed with errno ", errno);
Log.writeln(" on address ", start);
VM.assertions.fail("Can't get more space with mmap()");
}
}
@Override
public void dbgPrintSummary() {
Log.write("RFL[", base);
Log.write(":", limit);
Log.write(", blksz=", pagesPerBlock);
Log.write(", grain=", grain);
Log.write(", hwm=", highWater);
Log.write(", max=", maxUnits);
Log.write(", cur=", currentUnits);
Log.writeln("]");
}
@Override
public void dbgPrintFree() {
if (currentUnits > 0) {
super.dbgPrintFree();
} else {
Log.writeln("FL[<empty>]FL");
}
}
/**
* Print the entire list (for debugging purposes)
*/
@Override
public void dbgPrintDetail() {
Log.writeln("--vvv-- Free List --vvv--");
if (currentUnits > 0) {
for (int i = -heads; i <= currentUnits; i++) {
dbgPrintEntry(i);
}
}
Log.writeln("--^^^-- Free List --^^^--");
}
/**
* @return The highest virtual address that can be occupied by this
* list.
*/
public Address getLimit() {
return limit;
}
}