/* * 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); } }