1 /**
2 * \file
3 * Conc GC aware Hashtable implementation
4 *
5 * Author:
6 * Rodrigo Kumpera (kumpera@gmail.com)
7 *
8 */
9 #include <config.h>
10 #include <stdio.h>
11 #include <math.h>
12 #include <glib.h>
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>
17
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
23
24 typedef struct {
25 int table_size;
26 MonoGHashGCType gc_type;
27 void **keys;
28 void **values;
29 } conc_table;
30
31 struct _MonoConcGHashTable {
32 volatile conc_table *table; /* goes to HP0 */
33 GHashFunc hash_func;
34 GEqualFunc equal_func;
35 int element_count;
36 int overflow_count;
37 GDestroyNotify key_destroy_func;
38 GDestroyNotify value_destroy_func;
39 MonoGHashGCType gc_type;
40 MonoGCRootSource source;
41 const char *msg;
42 };
43
44
45 static conc_table*
conc_table_new(MonoConcGHashTable * hash,int size)46 conc_table_new (MonoConcGHashTable *hash, int size)
47 {
48 conc_table *table = g_new0 (conc_table, 1);
49
50 table->keys = g_new0 (void*, size);
51 table->values = g_new0 (void*, size);
52 table->table_size = size;
53 table->gc_type = hash->gc_type;
54
55 if (hash->gc_type & MONO_HASH_KEY_GC)
56 mono_gc_register_root_wbarrier ((char*)table->keys, sizeof (MonoObject*) * size, mono_gc_make_vector_descr (), hash->source, hash->msg);
57 if (hash->gc_type & MONO_HASH_VALUE_GC)
58 mono_gc_register_root_wbarrier ((char*)table->values, sizeof (MonoObject*) * size, mono_gc_make_vector_descr (), hash->source, hash->msg);
59
60 return table;
61 }
62
63 static void
conc_table_free(gpointer ptr)64 conc_table_free (gpointer ptr)
65 {
66 conc_table *table = (conc_table *)ptr;
67 if (table->gc_type & MONO_HASH_KEY_GC)
68 mono_gc_deregister_root ((char*)table->keys);
69 if (table->gc_type & MONO_HASH_VALUE_GC)
70 mono_gc_deregister_root ((char*)table->values);
71
72 g_free (table->keys);
73 g_free (table->values);
74 g_free (table);
75 }
76
77 static void
conc_table_lf_free(conc_table * table)78 conc_table_lf_free (conc_table *table)
79 {
80 mono_thread_hazardous_try_free (table, conc_table_free);
81 }
82
83
84 static gboolean
key_is_tombstone(MonoConcGHashTable * hash,gpointer ptr)85 key_is_tombstone (MonoConcGHashTable *hash, gpointer ptr)
86 {
87 if (hash->gc_type & MONO_HASH_KEY_GC)
88 return ptr == mono_domain_get()->ephemeron_tombstone;
89 return ptr == PTR_TOMBSTONE;
90 }
91
92 /*
93 A common problem with power of two hashtables is that it leads of bad clustering when dealing
94 with aligned numbers.
95
96 The solution here is to mix the bits from two primes plus the hash itself, it produces a better spread
97 than just the numbers.
98 */
99
100 static MONO_ALWAYS_INLINE int
mix_hash(int hash)101 mix_hash (int hash)
102 {
103 return ((hash * 215497) >> 16) ^ (hash * 1823231 + hash);
104 }
105
106
107 static inline void
set_key(conc_table * table,int slot,gpointer key)108 set_key (conc_table *table, int slot, gpointer key)
109 {
110 gpointer *key_addr = &table->keys [slot];
111 if (table->gc_type & MONO_HASH_KEY_GC)
112 mono_gc_wbarrier_generic_store (key_addr, key);
113 else
114 *key_addr = key;
115 }
116
117 static inline void
set_key_to_tombstone(conc_table * table,int slot)118 set_key_to_tombstone (conc_table *table, int slot)
119 {
120 gpointer *key_addr = &table->keys [slot];
121 if (table->gc_type & MONO_HASH_KEY_GC)
122 mono_gc_wbarrier_generic_store (key_addr, mono_domain_get()->ephemeron_tombstone);
123 else
124 *key_addr = PTR_TOMBSTONE;
125 }
126
127 static inline void
set_value(conc_table * table,int slot,gpointer value)128 set_value (conc_table *table, int slot, gpointer value)
129 {
130 gpointer *value_addr = &table->values [slot];
131 if (table->gc_type & MONO_HASH_VALUE_GC)
132 mono_gc_wbarrier_generic_store (value_addr, value);
133 else
134 *value_addr = value;
135 }
136
137 static MONO_ALWAYS_INLINE void
insert_one_local(conc_table * table,GHashFunc hash_func,gpointer key,gpointer value)138 insert_one_local (conc_table *table, GHashFunc hash_func, gpointer key, gpointer value)
139 {
140 int table_mask = table->table_size - 1;
141 int hash = mix_hash (hash_func (key));
142 int i = hash & table_mask;
143
144 while (table->keys [i])
145 i = (i + 1) & table_mask;
146
147 set_key (table, i, key);
148 set_value (table, i, value);
149 }
150
151 static void
expand_table(MonoConcGHashTable * hash_table)152 expand_table (MonoConcGHashTable *hash_table)
153 {
154 conc_table *old_table = (conc_table*)hash_table->table;
155 conc_table *new_table = conc_table_new (hash_table, old_table->table_size * EXPAND_RATIO);
156 int i;
157
158 for (i = 0; i < old_table->table_size; ++i) {
159 if (old_table->keys [i] && !key_is_tombstone (hash_table, old_table->keys [i]))
160 insert_one_local (new_table, hash_table->hash_func, old_table->keys [i], old_table->values [i]);
161 }
162
163 mono_memory_barrier ();
164 hash_table->table = new_table;
165 hash_table->overflow_count = (int)(new_table->table_size * LOAD_FACTOR);
166 conc_table_lf_free (old_table);
167 }
168
169
170 MonoConcGHashTable *
mono_conc_g_hash_table_new_type(GHashFunc hash_func,GEqualFunc key_equal_func,MonoGHashGCType type,MonoGCRootSource source,const char * msg)171 mono_conc_g_hash_table_new_type (GHashFunc hash_func, GEqualFunc key_equal_func, MonoGHashGCType type, MonoGCRootSource source, const char *msg)
172 {
173 MonoConcGHashTable *hash;
174
175 if (!hash_func)
176 hash_func = g_direct_hash;
177
178 hash = g_new0 (MonoConcGHashTable, 1);
179 hash->hash_func = hash_func;
180 hash->equal_func = key_equal_func;
181
182 hash->element_count = 0;
183 hash->overflow_count = (int)(INITIAL_SIZE * LOAD_FACTOR);
184 hash->gc_type = type;
185 hash->source = source;
186 hash->msg = msg;
187
188 hash->table = conc_table_new (hash, INITIAL_SIZE);
189
190 if (type > MONO_HASH_KEY_VALUE_GC)
191 g_error ("wrong type for gc hashtable");
192
193 return hash;
194 }
195
196 gpointer
mono_conc_g_hash_table_lookup(MonoConcGHashTable * hash,gconstpointer key)197 mono_conc_g_hash_table_lookup (MonoConcGHashTable *hash, gconstpointer key)
198 {
199 gpointer orig_key, value;
200
201 if (mono_conc_g_hash_table_lookup_extended (hash, key, &orig_key, &value))
202 return value;
203 else
204 return NULL;
205 }
206
207 gboolean
mono_conc_g_hash_table_lookup_extended(MonoConcGHashTable * hash_table,gconstpointer key,gpointer * orig_key_ptr,gpointer * value_ptr)208 mono_conc_g_hash_table_lookup_extended (MonoConcGHashTable *hash_table, gconstpointer key, gpointer *orig_key_ptr, gpointer *value_ptr)
209 {
210 MonoThreadHazardPointers* hp;
211 conc_table *table;
212 int hash, i, table_mask;
213 hash = mix_hash (hash_table->hash_func (key));
214 hp = mono_hazard_pointer_get ();
215
216 retry:
217 table = (conc_table *)mono_get_hazardous_pointer ((gpointer volatile*)&hash_table->table, hp, 0);
218 table_mask = table->table_size - 1;
219 i = hash & table_mask;
220
221 if (G_LIKELY (!hash_table->equal_func)) {
222 while (table->keys [i]) {
223 gpointer orig_key = table->keys [i];
224 if (key == orig_key) {
225 gpointer value;
226 /* The read of keys must happen before the read of values */
227 mono_memory_barrier ();
228 value = table->values [i];
229
230 /* We just read a value been deleted, try again. */
231 if (G_UNLIKELY (!value))
232 goto retry;
233
234 mono_hazard_pointer_clear (hp, 0);
235
236 *orig_key_ptr = orig_key;
237 *value_ptr = value;
238 return TRUE;
239 }
240 i = (i + 1) & table_mask;
241 }
242 } else {
243 GEqualFunc equal = hash_table->equal_func;
244
245 while (table->keys [i]) {
246 gpointer orig_key = table->keys [i];
247 if (!key_is_tombstone (hash_table, orig_key) && equal (key, orig_key)) {
248 gpointer value;
249 /* The read of keys must happen before the read of values */
250 mono_memory_barrier ();
251 value = table->values [i];
252
253 /* We just read a value been deleted, try again. */
254 if (G_UNLIKELY (!value))
255 goto retry;
256
257 mono_hazard_pointer_clear (hp, 0);
258 *orig_key_ptr = orig_key;
259 *value_ptr = value;
260 return TRUE;
261
262 }
263 i = (i + 1) & table_mask;
264 }
265 }
266
267 /* The table might have expanded and the value is now on the newer table */
268 mono_memory_barrier ();
269 if (hash_table->table != table)
270 goto retry;
271
272 mono_hazard_pointer_clear (hp, 0);
273
274 *orig_key_ptr = NULL;
275 *value_ptr = NULL;
276 return FALSE;
277 }
278
279 void
mono_conc_g_hash_table_foreach(MonoConcGHashTable * hash_table,GHFunc func,gpointer user_data)280 mono_conc_g_hash_table_foreach (MonoConcGHashTable *hash_table, GHFunc func, gpointer user_data)
281 {
282 int i;
283 conc_table *table = (conc_table*)hash_table->table;
284
285 for (i = 0; i < table->table_size; ++i) {
286 if (table->keys [i] && !key_is_tombstone (hash_table, table->keys [i])) {
287 func (table->keys [i], table->values [i], user_data);
288 }
289 }
290 }
291
292 void
mono_conc_g_hash_table_destroy(MonoConcGHashTable * hash_table)293 mono_conc_g_hash_table_destroy (MonoConcGHashTable *hash_table)
294 {
295 if (hash_table->key_destroy_func || hash_table->value_destroy_func) {
296 int i;
297 conc_table *table = (conc_table*)hash_table->table;
298
299 for (i = 0; i < table->table_size; ++i) {
300 if (table->keys [i] && !key_is_tombstone (hash_table, table->keys [i])) {
301 if (hash_table->key_destroy_func)
302 (hash_table->key_destroy_func) (table->keys [i]);
303 if (hash_table->value_destroy_func)
304 (hash_table->value_destroy_func) (table->values [i]);
305 }
306 }
307 }
308 conc_table_free ((gpointer)hash_table->table);
309 g_free (hash_table);
310 }
311
312 /* Return NULL on success or the old value in failure */
313 gpointer
mono_conc_g_hash_table_insert(MonoConcGHashTable * hash_table,gpointer key,gpointer value)314 mono_conc_g_hash_table_insert (MonoConcGHashTable *hash_table, gpointer key, gpointer value)
315 {
316 conc_table *table;
317 int hash, i, table_mask;
318
319 g_assert (key != NULL);
320 g_assert (value != NULL);
321
322 hash = mix_hash (hash_table->hash_func (key));
323
324 if (hash_table->element_count >= hash_table->overflow_count)
325 expand_table (hash_table);
326
327 table = (conc_table*)hash_table->table;
328 table_mask = table->table_size - 1;
329 i = hash & table_mask;
330
331 if (!hash_table->equal_func) {
332 for (;;) {
333 gpointer cur_key = table->keys [i];
334 if (!cur_key || key_is_tombstone (hash_table, cur_key)) {
335 set_value (table, i, value);
336
337 /* The write to values must happen after the write to keys */
338 mono_memory_barrier ();
339 set_key (table, i, key);
340 ++hash_table->element_count;
341 return NULL;
342 }
343 if (key == cur_key) {
344 gpointer value = table->values [i];
345 return value;
346 }
347 i = (i + 1) & table_mask;
348 }
349 } else {
350 GEqualFunc equal = hash_table->equal_func;
351 for (;;) {
352 gpointer cur_key = table->keys [i];
353 if (!cur_key || key_is_tombstone (hash_table, cur_key)) {
354 set_value (table, i, value);
355 /* The write to values must happen after the write to keys */
356 mono_memory_barrier ();
357 set_key (table, i, key);
358 ++hash_table->element_count;
359 return NULL;
360 }
361 if (equal (key, cur_key)) {
362 gpointer value = table->values [i];
363 return value;
364 }
365 i = (i + 1) & table_mask;
366 }
367 }
368 }
369
370 gpointer
mono_conc_g_hash_table_remove(MonoConcGHashTable * hash_table,gconstpointer key)371 mono_conc_g_hash_table_remove (MonoConcGHashTable *hash_table, gconstpointer key)
372 {
373 conc_table *table;
374 int hash, i, table_mask;
375
376 g_assert (key != NULL);
377
378 hash = mix_hash (hash_table->hash_func (key));
379
380 table = (conc_table*)hash_table->table;
381 table_mask = table->table_size - 1;
382 i = hash & table_mask;
383
384 if (!hash_table->equal_func) {
385 for (;;) {
386 gpointer cur_key = table->keys [i];
387 if (!cur_key) {
388 return NULL; /*key not found*/
389 }
390
391 if (key == cur_key) {
392 gpointer value = table->values [i];
393 table->values [i] = NULL;
394 mono_memory_barrier ();
395 set_key_to_tombstone (table, i);
396
397 --hash_table->element_count;
398
399 if (hash_table->key_destroy_func != NULL)
400 (*hash_table->key_destroy_func) (cur_key);
401 if (hash_table->value_destroy_func != NULL)
402 (*hash_table->value_destroy_func) (value);
403
404 return value;
405 }
406 i = (i + 1) & table_mask;
407 }
408 } else {
409 GEqualFunc equal = hash_table->equal_func;
410 for (;;) {
411 gpointer cur_key = table->keys [i];
412 if (!cur_key) {
413 return NULL; /*key not found*/
414 }
415
416 if (!key_is_tombstone (hash_table, cur_key) && equal (key, cur_key)) {
417 gpointer value = table->values [i];
418 table->values [i] = NULL;
419 mono_memory_barrier ();
420 set_key_to_tombstone (table, i);
421
422 if (hash_table->key_destroy_func != NULL)
423 (*hash_table->key_destroy_func) (cur_key);
424 if (hash_table->value_destroy_func != NULL)
425 (*hash_table->value_destroy_func) (value);
426 return value;
427 }
428
429 i = (i + 1) & table_mask;
430 }
431 }
432 }