/** * \file * A hash table which only stores values in the hash nodes. * * Author: * Miguel de Icaza (miguel@novell.com) * Zoltan Varga (vargaz@gmail.com) * * (C) 2006,2008 Novell, Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include "mono-value-hash.h" #ifndef G_MAXINT32 #define G_MAXINT32 2147483647 #endif /* * This code is based on eglib/ghashtable.c with work done by Hans Petter Jansson * (hpj@novell.com) to make it use internal probing instead of chaining. */ #define HASH_TABLE_MIN_SHIFT 3 /* 1 << 3 == 8 buckets */ typedef struct _Slot Slot; #define GET_VALUE(slot) ((gpointer)((((gsize)((slot)->value)) >> 2) << 2)) #define SET_VALUE(slot,value) ((slot)->value = (value)) #define IS_EMPTY(slot) ((gsize)((slot)->value) == 0) #define IS_TOMBSTONE(slot) ((gsize)((slot)->value) & 1) #define MAKE_TOMBSTONE(slot) do { (slot)->value = (gpointer)((gsize)((slot)->value) | 1); } while (1) #define HASH(table, key) ((table)->hash_func ((key))) struct _Slot { /* A NULL value means the slot is empty */ /* The tombstone status is stored in the lowest order bit of the value. */ gpointer value; }; static gpointer KEYMARKER_REMOVED = &KEYMARKER_REMOVED; struct _MonoValueHashTable { GHashFunc hash_func; GEqualFunc key_equal_func; MonoValueHashKeyExtractFunc key_extract_func; Slot *table; int table_size; int table_mask; int in_use; int n_occupied; GDestroyNotify value_destroy_func, key_destroy_func; }; static void mono_value_hash_table_set_shift (MonoValueHashTable *hash_table, gint shift) { gint i; guint mask = 0; hash_table->table_size = 1 << shift; for (i = 0; i < shift; i++) { mask <<= 1; mask |= 1; } hash_table->table_mask = mask; } static gint mono_value_hash_table_find_closest_shift (gint n) { gint i; for (i = 0; n; i++) n >>= 1; return i; } static void mono_value_hash_table_set_shift_from_size (MonoValueHashTable *hash_table, gint size) { gint shift; shift = mono_value_hash_table_find_closest_shift (size); shift = MAX (shift, HASH_TABLE_MIN_SHIFT); mono_value_hash_table_set_shift (hash_table, shift); } MonoValueHashTable * mono_value_hash_table_new (GHashFunc hash_func, GEqualFunc key_equal_func, MonoValueHashKeyExtractFunc key_extract) { MonoValueHashTable *hash; if (hash_func == NULL) hash_func = g_direct_hash; if (key_equal_func == NULL) key_equal_func = g_direct_equal; hash = g_new0 (MonoValueHashTable, 1); hash->hash_func = hash_func; hash->key_equal_func = key_equal_func; hash->key_extract_func = key_extract; mono_value_hash_table_set_shift (hash, HASH_TABLE_MIN_SHIFT); hash->table = g_new0 (Slot, hash->table_size); return hash; } #if 0 MonoValueHashTable * mono_value_hash_table_new_full (GHashFunc hash_func, GEqualFunc key_equal_func, GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func) { MonoValueHashTable *hash = mono_value_hash_table_new (hash_func, key_equal_func); if (hash == NULL) return NULL; hash->key_destroy_func = key_destroy_func; hash->value_destroy_func = value_destroy_func; return hash; } #endif static void do_rehash (MonoValueHashTable *hash) { int i; int old_size; Slot *old_table; old_size = hash->table_size; old_table = hash->table; mono_value_hash_table_set_shift_from_size (hash, hash->in_use * 2); /* printf ("New size: %d\n", hash->table_size); */ hash->table = g_new0 (Slot, hash->table_size); for (i = 0; i < old_size; i++){ Slot *s = &old_table [i]; Slot *new_s; guint hash_val; guint step = 0; gpointer s_value, s_key; if (IS_EMPTY (s) || IS_TOMBSTONE (s)) continue; s_value = GET_VALUE (s); s_key = hash->key_extract_func (s_value); hash_val = HASH (hash, s_key) & hash->table_mask; new_s = &hash->table [hash_val]; while (!IS_EMPTY (new_s)) { step++; hash_val += step; hash_val &= hash->table_mask; new_s = &hash->table [hash_val]; } *new_s = *s; } g_free (old_table); hash->n_occupied = hash->in_use; } static void rehash (MonoValueHashTable *hash) { int n_occupied = hash->n_occupied; int table_size = hash->table_size; if ((table_size > hash->in_use * 4 && table_size > 1 << HASH_TABLE_MIN_SHIFT) || (table_size <= n_occupied + (n_occupied / 16))) do_rehash (hash); } static void mono_value_hash_table_insert_replace (MonoValueHashTable *hash, gpointer key, gpointer value, gboolean replace) { guint hashcode; Slot *s; guint s_index; GEqualFunc equal; guint first_tombstone = 0; gboolean have_tombstone = FALSE; guint step = 0; g_assert (value); g_assert (hash->key_extract_func (value) == key); g_return_if_fail (hash != NULL); hashcode = HASH (hash, key); s_index = hashcode & hash->table_mask; s = &hash->table [s_index]; equal = hash->key_equal_func; while (!IS_EMPTY (s)) { gpointer s_value = GET_VALUE (s); gpointer s_key = hash->key_extract_func (s_value); guint s_key_hash = HASH (hash, s_key); if (s_key_hash == hashcode && (*equal) (s_key, key)) { if (replace){ if (hash->key_destroy_func != NULL) (*hash->key_destroy_func)(s_key); } if (hash->value_destroy_func != NULL) (*hash->value_destroy_func) (GET_VALUE (s)); SET_VALUE (s, value); return; } else if (IS_TOMBSTONE (s) && !have_tombstone) { first_tombstone = s_index; have_tombstone = TRUE; } step++; s_index += step; s_index &= hash->table_mask; s = &hash->table [s_index]; } if (have_tombstone) { s = &hash->table [first_tombstone]; } else { hash->n_occupied++; } SET_VALUE (s, value); hash->in_use++; rehash (hash); } void mono_value_hash_table_insert (MonoValueHashTable *hash, gpointer key, gpointer value) { mono_value_hash_table_insert_replace (hash, key, value, TRUE); } static Slot * lookup_internal (MonoValueHashTable *hash, gconstpointer key) { GEqualFunc equal; Slot *s; guint hashcode; guint s_index; guint step = 0; hashcode = HASH (hash, key); s_index = hashcode & hash->table_mask; s = &hash->table [s_index]; equal = hash->key_equal_func; while (!IS_EMPTY (s)) { gpointer s_value = GET_VALUE (s); gpointer s_key = hash->key_extract_func (s_value); guint s_key_hash = HASH (hash, s_key); if (s_key_hash == hashcode && (*equal) (hash->key_extract_func (s_value), key)) return s; step++; s_index += step; s_index &= hash->table_mask; s = &hash->table [s_index]; } return NULL; } gpointer mono_value_hash_table_lookup (MonoValueHashTable *hash, gconstpointer key) { Slot *slot = lookup_internal (hash, key); if (slot) return GET_VALUE (slot); else return NULL; } void mono_value_hash_table_destroy (MonoValueHashTable *hash) { int i; g_return_if_fail (hash != NULL); for (i = 0; i < hash->table_size; i++){ Slot *s = &hash->table [i]; if (!IS_EMPTY (s) && !IS_TOMBSTONE (s)) { if (hash->key_destroy_func != NULL) (*hash->key_destroy_func)(hash->key_extract_func (GET_VALUE (s))); if (hash->value_destroy_func != NULL) (*hash->value_destroy_func)(GET_VALUE (s)); } } g_free (hash->table); g_free (hash); }