blob: bd3dfc37646c7b0278d4cc1122478de395c7ed9d [file] [log] [blame]
/*
* 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.policy.RawPageSpace;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.*;
import org.vmmagic.unboxed.*;
/**
* This class implements a simple hashtable. It is intended for use
* in sanity checking or debugging, not high-performance algorithms.<p>
*
* This class is not thread safe.
*/
@Uninterruptible public abstract class SimpleHashtable implements Constants {
/** The number of low order bits to ignore */
private static final int HASH_SHIFT = 3;
/** Offset to the key */
private static final Offset KEY_OFFSET = Offset.zero();
/** Offset to the data */
private static final Offset DATA_OFFSET = Offset.fromIntSignExtend(BYTES_IN_WORD);
/** The size of each entry in the table */
private final Extent entrySize;
/** The mask to use to get the hash code */
private final Word mask;
/** The start address of the data table */
private Address base;
/** The full size of the table */
private final Extent size;
/** The space to use for allocating the data structure */
private final RawPageSpace space;
/** Is this table valid (created) */
private boolean valid;
/**
* Create a new data table of a specified size.
*
* @param rps The space to acquire the data structure from.
* @param logSize The log of the number of table entries.
* @param es The size of each entry.
*/
protected SimpleHashtable(RawPageSpace rps, int logSize, Extent es) {
mask = Word.fromIntZeroExtend((1 << logSize) - 1);
entrySize = es.plus(BYTES_IN_WORD);
size = Extent.fromIntZeroExtend((1 << logSize) * entrySize.toInt());
base = Address.zero();
space = rps;
valid = false;
}
/**
* Create a (zeroed) table.
*/
public final void acquireTable() {
base = space.acquire(Conversions.bytesToPages(size));
VM.memory.zero(base, size);
valid = true;
}
/**
* Drop the table (after collection).
*/
public final void releaseTable() {
space.release(base);
valid = false;
}
/**
* @return True if this table has backing data and is ready for use.
*/
public final boolean isValid() {
return valid;
}
/**
* Retrieve a pointer to the entry for the given object, or zero if one
* does not exist, unless create is passed.<p>
*
* If create is true, the return is guaranteed to be non-null.
*
* @param key The key used to lookup.
* @param create Create a new entry if not found.
* @return A pointer to the reference or null.
*/
@Inline
public final Address getEntry(Word key, boolean create) {
int startIndex = computeHash(key);
int index = startIndex;
Word curAddress;
Address entry;
do {
entry = getEntry(index);
curAddress = entry.loadWord(KEY_OFFSET);
index = (index + 1) & mask.toInt();
} while(curAddress.NE(key) &&
!curAddress.isZero() &&
index != startIndex);
if (index == startIndex) {
VM.assertions.fail("No room left in table!");
}
if (curAddress.isZero()) {
if (!create) return Address.zero();
entry.store(key, KEY_OFFSET);
}
return entry;
}
/**
* Compute the hashtable index for a given object.
*
* @param key The key.
* @return The index.
*/
@Inline
private int computeHash(Word key) {
return key.rshl(HASH_SHIFT).and(mask).toInt();
}
/**
* Return the address of a specified entry in the table.
*
* @param index The index of the entry.
* @return An address to the entry.
*/
@Inline
private Address getEntry(int index) {
return base.plus(Extent.fromIntZeroExtend(index * entrySize.toInt()));
}
/**
* Does the passed object have an entry in the table?
*
* @param key The key to find an entry for
* @return True if there is an entry for that object.
*/
public final boolean contains(Word key) {
return !getEntry(key, false).isZero();
}
/**
* @return The first non-zero element in the table, or null if
* the table is empty.
*/
public final Address getFirst() {
return getNext(base.minus(entrySize));
}
/**
* The next element in the table after the passed entry, or
* null if it is the last entry.
*
* @param curr The object to look for the next entry from.
* @return The next entry or null.
*/
public final Address getNext(Address curr) {
Address entry = curr.plus(entrySize);
while (entry.LT(base.plus(size))) {
if (!entry.loadWord().isZero()) return entry;
entry = entry.plus(entrySize);
}
return Address.zero();
}
/**
* Given an address of an entry, return a pointer to the payload.
*
* @param entry The entry
* @return The object reference.
*/
public static Address getPayloadAddress(Address entry) {
return entry.plus(DATA_OFFSET);
}
/**
* Given a key, return a pointer to the payload.
*
* @param key The key
* @return The object reference.
*/
public final Address getPayloadAddress(Word key) {
Address entry = getEntry(key, false);
if (entry.isZero()) return Address.zero();
return entry.plus(DATA_OFFSET);
}
/**
* Return the key for a given entry.
*
* @param entry The entry.
* @return The key.
*/
public static Word getKey(Address entry) {
return entry.loadWord(KEY_OFFSET);
}
/**
* Update the key for a given entry. This operation is not
* safe without rehashing
*
* @param entry The entry to update.
* @param key The new key.
*/
public static void replaceKey(Address entry, Word key) {
entry.store(key, KEY_OFFSET);
}
}