3 * Conc GC aware Hashtable implementation
6 * Rodrigo Kumpera (kumpera@gmail.com)
13 #include "mono-conc-hash.h"
14 #include "metadata/gc-internals.h"
15 #include <mono/utils/checked-build.h>
16 #include <mono/utils/mono-threads-coop.h>
19 #define mg_new0(type,n) ((type *) GC_MALLOC(sizeof(type) * (n)))
20 #define mg_new(type,n) ((type *) GC_MALLOC(sizeof(type) * (n)))
21 #define mg_free(x) do { } while (0)
23 #define mg_new0(x,n) g_new0(x,n)
24 #define mg_new(type,n) g_new(type,n)
25 #define mg_free(x) g_free(x)
28 #define INITIAL_SIZE 32
29 #define LOAD_FACTOR 0.75f
30 #define PTR_TOMBSTONE ((gpointer)(ssize_t)-1)
31 /* Expand ration must be a power of two */
32 #define EXPAND_RATIO 2
36 MonoGHashGCType gc_type;
41 struct _MonoConcGHashTable {
42 volatile conc_table *table; /* goes to HP0 */
44 GEqualFunc equal_func;
47 GDestroyNotify key_destroy_func;
48 GDestroyNotify value_destroy_func;
49 MonoGHashGCType gc_type;
50 MonoGCRootSource source;
56 conc_table_new (MonoConcGHashTable *hash, int size)
59 conc_table *table = mg_new0 (conc_table, 1);
61 conc_table *table = mono_gc_alloc_fixed (sizeof (conc_table), MONO_GC_ROOT_DESCR_FOR_FIXED (sizeof (conc_table)), hash->source, hash->msg);
64 table->keys = mg_new0 (void*, size);
65 table->values = mg_new0 (void*, size);
66 table->table_size = size;
67 table->gc_type = hash->gc_type;
70 if (hash->gc_type & MONO_HASH_KEY_GC)
71 mono_gc_register_root_wbarrier ((char*)table->keys, sizeof (MonoObject*) * size, mono_gc_make_vector_descr (), hash->source, hash->msg);
72 if (hash->gc_type & MONO_HASH_VALUE_GC)
73 mono_gc_register_root_wbarrier ((char*)table->values, sizeof (MonoObject*) * size, mono_gc_make_vector_descr (), hash->source, hash->msg);
80 conc_table_free (gpointer ptr)
82 conc_table *table = (conc_table *)ptr;
84 if (table->gc_type & MONO_HASH_KEY_GC)
85 mono_gc_deregister_root ((char*)table->keys);
86 if (table->gc_type & MONO_HASH_VALUE_GC)
87 mono_gc_deregister_root ((char*)table->values);
90 mg_free (table->keys);
91 mg_free (table->values);
95 mono_gc_free_fixed (table);
100 conc_table_lf_free (conc_table *table)
102 mono_thread_hazardous_try_free (table, conc_table_free);
107 key_is_tombstone (MonoConcGHashTable *hash, gpointer ptr)
109 if (hash->gc_type & MONO_HASH_KEY_GC)
110 return ptr == mono_domain_get()->ephemeron_tombstone;
111 return ptr == PTR_TOMBSTONE;
115 A common problem with power of two hashtables is that it leads of bad clustering when dealing
116 with aligned numbers.
118 The solution here is to mix the bits from two primes plus the hash itself, it produces a better spread
119 than just the numbers.
122 static MONO_ALWAYS_INLINE int
125 return ((hash * 215497) >> 16) ^ (hash * 1823231 + hash);
130 set_key (conc_table *table, int slot, gpointer key)
132 gpointer *key_addr = &table->keys [slot];
133 if (table->gc_type & MONO_HASH_KEY_GC)
134 mono_gc_wbarrier_generic_store (key_addr, key);
140 set_key_to_tombstone (conc_table *table, int slot)
142 gpointer *key_addr = &table->keys [slot];
143 if (table->gc_type & MONO_HASH_KEY_GC)
144 mono_gc_wbarrier_generic_store (key_addr, mono_domain_get()->ephemeron_tombstone);
146 *key_addr = PTR_TOMBSTONE;
150 set_value (conc_table *table, int slot, gpointer value)
152 gpointer *value_addr = &table->values [slot];
153 if (table->gc_type & MONO_HASH_VALUE_GC)
154 mono_gc_wbarrier_generic_store (value_addr, value);
159 static MONO_ALWAYS_INLINE void
160 insert_one_local (conc_table *table, GHashFunc hash_func, gpointer key, gpointer value)
162 int table_mask = table->table_size - 1;
163 int hash = mix_hash (hash_func (key));
164 int i = hash & table_mask;
166 while (table->keys [i])
167 i = (i + 1) & table_mask;
169 set_key (table, i, key);
170 set_value (table, i, value);
174 expand_table (MonoConcGHashTable *hash_table)
176 conc_table *old_table = (conc_table*)hash_table->table;
177 conc_table *new_table = conc_table_new (hash_table, old_table->table_size * EXPAND_RATIO);
180 for (i = 0; i < old_table->table_size; ++i) {
181 if (old_table->keys [i] && !key_is_tombstone (hash_table, old_table->keys [i]))
182 insert_one_local (new_table, hash_table->hash_func, old_table->keys [i], old_table->values [i]);
185 mono_memory_barrier ();
186 hash_table->table = new_table;
187 hash_table->overflow_count = (int)(new_table->table_size * LOAD_FACTOR);
188 conc_table_lf_free (old_table);
193 mono_conc_g_hash_table_new_type (GHashFunc hash_func, GEqualFunc key_equal_func, MonoGHashGCType type, MonoGCRootSource source, const char *msg)
195 MonoConcGHashTable *hash;
198 hash_func = g_direct_hash;
200 hash = g_new0 (MonoConcGHashTable, 1);
201 hash->hash_func = hash_func;
202 hash->equal_func = key_equal_func;
204 hash->element_count = 0;
205 hash->overflow_count = (int)(INITIAL_SIZE * LOAD_FACTOR);
206 hash->gc_type = type;
207 hash->source = source;
210 hash->table = conc_table_new (hash, INITIAL_SIZE);
212 if (type > MONO_HASH_KEY_VALUE_GC)
213 g_error ("wrong type for gc hashtable");
219 mono_conc_g_hash_table_lookup (MonoConcGHashTable *hash, gconstpointer key)
221 gpointer orig_key, value;
223 if (mono_conc_g_hash_table_lookup_extended (hash, key, &orig_key, &value))
230 mono_conc_g_hash_table_lookup_extended (MonoConcGHashTable *hash_table, gconstpointer key, gpointer *orig_key_ptr, gpointer *value_ptr)
232 MonoThreadHazardPointers* hp;
234 int hash, i, table_mask;
235 hash = mix_hash (hash_table->hash_func (key));
236 hp = mono_hazard_pointer_get ();
239 table = (conc_table *)mono_get_hazardous_pointer ((gpointer volatile*)&hash_table->table, hp, 0);
240 table_mask = table->table_size - 1;
241 i = hash & table_mask;
243 if (G_LIKELY (!hash_table->equal_func)) {
244 while (table->keys [i]) {
245 gpointer orig_key = table->keys [i];
246 if (key == orig_key) {
248 /* The read of keys must happen before the read of values */
249 mono_memory_barrier ();
250 value = table->values [i];
252 /* We just read a value been deleted, try again. */
253 if (G_UNLIKELY (!value))
256 mono_hazard_pointer_clear (hp, 0);
258 *orig_key_ptr = orig_key;
262 i = (i + 1) & table_mask;
265 GEqualFunc equal = hash_table->equal_func;
267 while (table->keys [i]) {
268 gpointer orig_key = table->keys [i];
269 if (!key_is_tombstone (hash_table, orig_key) && equal (key, orig_key)) {
271 /* The read of keys must happen before the read of values */
272 mono_memory_barrier ();
273 value = table->values [i];
275 /* We just read a value been deleted, try again. */
276 if (G_UNLIKELY (!value))
279 mono_hazard_pointer_clear (hp, 0);
280 *orig_key_ptr = orig_key;
285 i = (i + 1) & table_mask;
289 /* The table might have expanded and the value is now on the newer table */
290 mono_memory_barrier ();
291 if (hash_table->table != table)
294 mono_hazard_pointer_clear (hp, 0);
296 *orig_key_ptr = NULL;
302 mono_conc_g_hash_table_foreach (MonoConcGHashTable *hash_table, GHFunc func, gpointer user_data)
305 conc_table *table = (conc_table*)hash_table->table;
307 for (i = 0; i < table->table_size; ++i) {
308 if (table->keys [i] && !key_is_tombstone (hash_table, table->keys [i])) {
309 func (table->keys [i], table->values [i], user_data);
315 mono_conc_g_hash_table_destroy (MonoConcGHashTable *hash_table)
317 if (hash_table->key_destroy_func || hash_table->value_destroy_func) {
319 conc_table *table = (conc_table*)hash_table->table;
321 for (i = 0; i < table->table_size; ++i) {
322 if (table->keys [i] && !key_is_tombstone (hash_table, table->keys [i])) {
323 if (hash_table->key_destroy_func)
324 (hash_table->key_destroy_func) (table->keys [i]);
325 if (hash_table->value_destroy_func)
326 (hash_table->value_destroy_func) (table->values [i]);
330 conc_table_free ((gpointer)hash_table->table);
334 /* Return NULL on success or the old value in failure */
336 mono_conc_g_hash_table_insert (MonoConcGHashTable *hash_table, gpointer key, gpointer value)
339 int hash, i, table_mask;
341 g_assert (key != NULL);
342 g_assert (value != NULL);
344 hash = mix_hash (hash_table->hash_func (key));
346 if (hash_table->element_count >= hash_table->overflow_count)
347 expand_table (hash_table);
349 table = (conc_table*)hash_table->table;
350 table_mask = table->table_size - 1;
351 i = hash & table_mask;
353 if (!hash_table->equal_func) {
355 gpointer cur_key = table->keys [i];
356 if (!cur_key || key_is_tombstone (hash_table, cur_key)) {
357 set_value (table, i, value);
359 /* The write to values must happen after the write to keys */
360 mono_memory_barrier ();
361 set_key (table, i, key);
362 ++hash_table->element_count;
365 if (key == cur_key) {
366 gpointer value = table->values [i];
369 i = (i + 1) & table_mask;
372 GEqualFunc equal = hash_table->equal_func;
374 gpointer cur_key = table->keys [i];
375 if (!cur_key || key_is_tombstone (hash_table, cur_key)) {
376 set_value (table, i, value);
377 /* The write to values must happen after the write to keys */
378 mono_memory_barrier ();
379 set_key (table, i, key);
380 ++hash_table->element_count;
383 if (equal (key, cur_key)) {
384 gpointer value = table->values [i];
387 i = (i + 1) & table_mask;
393 mono_conc_g_hash_table_remove (MonoConcGHashTable *hash_table, gconstpointer key)
396 int hash, i, table_mask;
398 g_assert (key != NULL);
400 hash = mix_hash (hash_table->hash_func (key));
402 table = (conc_table*)hash_table->table;
403 table_mask = table->table_size - 1;
404 i = hash & table_mask;
406 if (!hash_table->equal_func) {
408 gpointer cur_key = table->keys [i];
410 return NULL; /*key not found*/
413 if (key == cur_key) {
414 gpointer value = table->values [i];
415 table->values [i] = NULL;
416 mono_memory_barrier ();
417 set_key_to_tombstone (table, i);
419 --hash_table->element_count;
421 if (hash_table->key_destroy_func != NULL)
422 (*hash_table->key_destroy_func) (cur_key);
423 if (hash_table->value_destroy_func != NULL)
424 (*hash_table->value_destroy_func) (value);
428 i = (i + 1) & table_mask;
431 GEqualFunc equal = hash_table->equal_func;
433 gpointer cur_key = table->keys [i];
435 return NULL; /*key not found*/
438 if (!key_is_tombstone (hash_table, cur_key) && equal (key, cur_key)) {
439 gpointer value = table->values [i];
440 table->values [i] = NULL;
441 mono_memory_barrier ();
442 set_key_to_tombstone (table, i);
444 if (hash_table->key_destroy_func != NULL)
445 (*hash_table->key_destroy_func) (cur_key);
446 if (hash_table->value_destroy_func != NULL)
447 (*hash_table->value_destroy_func) (value);
451 i = (i + 1) & table_mask;