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>
18 #define INITIAL_SIZE 32
19 #define LOAD_FACTOR 0.75f
20 #define PTR_TOMBSTONE ((gpointer)(ssize_t)-1)
21 /* Expand ration must be a power of two */
22 #define EXPAND_RATIO 2
26 MonoGHashGCType gc_type
;
31 struct _MonoConcGHashTable
{
32 volatile conc_table
*table
; /* goes to HP0 */
34 GEqualFunc equal_func
;
35 int element_count
; //KVP + tombstones
36 int tombstone_count
; //just tombstones
38 GDestroyNotify key_destroy_func
;
39 GDestroyNotify value_destroy_func
;
40 MonoGHashGCType gc_type
;
41 MonoGCRootSource source
;
48 conc_table_new (MonoConcGHashTable
*hash
, int size
)
50 conc_table
*table
= g_new0 (conc_table
, 1);
52 table
->keys
= g_new0 (void*, size
);
53 table
->values
= g_new0 (void*, size
);
54 table
->table_size
= size
;
55 table
->gc_type
= hash
->gc_type
;
57 if (hash
->gc_type
& MONO_HASH_KEY_GC
)
58 mono_gc_register_root_wbarrier ((char*)table
->keys
, sizeof (MonoObject
*) * size
, mono_gc_make_vector_descr (), hash
->source
, hash
->key
, hash
->msg
);
59 if (hash
->gc_type
& MONO_HASH_VALUE_GC
)
60 mono_gc_register_root_wbarrier ((char*)table
->values
, sizeof (MonoObject
*) * size
, mono_gc_make_vector_descr (), hash
->source
, hash
->key
, hash
->msg
);
66 conc_table_free (gpointer ptr
)
68 MONO_REQ_GC_UNSAFE_MODE
;
70 conc_table
*table
= (conc_table
*)ptr
;
71 if (table
->gc_type
& MONO_HASH_KEY_GC
)
72 mono_gc_deregister_root ((char*)table
->keys
);
73 if (table
->gc_type
& MONO_HASH_VALUE_GC
)
74 mono_gc_deregister_root ((char*)table
->values
);
77 g_free (table
->values
);
82 conc_table_lf_free (conc_table
*table
)
84 mono_thread_hazardous_try_free (table
, conc_table_free
);
89 key_is_tombstone (MonoConcGHashTable
*hash
, gpointer ptr
)
91 if (hash
->gc_type
& MONO_HASH_KEY_GC
)
92 return ptr
== mono_domain_get()->ephemeron_tombstone
;
93 return ptr
== PTR_TOMBSTONE
;
97 A common problem with power of two hashtables is that it leads of bad clustering when dealing
100 The solution here is to mix the bits from two primes plus the hash itself, it produces a better spread
101 than just the numbers.
104 static MONO_ALWAYS_INLINE
int
107 return ((hash
* 215497) >> 16) ^ (hash
* 1823231 + hash
);
112 set_key (conc_table
*table
, int slot
, gpointer key
)
114 gpointer
*key_addr
= &table
->keys
[slot
];
115 if (table
->gc_type
& MONO_HASH_KEY_GC
)
116 mono_gc_wbarrier_generic_store_internal (key_addr
, (MonoObject
*)key
);
122 set_key_to_tombstone (conc_table
*table
, int slot
)
124 gpointer
*key_addr
= &table
->keys
[slot
];
125 if (table
->gc_type
& MONO_HASH_KEY_GC
)
126 mono_gc_wbarrier_generic_store_internal (key_addr
, mono_domain_get()->ephemeron_tombstone
);
128 *key_addr
= PTR_TOMBSTONE
;
132 set_value (conc_table
*table
, int slot
, gpointer value
)
134 gpointer
*value_addr
= &table
->values
[slot
];
135 if (table
->gc_type
& MONO_HASH_VALUE_GC
)
136 mono_gc_wbarrier_generic_store_internal (value_addr
, (MonoObject
*)value
);
141 static MONO_ALWAYS_INLINE
void
142 insert_one_local (conc_table
*table
, GHashFunc hash_func
, gpointer key
, gpointer value
)
144 int table_mask
= table
->table_size
- 1;
145 int hash
= mix_hash (hash_func (key
));
146 int i
= hash
& table_mask
;
148 while (table
->keys
[i
])
149 i
= (i
+ 1) & table_mask
;
151 set_key (table
, i
, key
);
152 set_value (table
, i
, value
);
156 rehash_table (MonoConcGHashTable
*hash_table
, int multiplier
)
158 conc_table
*old_table
= (conc_table
*)hash_table
->table
;
159 conc_table
*new_table
= conc_table_new (hash_table
, old_table
->table_size
* multiplier
);
162 for (i
= 0; i
< old_table
->table_size
; ++i
) {
163 if (old_table
->keys
[i
] && !key_is_tombstone (hash_table
, old_table
->keys
[i
]))
164 insert_one_local (new_table
, hash_table
->hash_func
, old_table
->keys
[i
], old_table
->values
[i
]);
167 mono_memory_barrier ();
168 hash_table
->table
= new_table
;
169 hash_table
->overflow_count
= (int)(new_table
->table_size
* LOAD_FACTOR
);
170 hash_table
->element_count
-= hash_table
->tombstone_count
;
171 hash_table
->tombstone_count
= 0;
172 conc_table_lf_free (old_table
);
177 check_table_size (MonoConcGHashTable
*hash_table
)
179 if (hash_table
->element_count
>= hash_table
->overflow_count
) {
180 //if we have more tombstones than KVP we rehash to the same size
181 if (hash_table
->tombstone_count
> hash_table
->element_count
/ 2)
182 rehash_table (hash_table
, 1);
184 rehash_table (hash_table
, EXPAND_RATIO
);
189 mono_conc_g_hash_table_new_type (GHashFunc hash_func
, GEqualFunc key_equal_func
, MonoGHashGCType type
, MonoGCRootSource source
, void *key
, const char *msg
)
191 MonoConcGHashTable
*hash
;
194 hash_func
= g_direct_hash
;
196 hash
= g_new0 (MonoConcGHashTable
, 1);
197 hash
->hash_func
= hash_func
;
198 hash
->equal_func
= key_equal_func
;
200 hash
->element_count
= 0;
201 hash
->overflow_count
= (int)(INITIAL_SIZE
* LOAD_FACTOR
);
202 hash
->gc_type
= type
;
203 hash
->source
= source
;
207 hash
->table
= conc_table_new (hash
, INITIAL_SIZE
);
209 if (type
> MONO_HASH_KEY_VALUE_GC
)
210 g_error ("wrong type for gc hashtable");
216 mono_conc_g_hash_table_lookup (MonoConcGHashTable
*hash
, gconstpointer key
)
218 gpointer orig_key
, value
;
220 if (mono_conc_g_hash_table_lookup_extended (hash
, key
, &orig_key
, &value
))
227 mono_conc_g_hash_table_lookup_extended (MonoConcGHashTable
*hash_table
, gconstpointer key
, gpointer
*orig_key_ptr
, gpointer
*value_ptr
)
229 MonoThreadHazardPointers
* hp
;
231 int hash
, i
, table_mask
;
232 hash
= mix_hash (hash_table
->hash_func (key
));
233 hp
= mono_hazard_pointer_get ();
236 table
= (conc_table
*)mono_get_hazardous_pointer ((gpointer
volatile*)&hash_table
->table
, hp
, 0);
237 table_mask
= table
->table_size
- 1;
238 i
= hash
& table_mask
;
240 if (G_LIKELY (!hash_table
->equal_func
)) {
241 while (table
->keys
[i
]) {
242 gpointer orig_key
= table
->keys
[i
];
243 if (key
== orig_key
) {
245 /* The read of keys must happen before the read of values */
246 mono_memory_barrier ();
247 value
= table
->values
[i
];
249 /* We just read a value been deleted, try again. */
250 if (G_UNLIKELY (!value
))
253 mono_hazard_pointer_clear (hp
, 0);
255 *orig_key_ptr
= orig_key
;
259 i
= (i
+ 1) & table_mask
;
262 GEqualFunc equal
= hash_table
->equal_func
;
264 while (table
->keys
[i
]) {
265 gpointer orig_key
= table
->keys
[i
];
266 if (!key_is_tombstone (hash_table
, orig_key
) && equal (key
, orig_key
)) {
268 /* The read of keys must happen before the read of values */
269 mono_memory_barrier ();
270 value
= table
->values
[i
];
272 /* We just read a value been deleted, try again. */
273 if (G_UNLIKELY (!value
))
276 mono_hazard_pointer_clear (hp
, 0);
277 *orig_key_ptr
= orig_key
;
282 i
= (i
+ 1) & table_mask
;
286 /* The table might have expanded and the value is now on the newer table */
287 mono_memory_barrier ();
288 if (hash_table
->table
!= table
)
291 mono_hazard_pointer_clear (hp
, 0);
293 *orig_key_ptr
= NULL
;
299 mono_conc_g_hash_table_foreach (MonoConcGHashTable
*hash_table
, GHFunc func
, gpointer user_data
)
302 conc_table
*table
= (conc_table
*)hash_table
->table
;
304 for (i
= 0; i
< table
->table_size
; ++i
) {
305 if (table
->keys
[i
] && !key_is_tombstone (hash_table
, table
->keys
[i
])) {
306 func (table
->keys
[i
], table
->values
[i
], user_data
);
312 mono_conc_g_hash_table_destroy (MonoConcGHashTable
*hash_table
)
314 if (hash_table
->key_destroy_func
|| hash_table
->value_destroy_func
) {
316 conc_table
*table
= (conc_table
*)hash_table
->table
;
318 for (i
= 0; i
< table
->table_size
; ++i
) {
319 if (table
->keys
[i
] && !key_is_tombstone (hash_table
, table
->keys
[i
])) {
320 if (hash_table
->key_destroy_func
)
321 (hash_table
->key_destroy_func
) (table
->keys
[i
]);
322 if (hash_table
->value_destroy_func
)
323 (hash_table
->value_destroy_func
) (table
->values
[i
]);
327 conc_table_free ((gpointer
)hash_table
->table
);
331 /* Return NULL on success or the old value in failure */
333 mono_conc_g_hash_table_insert (MonoConcGHashTable
*hash_table
, gpointer key
, gpointer value
)
336 int hash
, i
, table_mask
;
338 g_assert (key
!= NULL
);
339 g_assert (value
!= NULL
);
341 hash
= mix_hash (hash_table
->hash_func (key
));
343 check_table_size (hash_table
);
345 table
= (conc_table
*)hash_table
->table
;
346 table_mask
= table
->table_size
- 1;
347 i
= hash
& table_mask
;
349 if (!hash_table
->equal_func
) {
351 gpointer cur_key
= table
->keys
[i
];
352 gboolean is_tombstone
= FALSE
;
353 if (!cur_key
|| (is_tombstone
= key_is_tombstone (hash_table
, cur_key
))) {
354 set_value (table
, i
, value
);
356 /* The write to values must happen after the write to keys */
357 mono_memory_barrier ();
358 set_key (table
, i
, key
);
360 --hash_table
->tombstone_count
;
362 ++hash_table
->element_count
;
366 if (key
== cur_key
) {
367 gpointer value
= table
->values
[i
];
370 i
= (i
+ 1) & table_mask
;
373 GEqualFunc equal
= hash_table
->equal_func
;
375 gpointer cur_key
= table
->keys
[i
];
376 gboolean is_tombstone
= FALSE
;
377 if (!cur_key
|| (is_tombstone
= key_is_tombstone (hash_table
, cur_key
))) {
378 set_value (table
, i
, value
);
379 /* The write to values must happen after the write to keys */
380 mono_memory_barrier ();
381 set_key (table
, i
, key
);
383 --hash_table
->tombstone_count
;
385 ++hash_table
->element_count
;
389 if (equal (key
, cur_key
)) {
390 gpointer value
= table
->values
[i
];
393 i
= (i
+ 1) & table_mask
;
399 mono_conc_g_hash_table_remove (MonoConcGHashTable
*hash_table
, gconstpointer key
)
402 int hash
, i
, table_mask
;
404 g_assert (key
!= NULL
);
406 hash
= mix_hash (hash_table
->hash_func (key
));
408 table
= (conc_table
*)hash_table
->table
;
409 table_mask
= table
->table_size
- 1;
410 i
= hash
& table_mask
;
412 if (!hash_table
->equal_func
) {
414 gpointer cur_key
= table
->keys
[i
];
416 return NULL
; /*key not found*/
419 if (key
== cur_key
) {
420 gpointer value
= table
->values
[i
];
421 table
->values
[i
] = NULL
;
422 mono_memory_barrier ();
423 set_key_to_tombstone (table
, i
);
424 ++hash_table
->tombstone_count
;
426 if (hash_table
->key_destroy_func
!= NULL
)
427 (*hash_table
->key_destroy_func
) (cur_key
);
428 if (hash_table
->value_destroy_func
!= NULL
)
429 (*hash_table
->value_destroy_func
) (value
);
431 check_table_size (hash_table
);
434 i
= (i
+ 1) & table_mask
;
437 GEqualFunc equal
= hash_table
->equal_func
;
439 gpointer cur_key
= table
->keys
[i
];
441 return NULL
; /*key not found*/
444 if (!key_is_tombstone (hash_table
, cur_key
) && equal (key
, cur_key
)) {
445 gpointer value
= table
->values
[i
];
446 table
->values
[i
] = NULL
;
447 mono_memory_barrier ();
448 set_key_to_tombstone (table
, i
);
450 if (hash_table
->key_destroy_func
!= NULL
)
451 (*hash_table
->key_destroy_func
) (cur_key
);
452 if (hash_table
->value_destroy_func
!= NULL
)
453 (*hash_table
->value_destroy_func
) (value
);
455 check_table_size (hash_table
);
459 i
= (i
+ 1) & table_mask
;