3 * Hashtable implementation
6 * Miguel de Icaza (miguel@novell.com)
8 * (C) 2006 Novell, Inc.
10 * Permission is hereby granted, free of charge, to any person obtaining
11 * a copy of this software and associated documentation files (the
12 * "Software"), to deal in the Software without restriction, including
13 * without limitation the rights to use, copy, modify, merge, publish,
14 * distribute, sublicense, and/or sell copies of the Software, and to
15 * permit persons to whom the Software is furnished to do so, subject to
16 * the following conditions:
18 * The above copyright notice and this permission notice shall be
19 * included in all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 #include "mono-hash.h"
34 #include "metadata/gc-internals.h"
35 #include <mono/utils/checked-build.h>
36 #include <mono/utils/mono-threads-coop.h>
37 #include <mono/utils/unlocked.h>
39 gint32 mono_g_hash_table_max_chain_length
;
41 struct _MonoGHashTable
{
43 GEqualFunc key_equal_func
;
49 GDestroyNotify value_destroy_func
, key_destroy_func
;
50 MonoGHashGCType gc_type
;
51 MonoGCRootSource source
;
57 mono_g_hash_table_new_type_internal (GHashFunc hash_func
, GEqualFunc key_equal_func
, MonoGHashGCType type
, MonoGCRootSource source
, void *key
, const char *msg
);
65 for (n
= 3; n
< (int)sqrt (x
); n
+= 2) {
71 // There is only one even prime - 2.
80 for (i
= (x
& (~1))-1; i
< G_MAXINT32
; i
+= 2) {
88 #define HASH_TABLE_MAX_LOAD_FACTOR 0.7f
89 /* We didn't really do compaction before, keep it lenient for now */
90 #define HASH_TABLE_MIN_LOAD_FACTOR 0.05f
91 /* We triple the table size at rehash time, similar with previous implementation */
92 #define HASH_TABLE_RESIZE_RATIO 3
94 static inline void mono_g_hash_table_key_store (MonoGHashTable
*hash
, int slot
, MonoObject
* key
)
96 MonoObject
**key_addr
= &hash
->keys
[slot
];
97 if (hash
->gc_type
& MONO_HASH_KEY_GC
)
98 mono_gc_wbarrier_generic_store_internal (key_addr
, key
);
103 static inline void mono_g_hash_table_value_store (MonoGHashTable
*hash
, int slot
, MonoObject
* value
)
105 MonoObject
**value_addr
= &hash
->values
[slot
];
106 if (hash
->gc_type
& MONO_HASH_VALUE_GC
)
107 mono_gc_wbarrier_generic_store_internal (value_addr
, value
);
112 /* Returns position of key or of an empty slot for it */
113 static inline int mono_g_hash_table_find_slot (MonoGHashTable
*hash
, const MonoObject
*key
)
115 guint start
= ((*hash
->hash_func
) (key
)) % hash
->table_size
;
118 if (hash
->key_equal_func
) {
119 GEqualFunc equal
= hash
->key_equal_func
;
121 while (hash
->keys
[i
] && !(*equal
) (hash
->keys
[i
], key
)) {
123 if (i
== hash
->table_size
)
127 while (hash
->keys
[i
] && hash
->keys
[i
] != key
) {
129 if (i
== hash
->table_size
)
134 gint32 max_length
= UnlockedRead (&mono_g_hash_table_max_chain_length
);
135 if (i
> start
&& (i
- start
) > max_length
)
136 UnlockedWrite (&mono_g_hash_table_max_chain_length
, i
- start
);
137 else if (i
< start
&& (hash
->table_size
- (start
- i
)) > max_length
)
138 UnlockedWrite (&mono_g_hash_table_max_chain_length
, hash
->table_size
- (start
- i
));
145 mono_g_hash_table_new_type (GHashFunc hash_func
, GEqualFunc key_equal_func
, MonoGHashGCType type
, MonoGCRootSource source
, void *key
, const char *msg
)
147 MonoGHashTable
*result
;
148 MONO_ENTER_GC_UNSAFE
;
149 result
= mono_g_hash_table_new_type_internal (hash_func
, key_equal_func
, type
, source
, key
, msg
);
156 mono_g_hash_table_new_type_internal (GHashFunc hash_func
, GEqualFunc key_equal_func
, MonoGHashGCType type
, MonoGCRootSource source
, void *key
, const char *msg
)
158 MONO_REQ_GC_UNSAFE_MODE
;
159 MonoGHashTable
*hash
;
162 hash_func
= g_direct_hash
;
164 hash
= g_new0 (MonoGHashTable
, 1);
166 hash
->hash_func
= hash_func
;
167 hash
->key_equal_func
= key_equal_func
;
169 hash
->table_size
= g_spaced_primes_closest (1);
170 hash
->keys
= g_new0 (MonoObject
*, hash
->table_size
);
171 hash
->values
= g_new0 (MonoObject
*, hash
->table_size
);
173 hash
->gc_type
= type
;
174 hash
->source
= source
;
178 if (type
> MONO_HASH_KEY_VALUE_GC
)
179 g_error ("wrong type for gc hashtable");
181 if (hash
->gc_type
& MONO_HASH_KEY_GC
)
182 mono_gc_register_root_wbarrier ((char*)hash
->keys
, sizeof (MonoObject
*) * hash
->table_size
, mono_gc_make_vector_descr (), hash
->source
, hash
->key
, hash
->msg
);
183 if (hash
->gc_type
& MONO_HASH_VALUE_GC
)
184 mono_gc_register_root_wbarrier ((char*)hash
->values
, sizeof (MonoObject
*) * hash
->table_size
, mono_gc_make_vector_descr (), hash
->source
, hash
->key
, hash
->msg
);
190 MonoGHashTable
*hash
;
197 do_rehash (void *_data
)
199 RehashData
*data
= (RehashData
*)_data
;
200 MonoGHashTable
*hash
= data
->hash
;
202 MonoObject
**old_keys
;
203 MonoObject
**old_values
;
205 current_size
= hash
->table_size
;
206 hash
->table_size
= data
->new_size
;
207 old_keys
= hash
->keys
;
208 old_values
= hash
->values
;
209 hash
->keys
= data
->keys
;
210 hash
->values
= data
->values
;
212 for (i
= 0; i
< current_size
; i
++) {
214 int slot
= mono_g_hash_table_find_slot (hash
, old_keys
[i
]);
215 mono_g_hash_table_key_store (hash
, slot
, old_keys
[i
]);
216 mono_g_hash_table_value_store (hash
, slot
, old_values
[i
]);
223 rehash (MonoGHashTable
*hash
)
225 MONO_REQ_GC_UNSAFE_MODE
; //we must run in unsafe mode to make rehash safe
228 void *old_keys
= hash
->keys
;
229 void *old_values
= hash
->values
;
233 * Rehash to a size that can fit the current elements. Rehash relative to in_use
234 * to allow also for compaction.
236 data
.new_size
= g_spaced_primes_closest (hash
->in_use
/ HASH_TABLE_MAX_LOAD_FACTOR
* HASH_TABLE_RESIZE_RATIO
);
237 data
.keys
= g_new0 (MonoObject
*, data
.new_size
);
238 data
.values
= g_new0 (MonoObject
*, data
.new_size
);
240 if (hash
->gc_type
& MONO_HASH_KEY_GC
)
241 mono_gc_register_root_wbarrier ((char*)data
.keys
, sizeof (MonoObject
*) * data
.new_size
, mono_gc_make_vector_descr (), hash
->source
, hash
->key
, hash
->msg
);
242 if (hash
->gc_type
& MONO_HASH_VALUE_GC
)
243 mono_gc_register_root_wbarrier ((char*)data
.values
, sizeof (MonoObject
*) * data
.new_size
, mono_gc_make_vector_descr (), hash
->source
, hash
->key
, hash
->msg
);
245 if (!mono_threads_are_safepoints_enabled ()) {
246 mono_gc_invoke_with_gc_lock (do_rehash
, &data
);
248 /* We cannot be preempted */
252 if (hash
->gc_type
& MONO_HASH_KEY_GC
)
253 mono_gc_deregister_root ((char*)old_keys
);
254 if (hash
->gc_type
& MONO_HASH_VALUE_GC
)
255 mono_gc_deregister_root ((char*)old_values
);
262 * mono_g_hash_table_size:
265 mono_g_hash_table_size (MonoGHashTable
*hash
)
267 g_return_val_if_fail (hash
!= NULL
, 0);
273 * mono_g_hash_table_lookup:
276 mono_g_hash_table_lookup (MonoGHashTable
*hash
, gconstpointer key
)
278 gpointer orig_key
, value
;
280 if (mono_g_hash_table_lookup_extended (hash
, key
, &orig_key
, &value
))
287 * mono_g_hash_table_lookup_extended:
290 mono_g_hash_table_lookup_extended (MonoGHashTable
*hash
, gconstpointer key
, gpointer
*orig_key
, gpointer
*value
)
294 g_return_val_if_fail (hash
!= NULL
, FALSE
);
296 slot
= mono_g_hash_table_find_slot (hash
, (MonoObject
*)key
);
298 if (hash
->keys
[slot
]) {
300 *orig_key
= hash
->keys
[slot
];
302 *value
= hash
->values
[slot
];
310 * mono_g_hash_table_foreach:
313 mono_g_hash_table_foreach (MonoGHashTable
*hash
, GHFunc func
, gpointer user_data
)
317 g_return_if_fail (hash
!= NULL
);
318 g_return_if_fail (func
!= NULL
);
320 for (i
= 0; i
< hash
->table_size
; i
++) {
322 (*func
)(hash
->keys
[i
], hash
->values
[i
], user_data
);
327 mono_g_hash_table_find (MonoGHashTable
*hash
, GHRFunc predicate
, gpointer user_data
)
331 g_return_val_if_fail (hash
!= NULL
, NULL
);
332 g_return_val_if_fail (predicate
!= NULL
, NULL
);
334 for (i
= 0; i
< hash
->table_size
; i
++) {
335 if (hash
->keys
[i
] && (*predicate
)(hash
->keys
[i
], hash
->values
[i
], user_data
))
336 return hash
->values
[i
];
342 * mono_g_hash_table_remove:
345 mono_g_hash_table_remove (MonoGHashTable
*hash
, gconstpointer key
)
347 int slot
, last_clear_slot
;
349 g_return_val_if_fail (hash
!= NULL
, FALSE
);
350 slot
= mono_g_hash_table_find_slot (hash
, (MonoObject
*)key
);
352 if (!hash
->keys
[slot
])
355 if (hash
->key_destroy_func
)
356 (*hash
->key_destroy_func
)(hash
->keys
[slot
]);
357 hash
->keys
[slot
] = NULL
;
358 if (hash
->value_destroy_func
)
359 (*hash
->value_destroy_func
)(hash
->values
[slot
]);
360 hash
->values
[slot
] = NULL
;
364 * When we insert in the hashtable, if the required position is occupied we
365 * consecutively try out following positions. In order to be able to find
366 * if a key exists or not in the array (without traversing the entire hash)
367 * we maintain the constraint that there can be no free slots between two
368 * entries that are hashed to the same position. This means that, at search
369 * time, when we encounter a free slot we can stop looking for collissions.
370 * Similarly, at remove time, we need to shift all following slots to their
371 * normal slot, until we reach an empty slot.
373 last_clear_slot
= slot
;
374 slot
= (slot
+ 1) % hash
->table_size
;
375 while (hash
->keys
[slot
]) {
376 guint hashcode
= ((*hash
->hash_func
)(hash
->keys
[slot
])) % hash
->table_size
;
378 * We try to move the current element to last_clear_slot, but only if
379 * it brings it closer to its normal position (hashcode)
381 if ((last_clear_slot
< slot
&& (hashcode
> slot
|| hashcode
<= last_clear_slot
)) ||
382 (last_clear_slot
> slot
&& (hashcode
> slot
&& hashcode
<= last_clear_slot
))) {
383 mono_g_hash_table_key_store (hash
, last_clear_slot
, hash
->keys
[slot
]);
384 mono_g_hash_table_value_store (hash
, last_clear_slot
, hash
->values
[slot
]);
385 hash
->keys
[slot
] = NULL
;
386 hash
->values
[slot
] = NULL
;
387 last_clear_slot
= slot
;
390 if (slot
== hash
->table_size
)
397 * mono_g_hash_table_foreach_remove:
400 mono_g_hash_table_foreach_remove (MonoGHashTable
*hash
, GHRFunc func
, gpointer user_data
)
405 g_return_val_if_fail (hash
!= NULL
, 0);
406 g_return_val_if_fail (func
!= NULL
, 0);
408 for (i
= 0; i
< hash
->table_size
; i
++) {
409 if (hash
->keys
[i
] && (*func
)(hash
->keys
[i
], hash
->values
[i
], user_data
)) {
410 mono_g_hash_table_remove (hash
, hash
->keys
[i
]);
412 /* Retry current slot in case the removal shifted elements */
416 if (hash
->in_use
< hash
->table_size
* HASH_TABLE_MIN_LOAD_FACTOR
)
422 * mono_g_hash_table_destroy:
425 mono_g_hash_table_destroy (MonoGHashTable
*hash
)
429 g_return_if_fail (hash
!= NULL
);
431 if (hash
->gc_type
& MONO_HASH_KEY_GC
)
432 mono_gc_deregister_root ((char*)hash
->keys
);
433 if (hash
->gc_type
& MONO_HASH_VALUE_GC
)
434 mono_gc_deregister_root ((char*)hash
->values
);
436 for (i
= 0; i
< hash
->table_size
; i
++) {
437 if (hash
->keys
[i
]) {
438 if (hash
->key_destroy_func
)
439 (*hash
->key_destroy_func
)(hash
->keys
[i
]);
440 if (hash
->value_destroy_func
)
441 (*hash
->value_destroy_func
)(hash
->values
[i
]);
445 g_free (hash
->values
);
450 mono_g_hash_table_insert_replace (MonoGHashTable
*hash
, gpointer key
, gpointer value
, gboolean replace
)
452 MONO_REQ_GC_UNSAFE_MODE
;
454 g_return_if_fail (hash
!= NULL
);
456 if (hash
->in_use
> (hash
->table_size
* HASH_TABLE_MAX_LOAD_FACTOR
))
459 slot
= mono_g_hash_table_find_slot (hash
, (MonoObject
*)key
);
461 if (hash
->keys
[slot
]) {
463 if (hash
->key_destroy_func
)
464 (*hash
->key_destroy_func
)(hash
->keys
[slot
]);
465 mono_g_hash_table_key_store (hash
, slot
, (MonoObject
*)key
);
467 if (hash
->value_destroy_func
)
468 (*hash
->value_destroy_func
) (hash
->values
[slot
]);
469 mono_g_hash_table_value_store (hash
, slot
, (MonoObject
*)value
);
471 mono_g_hash_table_key_store (hash
, slot
, (MonoObject
*)key
);
472 mono_g_hash_table_value_store (hash
, slot
, (MonoObject
*)value
);
478 * mono_g_hash_table_insert:
481 mono_g_hash_table_insert (MonoGHashTable
*h
, gpointer k
, gpointer v
)
483 MONO_ENTER_GC_UNSAFE
;
484 mono_g_hash_table_insert_replace (h
, k
, v
, FALSE
);
489 * mono_g_hash_table_replace:
492 mono_g_hash_table_replace(MonoGHashTable
*h
, gpointer k
, gpointer v
)
494 mono_g_hash_table_insert_replace (h
, k
, v
, TRUE
);
498 mono_g_hash_table_print_stats (MonoGHashTable
*hash
)
500 int i
= 0, chain_size
= 0, max_chain_size
= 0;
501 gboolean wrapped_around
= FALSE
;
504 if (hash
->keys
[i
]) {
507 max_chain_size
= MAX(max_chain_size
, chain_size
);
513 if (i
== (hash
->table_size
- 1)) {
514 wrapped_around
= TRUE
;
520 /* Rehash to a size that can fit the current elements */
521 printf ("Size: %d Table Size: %d Max Chain Length: %d\n", hash
->in_use
, hash
->table_size
, max_chain_size
);