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 }