[w32handle] Initialize them earlier
[mono-project.git] / mono / utils / lock-free-alloc.c
blobd103ef6aa333eeb96a920d8e62ef0af79db5ecb2
1 /*
2 * lock-free-alloc.c: Lock free allocator.
4 * (C) Copyright 2011 Novell, Inc
6 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
7 */
9 /*
10 * This is a simplified version of the lock-free allocator described in
12 * Scalable Lock-Free Dynamic Memory Allocation
13 * Maged M. Michael, PLDI 2004
15 * I could not get Michael's allocator working bug free under heavy
16 * stress tests. The paper doesn't provide correctness proof and after
17 * failing to formalize the ownership of descriptors I devised this
18 * simpler allocator.
20 * Allocation within superblocks proceeds exactly like in Michael's
21 * allocator. The simplification is that a thread has to "acquire" a
22 * descriptor before it can allocate from its superblock. While it owns
23 * the descriptor no other thread can acquire and hence allocate from
24 * it. A consequence of this is that the ABA problem cannot occur, so
25 * we don't need the tag field and don't have to use 64 bit CAS.
27 * Descriptors are stored in two locations: The partial queue and the
28 * active field. They can only be in at most one of those at one time.
29 * If a thread wants to allocate, it needs to get a descriptor. It
30 * tries the active descriptor first, CASing it to NULL. If that
31 * doesn't work, it gets a descriptor out of the partial queue. Once it
32 * has the descriptor it owns it because it is not referenced anymore.
33 * It allocates a slot and then gives the descriptor back (unless it is
34 * FULL).
36 * Note that it is still possible that a slot is freed while an
37 * allocation is in progress from the same superblock. Ownership in
38 * this case is not complicated, though. If the block was FULL and the
39 * free set it to PARTIAL, the free now owns the block (because FULL
40 * blocks are not referenced from partial and active) and has to give it
41 * back. If the block was PARTIAL then the free doesn't own the block
42 * (because it's either still referenced, or an alloc owns it). A
43 * special case of this is that it has changed from PARTIAL to EMPTY and
44 * now needs to be retired. Technically, the free wouldn't have to do
45 * anything in this case because the first thing an alloc does when it
46 * gets ownership of a descriptor is to check whether it is EMPTY and
47 * retire it if that is the case. As an optimization, our free does try
48 * to acquire the descriptor (by CASing the active field, which, if it
49 * is lucky, points to that descriptor) and if it can do so, retire it.
50 * If it can't, it tries to retire other descriptors from the partial
51 * queue, so that we can be sure that even if no more allocations
52 * happen, descriptors are still retired. This is analogous to what
53 * Michael's allocator does.
55 * Another difference to Michael's allocator is not related to
56 * concurrency, however: We don't point from slots to descriptors.
57 * Instead we allocate superblocks aligned and point from the start of
58 * the superblock to the descriptor, so we only need one word of
59 * metadata per superblock.
61 * FIXME: Having more than one allocator per size class is probably
62 * buggy because it was never tested.
65 #include <glib.h>
66 #include <stdlib.h>
68 #include <mono/utils/atomic.h>
69 #ifdef SGEN_WITHOUT_MONO
70 #include <mono/sgen/sgen-gc.h>
71 #include <mono/sgen/sgen-client.h>
72 #else
73 #include <mono/utils/mono-mmap.h>
74 #endif
75 #include <mono/utils/mono-membar.h>
76 #include <mono/utils/hazard-pointer.h>
77 #include <mono/utils/lock-free-queue.h>
79 #include <mono/utils/lock-free-alloc.h>
81 //#define DESC_AVAIL_DUMMY
83 enum {
84 STATE_FULL,
85 STATE_PARTIAL,
86 STATE_EMPTY
89 typedef union {
90 gint32 value;
91 struct {
92 guint32 avail : 15;
93 guint32 count : 15;
94 guint32 state : 2;
95 } data;
96 } Anchor;
98 typedef struct _MonoLockFreeAllocDescriptor Descriptor;
99 struct _MonoLockFreeAllocDescriptor {
100 MonoLockFreeQueueNode node;
101 MonoLockFreeAllocator *heap;
102 volatile Anchor anchor;
103 unsigned int slot_size;
104 unsigned int block_size;
105 unsigned int max_count;
106 gpointer sb;
107 #ifndef DESC_AVAIL_DUMMY
108 Descriptor * volatile next;
109 #endif
110 gboolean in_use; /* used for debugging only */
113 #define NUM_DESC_BATCH 64
115 static MONO_ALWAYS_INLINE gpointer
116 sb_header_for_addr (gpointer addr, size_t block_size)
118 return (gpointer)(((size_t)addr) & (~(block_size - 1)));
121 /* Taken from SGen */
123 static unsigned long
124 prot_flags_for_activate (int activate)
126 unsigned long prot_flags = activate? MONO_MMAP_READ|MONO_MMAP_WRITE: MONO_MMAP_NONE;
127 return prot_flags | MONO_MMAP_PRIVATE | MONO_MMAP_ANON;
130 static gpointer
131 alloc_sb (Descriptor *desc)
133 static int pagesize = -1;
135 gpointer sb_header;
137 if (pagesize == -1)
138 pagesize = mono_pagesize ();
140 sb_header = desc->block_size == pagesize ?
141 mono_valloc (NULL, desc->block_size, prot_flags_for_activate (TRUE)) :
142 mono_valloc_aligned (desc->block_size, desc->block_size, prot_flags_for_activate (TRUE));
144 g_assert (sb_header == sb_header_for_addr (sb_header, desc->block_size));
146 *(Descriptor**)sb_header = desc;
147 //g_print ("sb %p for %p\n", sb_header, desc);
149 return (char*)sb_header + LOCK_FREE_ALLOC_SB_HEADER_SIZE;
152 static void
153 free_sb (gpointer sb, size_t block_size)
155 gpointer sb_header = sb_header_for_addr (sb, block_size);
156 g_assert ((char*)sb_header + LOCK_FREE_ALLOC_SB_HEADER_SIZE == sb);
157 mono_vfree (sb_header, block_size);
158 //g_print ("free sb %p\n", sb_header);
161 #ifndef DESC_AVAIL_DUMMY
162 static Descriptor * volatile desc_avail;
164 static Descriptor*
165 desc_alloc (void)
167 MonoThreadHazardPointers *hp = mono_hazard_pointer_get ();
168 Descriptor *desc;
170 for (;;) {
171 gboolean success;
173 desc = (Descriptor *) get_hazardous_pointer ((gpointer * volatile)&desc_avail, hp, 1);
174 if (desc) {
175 Descriptor *next = desc->next;
176 success = (InterlockedCompareExchangePointer ((gpointer * volatile)&desc_avail, next, desc) == desc);
177 } else {
178 size_t desc_size = sizeof (Descriptor);
179 Descriptor *d;
180 int i;
182 desc = (Descriptor *) mono_valloc (NULL, desc_size * NUM_DESC_BATCH, prot_flags_for_activate (TRUE));
184 /* Organize into linked list. */
185 d = desc;
186 for (i = 0; i < NUM_DESC_BATCH; ++i) {
187 Descriptor *next = (i == (NUM_DESC_BATCH - 1)) ? NULL : (Descriptor*)((char*)desc + ((i + 1) * desc_size));
188 d->next = next;
189 mono_lock_free_queue_node_init (&d->node, TRUE);
190 d = next;
193 mono_memory_write_barrier ();
195 success = (InterlockedCompareExchangePointer ((gpointer * volatile)&desc_avail, desc->next, NULL) == NULL);
197 if (!success)
198 mono_vfree (desc, desc_size * NUM_DESC_BATCH);
201 mono_hazard_pointer_clear (hp, 1);
203 if (success)
204 break;
207 g_assert (!desc->in_use);
208 desc->in_use = TRUE;
210 return desc;
213 static void
214 desc_enqueue_avail (gpointer _desc)
216 Descriptor *desc = (Descriptor *) _desc;
217 Descriptor *old_head;
219 g_assert (desc->anchor.data.state == STATE_EMPTY);
220 g_assert (!desc->in_use);
222 do {
223 old_head = desc_avail;
224 desc->next = old_head;
225 mono_memory_write_barrier ();
226 } while (InterlockedCompareExchangePointer ((gpointer * volatile)&desc_avail, desc, old_head) != old_head);
229 static void
230 desc_retire (Descriptor *desc)
232 g_assert (desc->anchor.data.state == STATE_EMPTY);
233 g_assert (desc->in_use);
234 desc->in_use = FALSE;
235 free_sb (desc->sb, desc->block_size);
236 mono_thread_hazardous_try_free (desc, desc_enqueue_avail);
238 #else
239 MonoLockFreeQueue available_descs;
241 static Descriptor*
242 desc_alloc (void)
244 Descriptor *desc = (Descriptor*)mono_lock_free_queue_dequeue (&available_descs);
246 if (desc)
247 return desc;
249 return calloc (1, sizeof (Descriptor));
252 static void
253 desc_retire (Descriptor *desc)
255 free_sb (desc->sb, desc->block_size);
256 mono_lock_free_queue_enqueue (&available_descs, &desc->node);
258 #endif
260 static Descriptor*
261 list_get_partial (MonoLockFreeAllocSizeClass *sc)
263 for (;;) {
264 Descriptor *desc = (Descriptor*) mono_lock_free_queue_dequeue (&sc->partial);
265 if (!desc)
266 return NULL;
267 if (desc->anchor.data.state != STATE_EMPTY)
268 return desc;
269 desc_retire (desc);
273 static void
274 desc_put_partial (gpointer _desc)
276 Descriptor *desc = (Descriptor *) _desc;
278 g_assert (desc->anchor.data.state != STATE_FULL);
280 mono_lock_free_queue_node_unpoison (&desc->node);
281 mono_lock_free_queue_enqueue (&desc->heap->sc->partial, &desc->node);
284 static void
285 list_put_partial (Descriptor *desc)
287 g_assert (desc->anchor.data.state != STATE_FULL);
288 mono_thread_hazardous_try_free (desc, desc_put_partial);
291 static void
292 list_remove_empty_desc (MonoLockFreeAllocSizeClass *sc)
294 int num_non_empty = 0;
295 for (;;) {
296 Descriptor *desc = (Descriptor*) mono_lock_free_queue_dequeue (&sc->partial);
297 if (!desc)
298 return;
300 * We don't need to read atomically because we're the
301 * only thread that references this descriptor.
303 if (desc->anchor.data.state == STATE_EMPTY) {
304 desc_retire (desc);
305 } else {
306 g_assert (desc->heap->sc == sc);
307 mono_thread_hazardous_try_free (desc, desc_put_partial);
308 if (++num_non_empty >= 2)
309 return;
314 static Descriptor*
315 heap_get_partial (MonoLockFreeAllocator *heap)
317 return list_get_partial (heap->sc);
320 static void
321 heap_put_partial (Descriptor *desc)
323 list_put_partial (desc);
326 static gboolean
327 set_anchor (Descriptor *desc, Anchor old_anchor, Anchor new_anchor)
329 if (old_anchor.data.state == STATE_EMPTY)
330 g_assert (new_anchor.data.state == STATE_EMPTY);
332 return InterlockedCompareExchange (&desc->anchor.value, new_anchor.value, old_anchor.value) == old_anchor.value;
335 static gpointer
336 alloc_from_active_or_partial (MonoLockFreeAllocator *heap)
338 Descriptor *desc;
339 Anchor old_anchor, new_anchor;
340 gpointer addr;
342 retry:
343 desc = heap->active;
344 if (desc) {
345 if (InterlockedCompareExchangePointer ((gpointer * volatile)&heap->active, NULL, desc) != desc)
346 goto retry;
347 } else {
348 desc = heap_get_partial (heap);
349 if (!desc)
350 return NULL;
353 /* Now we own the desc. */
355 do {
356 unsigned int next;
357 new_anchor = old_anchor = *(volatile Anchor*)&desc->anchor.value;
358 if (old_anchor.data.state == STATE_EMPTY) {
359 /* We must free it because we own it. */
360 desc_retire (desc);
361 goto retry;
363 g_assert (old_anchor.data.state == STATE_PARTIAL);
364 g_assert (old_anchor.data.count > 0);
366 addr = (char*)desc->sb + old_anchor.data.avail * desc->slot_size;
368 mono_memory_read_barrier ();
370 next = *(unsigned int*)addr;
371 g_assert (next < LOCK_FREE_ALLOC_SB_USABLE_SIZE (desc->block_size) / desc->slot_size);
373 new_anchor.data.avail = next;
374 --new_anchor.data.count;
376 if (new_anchor.data.count == 0)
377 new_anchor.data.state = STATE_FULL;
378 } while (!set_anchor (desc, old_anchor, new_anchor));
380 /* If the desc is partial we have to give it back. */
381 if (new_anchor.data.state == STATE_PARTIAL) {
382 if (InterlockedCompareExchangePointer ((gpointer * volatile)&heap->active, desc, NULL) != NULL)
383 heap_put_partial (desc);
386 return addr;
389 static gpointer
390 alloc_from_new_sb (MonoLockFreeAllocator *heap)
392 unsigned int slot_size, block_size, count, i;
393 Descriptor *desc = desc_alloc ();
395 slot_size = desc->slot_size = heap->sc->slot_size;
396 block_size = desc->block_size = heap->sc->block_size;
397 count = LOCK_FREE_ALLOC_SB_USABLE_SIZE (block_size) / slot_size;
399 desc->heap = heap;
401 * Setting avail to 1 because 0 is the block we're allocating
402 * right away.
404 desc->anchor.data.avail = 1;
405 desc->slot_size = heap->sc->slot_size;
406 desc->max_count = count;
408 desc->anchor.data.count = desc->max_count - 1;
409 desc->anchor.data.state = STATE_PARTIAL;
411 desc->sb = alloc_sb (desc);
413 /* Organize blocks into linked list. */
414 for (i = 1; i < count - 1; ++i)
415 *(unsigned int*)((char*)desc->sb + i * slot_size) = i + 1;
417 mono_memory_write_barrier ();
419 /* Make it active or free it again. */
420 if (InterlockedCompareExchangePointer ((gpointer * volatile)&heap->active, desc, NULL) == NULL) {
421 return desc->sb;
422 } else {
423 desc->anchor.data.state = STATE_EMPTY;
424 desc_retire (desc);
425 return NULL;
429 gpointer
430 mono_lock_free_alloc (MonoLockFreeAllocator *heap)
432 gpointer addr;
434 for (;;) {
436 addr = alloc_from_active_or_partial (heap);
437 if (addr)
438 break;
440 addr = alloc_from_new_sb (heap);
441 if (addr)
442 break;
445 return addr;
448 void
449 mono_lock_free_free (gpointer ptr, size_t block_size)
451 Anchor old_anchor, new_anchor;
452 Descriptor *desc;
453 gpointer sb;
454 MonoLockFreeAllocator *heap = NULL;
456 desc = *(Descriptor**) sb_header_for_addr (ptr, block_size);
457 g_assert (block_size == desc->block_size);
459 sb = desc->sb;
461 do {
462 new_anchor = old_anchor = *(volatile Anchor*)&desc->anchor.value;
463 *(unsigned int*)ptr = old_anchor.data.avail;
464 new_anchor.data.avail = ((char*)ptr - (char*)sb) / desc->slot_size;
465 g_assert (new_anchor.data.avail < LOCK_FREE_ALLOC_SB_USABLE_SIZE (block_size) / desc->slot_size);
467 if (old_anchor.data.state == STATE_FULL)
468 new_anchor.data.state = STATE_PARTIAL;
470 if (++new_anchor.data.count == desc->max_count) {
471 heap = desc->heap;
472 new_anchor.data.state = STATE_EMPTY;
474 } while (!set_anchor (desc, old_anchor, new_anchor));
476 if (new_anchor.data.state == STATE_EMPTY) {
477 g_assert (old_anchor.data.state != STATE_EMPTY);
479 if (InterlockedCompareExchangePointer ((gpointer * volatile)&heap->active, NULL, desc) == desc) {
481 * We own desc, check if it's still empty, in which case we retire it.
482 * If it's partial we need to put it back either on the active slot or
483 * on the partial list.
485 if (desc->anchor.data.state == STATE_EMPTY) {
486 desc_retire (desc);
487 } else if (desc->anchor.data.state == STATE_PARTIAL) {
488 if (InterlockedCompareExchangePointer ((gpointer * volatile)&heap->active, desc, NULL) != NULL)
489 heap_put_partial (desc);
492 } else {
494 * Somebody else must free it, so we do some
495 * freeing for others.
497 list_remove_empty_desc (heap->sc);
499 } else if (old_anchor.data.state == STATE_FULL) {
501 * Nobody owned it, now we do, so we need to give it
502 * back.
505 g_assert (new_anchor.data.state == STATE_PARTIAL);
507 if (InterlockedCompareExchangePointer ((gpointer * volatile)&desc->heap->active, desc, NULL) != NULL)
508 heap_put_partial (desc);
512 #define g_assert_OR_PRINT(c, format, ...) do { \
513 if (!(c)) { \
514 if (print) \
515 g_print ((format), ## __VA_ARGS__); \
516 else \
517 g_assert (FALSE); \
519 } while (0)
521 static void
522 descriptor_check_consistency (Descriptor *desc, gboolean print)
524 int count = desc->anchor.data.count;
525 int max_count = LOCK_FREE_ALLOC_SB_USABLE_SIZE (desc->block_size) / desc->slot_size;
526 #if _MSC_VER
527 gboolean* linked = alloca(max_count*sizeof(gboolean));
528 #else
529 gboolean linked [max_count];
530 #endif
531 int i, last;
532 unsigned int index;
534 #ifndef DESC_AVAIL_DUMMY
535 Descriptor *avail;
537 for (avail = desc_avail; avail; avail = avail->next)
538 g_assert_OR_PRINT (desc != avail, "descriptor is in the available list\n");
539 #endif
541 g_assert_OR_PRINT (desc->slot_size == desc->heap->sc->slot_size, "slot size doesn't match size class\n");
543 if (print)
544 g_print ("descriptor %p is ", desc);
546 switch (desc->anchor.data.state) {
547 case STATE_FULL:
548 if (print)
549 g_print ("full\n");
550 g_assert_OR_PRINT (count == 0, "count is not zero: %d\n", count);
551 break;
552 case STATE_PARTIAL:
553 if (print)
554 g_print ("partial\n");
555 g_assert_OR_PRINT (count < max_count, "count too high: is %d but must be below %d\n", count, max_count);
556 break;
557 case STATE_EMPTY:
558 if (print)
559 g_print ("empty\n");
560 g_assert_OR_PRINT (count == max_count, "count is wrong: is %d but should be %d\n", count, max_count);
561 break;
562 default:
563 g_assert_OR_PRINT (FALSE, "invalid state\n");
566 for (i = 0; i < max_count; ++i)
567 linked [i] = FALSE;
569 index = desc->anchor.data.avail;
570 last = -1;
571 for (i = 0; i < count; ++i) {
572 gpointer addr = (char*)desc->sb + index * desc->slot_size;
573 g_assert_OR_PRINT (index >= 0 && index < max_count,
574 "index %d for %dth available slot, linked from %d, not in range [0 .. %d)\n",
575 index, i, last, max_count);
576 g_assert_OR_PRINT (!linked [index], "%dth available slot %d linked twice\n", i, index);
577 if (linked [index])
578 break;
579 linked [index] = TRUE;
580 last = index;
581 index = *(unsigned int*)addr;
585 gboolean
586 mono_lock_free_allocator_check_consistency (MonoLockFreeAllocator *heap)
588 Descriptor *active = heap->active;
589 Descriptor *desc;
590 if (active) {
591 g_assert (active->anchor.data.state == STATE_PARTIAL);
592 descriptor_check_consistency (active, FALSE);
594 while ((desc = (Descriptor*)mono_lock_free_queue_dequeue (&heap->sc->partial))) {
595 g_assert (desc->anchor.data.state == STATE_PARTIAL || desc->anchor.data.state == STATE_EMPTY);
596 descriptor_check_consistency (desc, FALSE);
598 return TRUE;
601 void
602 mono_lock_free_allocator_init_size_class (MonoLockFreeAllocSizeClass *sc, unsigned int slot_size, unsigned int block_size)
604 g_assert (block_size > 0);
605 g_assert ((block_size & (block_size - 1)) == 0); /* check if power of 2 */
606 g_assert (slot_size * 2 <= LOCK_FREE_ALLOC_SB_USABLE_SIZE (block_size));
608 mono_lock_free_queue_init (&sc->partial);
609 sc->slot_size = slot_size;
610 sc->block_size = block_size;
613 void
614 mono_lock_free_allocator_init_allocator (MonoLockFreeAllocator *heap, MonoLockFreeAllocSizeClass *sc)
616 heap->sc = sc;
617 heap->active = NULL;