bb8f58c05904e77743be1c6bd7f45f598feb1d23
[mono.git] / mono / sgen / sgen-gray.c
1 /*
2  * sgen-gray.c: Gray queue management.
3  *
4  * Copyright 2001-2003 Ximian, Inc
5  * Copyright 2003-2010 Novell, Inc.
6  * Copyright (C) 2012 Xamarin Inc
7  *
8  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
9  */
10 #include "config.h"
11 #ifdef HAVE_SGEN_GC
12
13 #include "mono/sgen/sgen-gc.h"
14 #include "mono/sgen/sgen-protocol.h"
15
16 #ifdef HEAVY_STATISTICS
17 guint64 stat_gray_queue_section_alloc;
18 guint64 stat_gray_queue_section_free;
19 guint64 stat_gray_queue_enqueue_fast_path;
20 guint64 stat_gray_queue_dequeue_fast_path;
21 guint64 stat_gray_queue_enqueue_slow_path;
22 guint64 stat_gray_queue_dequeue_slow_path;
23 #endif
24
25 #define GRAY_QUEUE_LENGTH_LIMIT 64
26
27 #ifdef SGEN_CHECK_GRAY_OBJECT_SECTIONS
28 #define STATE_TRANSITION(s,o,n) do {                                    \
29                 int __old = (o);                                        \
30                 if (InterlockedCompareExchange ((volatile int*)&(s)->state, (n), __old) != __old) \
31                         g_assert_not_reached ();                        \
32         } while (0)
33 #define STATE_SET(s,v)          (s)->state = (v)
34 #define STATE_ASSERT(s,v)       g_assert ((s)->state == (v))
35 #else
36 #define STATE_TRANSITION(s,o,n)
37 #define STATE_SET(s,v)
38 #define STATE_ASSERT(s,v)
39 #endif
40
41 /*
42  * Whenever we dispose a gray queue, we save its free list.  Then, in the next collection,
43  * we reuse that free list for the new gray queue.
44  */
45 static GrayQueueSection *last_gray_queue_free_list;
46
47 void
48 sgen_gray_object_alloc_queue_section (SgenGrayQueue *queue)
49 {
50         GrayQueueSection *section;
51
52         if (queue->free_list) {
53                 /* Use the previously allocated queue sections if possible */
54                 section = queue->free_list;
55                 queue->free_list = section->next;
56                 STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_FREE_LIST, GRAY_QUEUE_SECTION_STATE_FLOATING);
57         } else {
58                 HEAVY_STAT (stat_gray_queue_section_alloc ++);
59
60                 /* Allocate a new section */
61                 section = (GrayQueueSection *)sgen_alloc_internal (INTERNAL_MEM_GRAY_QUEUE);
62                 STATE_SET (section, GRAY_QUEUE_SECTION_STATE_FLOATING);
63         }
64
65         /* Section is empty */
66         section->size = 0;
67
68         STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_FLOATING, GRAY_QUEUE_SECTION_STATE_ENQUEUED);
69
70         /* Link it with the others */
71         section->next = queue->first;
72         section->prev = NULL;
73         if (queue->first)
74                 queue->first->prev = section;
75         else
76                 queue->last = section;
77         queue->first = section;
78         queue->cursor = section->entries - 1;
79
80         mono_memory_write_barrier ();
81         /*
82          * FIXME
83          * we could probably optimize the code to only rely on the write barrier
84          * for synchronization with the stealer thread. Additionally we could also
85          * do a write barrier once every other gray queue change, and request
86          * to have a minimum of sections before stealing, to keep consistency.
87          */
88         InterlockedIncrement (&queue->num_sections);
89 }
90
91 void
92 sgen_gray_object_free_queue_section (GrayQueueSection *section)
93 {
94         HEAVY_STAT (stat_gray_queue_section_free ++);
95
96         STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_FLOATING, GRAY_QUEUE_SECTION_STATE_FREED);
97         sgen_free_internal (section, INTERNAL_MEM_GRAY_QUEUE);
98 }
99
100 /*
101  * The following two functions are called in the inner loops of the
102  * collector, so they need to be as fast as possible.  We have macros
103  * for them in sgen-gc.h.
104  */
105
106 void
107 sgen_gray_object_enqueue (SgenGrayQueue *queue, GCObject *obj, SgenDescriptor desc)
108 {
109         GrayQueueEntry entry = SGEN_GRAY_QUEUE_ENTRY (obj, desc);
110
111         HEAVY_STAT (stat_gray_queue_enqueue_slow_path ++);
112
113         SGEN_ASSERT (9, obj, "enqueueing a null object");
114         //sgen_check_objref (obj);
115
116 #ifdef SGEN_CHECK_GRAY_OBJECT_ENQUEUE
117         if (queue->enqueue_check_func)
118                 queue->enqueue_check_func (obj);
119 #endif
120
121         if (G_UNLIKELY (!queue->first || queue->cursor == GRAY_LAST_CURSOR_POSITION (queue->first))) {
122                 if (queue->first) {
123                         /*
124                          * We don't actively update the section size with each push/pop. For the first
125                          * section we determine the size from the cursor position. For the reset of the
126                          * sections we need to have the size set.
127                          */
128                         queue->first->size = SGEN_GRAY_QUEUE_SECTION_SIZE;
129                 }
130
131                 sgen_gray_object_alloc_queue_section (queue);
132         }
133         STATE_ASSERT (queue->first, GRAY_QUEUE_SECTION_STATE_ENQUEUED);
134         SGEN_ASSERT (9, queue->cursor <= GRAY_LAST_CURSOR_POSITION (queue->first), "gray queue %p overflow, first %p, cursor %p", queue, queue->first, queue->cursor);
135         *++queue->cursor = entry;
136
137 #ifdef SGEN_HEAVY_BINARY_PROTOCOL
138         binary_protocol_gray_enqueue (queue, queue->cursor, obj);
139 #endif
140 }
141
142 /*
143  * We attempt to spread the objects in the gray queue across a number
144  * of sections. If the queue has more sections, then it's already spread,
145  * if it doesn't have enough sections, then we allocate as many as we
146  * can.
147  */
148 void
149 sgen_gray_object_spread (SgenGrayQueue *queue, int num_sections)
150 {
151         GrayQueueSection *section_start, *section_end;
152         int total_entries = 0, num_entries_per_section;
153         int num_sections_final;
154
155         if (queue->num_sections >= num_sections)
156                 return;
157
158         if (!queue->first)
159                 return;
160
161         /* Compute number of elements in the gray queue */
162         queue->first->size = queue->cursor - queue->first->entries + 1;
163         total_entries = queue->first->size;
164         for (section_start = queue->first->next; section_start != NULL; section_start = section_start->next) {
165                 SGEN_ASSERT (0, section_start->size == SGEN_GRAY_QUEUE_SECTION_SIZE, "We expect all section aside from the first one to be full");
166                 total_entries += section_start->size;
167         }
168
169         /* Compute how many sections we should have and elements per section */
170         num_sections_final = (total_entries > num_sections) ? num_sections : total_entries;
171         num_entries_per_section = total_entries / num_sections_final;
172
173         /* Allocate all needed sections */
174         while (queue->num_sections < num_sections_final)
175                 sgen_gray_object_alloc_queue_section (queue);
176
177         /* Spread out the elements in the sections. By design, sections at the end are fuller. */
178         section_start = queue->first;
179         section_end = queue->last;
180         while (section_start != section_end) {
181                 /* We move entries from end to start, until they meet */
182                 while (section_start->size < num_entries_per_section) {
183                         GrayQueueEntry entry;
184                         if (section_end->size <= num_entries_per_section) {
185                                 section_end = section_end->prev;
186                                 if (section_end == section_start)
187                                         break;
188                         }
189                         if (section_end->size <= num_entries_per_section)
190                                 break;
191
192                         section_end->size--;
193                         entry = section_end->entries [section_end->size];
194                         section_start->entries [section_start->size] = entry;
195                         section_start->size++;
196                 }
197                 section_start = section_start->next;
198         }
199
200         queue->cursor = queue->first->entries + queue->first->size - 1;
201         queue->num_sections = num_sections_final;
202 }
203
204 GrayQueueEntry
205 sgen_gray_object_dequeue (SgenGrayQueue *queue)
206 {
207         GrayQueueEntry entry;
208
209         HEAVY_STAT (stat_gray_queue_dequeue_slow_path ++);
210
211         if (sgen_gray_object_queue_is_empty (queue)) {
212                 entry.obj = NULL;
213                 return entry;
214         }
215
216         STATE_ASSERT (queue->first, GRAY_QUEUE_SECTION_STATE_ENQUEUED);
217         SGEN_ASSERT (9, queue->cursor >= GRAY_FIRST_CURSOR_POSITION (queue->first), "gray queue %p underflow", queue);
218
219         entry = *queue->cursor--;
220
221 #ifdef SGEN_HEAVY_BINARY_PROTOCOL
222         binary_protocol_gray_dequeue (queue, queue->cursor + 1, entry.obj);
223 #endif
224
225         if (G_UNLIKELY (queue->cursor < GRAY_FIRST_CURSOR_POSITION (queue->first))) {
226                 GrayQueueSection *section;
227                 gint32 old_num_sections;
228
229                 old_num_sections = InterlockedDecrement (&queue->num_sections);
230
231                 if (old_num_sections <= 0) {
232                         mono_os_mutex_lock (&queue->steal_mutex);
233                 }
234
235                 section = queue->first;
236                 queue->first = section->next;
237                 if (queue->first) {
238                         queue->first->prev = NULL;
239                 } else {
240                         queue->last = NULL;
241                         SGEN_ASSERT (0, !old_num_sections, "Why do we have an inconsistent number of sections ?");
242                 }
243                 section->next = queue->free_list;
244
245                 STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_ENQUEUED, GRAY_QUEUE_SECTION_STATE_FREE_LIST);
246
247                 queue->free_list = section;
248                 queue->cursor = queue->first ? queue->first->entries + queue->first->size - 1 : NULL;
249
250                 if (old_num_sections <= 0) {
251                         mono_os_mutex_unlock (&queue->steal_mutex);
252                 }
253         }
254
255         return entry;
256 }
257
258 GrayQueueSection*
259 sgen_gray_object_dequeue_section (SgenGrayQueue *queue)
260 {
261         GrayQueueSection *section;
262
263         if (!queue->first)
264                 return NULL;
265
266         /* We never steal from this queue */
267         queue->num_sections--;
268
269         section = queue->first;
270         queue->first = section->next;
271         if (queue->first)
272                 queue->first->prev = NULL;
273         else
274                 queue->last = NULL;
275
276         section->next = NULL;
277         section->size = queue->cursor - section->entries + 1;
278
279         queue->cursor = queue->first ? queue->first->entries + queue->first->size - 1 : NULL;
280
281         STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_ENQUEUED, GRAY_QUEUE_SECTION_STATE_FLOATING);
282
283         return section;
284 }
285
286 GrayQueueSection*
287 sgen_gray_object_steal_section (SgenGrayQueue *queue)
288 {
289         gint32 sections_remaining;
290         GrayQueueSection *section = NULL;
291
292         /*
293          * With each push/pop into the queue we increment the number of sections.
294          * There is only one thread accessing the top (the owner) and potentially
295          * multiple workers trying to steal sections from the bottom, so we need
296          * to lock. A num sections decrement from the owner means that the first
297          * section is reserved, while a decrement by the stealer means that the
298          * last section is reserved. If after we decrement the num sections, we
299          * have at least one more section present, it means we can't race with
300          * the other thread. If this is not the case the steal end abandons the
301          * pop, setting back the num_sections, while the owner end will take a
302          * lock to make sure we are not racing with the stealer (since the stealer
303          * might have popped an entry and be in the process of updating the entry
304          * that the owner is trying to pop.
305          */
306
307         if (queue->num_sections <= 1)
308                 return NULL;
309
310         /* Give up if there is contention on the last section */
311         if (mono_os_mutex_trylock (&queue->steal_mutex) != 0)
312                 return NULL;
313
314         sections_remaining = InterlockedDecrement (&queue->num_sections);
315         if (sections_remaining <= 0) {
316                 /* The section that we tried to steal might be the head of the queue. */
317                 InterlockedIncrement (&queue->num_sections);
318         } else {
319                 /* We have reserved for us the tail section of the queue */
320                 section = queue->last;
321                 SGEN_ASSERT (0, section, "Why we don't have any sections to steal?");
322                 SGEN_ASSERT (0, !section->next, "Why aren't we stealing the tail?");
323                 queue->last = section->prev;
324                 section->prev = NULL;
325                 SGEN_ASSERT (0, queue->last, "Why are we stealing the last section?");
326                 queue->last->next = NULL;
327
328                 STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_ENQUEUED, GRAY_QUEUE_SECTION_STATE_FLOATING);
329         }
330
331         mono_os_mutex_unlock (&queue->steal_mutex);
332         return section;
333 }
334
335 void
336 sgen_gray_object_enqueue_section (SgenGrayQueue *queue, GrayQueueSection *section)
337 {
338         STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_FLOATING, GRAY_QUEUE_SECTION_STATE_ENQUEUED);
339
340         if (queue->first)
341                 queue->first->size = queue->cursor - queue->first->entries + 1;
342
343         section->next = queue->first;
344         section->prev = NULL;
345         if (queue->first)
346                 queue->first->prev = section;
347         else
348                 queue->last = section;
349         queue->first = section;
350         queue->cursor = queue->first->entries + queue->first->size - 1;
351 #ifdef SGEN_CHECK_GRAY_OBJECT_ENQUEUE
352         if (queue->enqueue_check_func) {
353                 int i;
354                 for (i = 0; i < section->size; ++i)
355                         queue->enqueue_check_func (section->entries [i].obj);
356         }
357 #endif
358         mono_memory_write_barrier ();
359         InterlockedIncrement (&queue->num_sections);
360 }
361
362 void
363 sgen_gray_object_queue_trim_free_list (SgenGrayQueue *queue)
364 {
365         GrayQueueSection *section, *next;
366         int i = 0;
367         for (section = queue->free_list; section && i < GRAY_QUEUE_LENGTH_LIMIT - 1; section = section->next) {
368                 STATE_ASSERT (section, GRAY_QUEUE_SECTION_STATE_FREE_LIST);
369                 i ++;
370         }
371         if (!section)
372                 return;
373         while (section->next) {
374                 next = section->next;
375                 section->next = next->next;
376                 STATE_TRANSITION (next, GRAY_QUEUE_SECTION_STATE_FREE_LIST, GRAY_QUEUE_SECTION_STATE_FLOATING);
377                 sgen_gray_object_free_queue_section (next);
378         }
379 }
380
381 void
382 sgen_gray_object_queue_init (SgenGrayQueue *queue, GrayQueueEnqueueCheckFunc enqueue_check_func, gboolean reuse_free_list)
383 {
384         memset (queue, 0, sizeof (SgenGrayQueue));
385
386 #ifdef SGEN_CHECK_GRAY_OBJECT_ENQUEUE
387         queue->enqueue_check_func = enqueue_check_func;
388 #endif
389
390         mono_os_mutex_init (&queue->steal_mutex);
391
392         if (reuse_free_list) {
393                 queue->free_list = last_gray_queue_free_list;
394                 last_gray_queue_free_list = NULL;
395         }
396 }
397
398 void
399 sgen_gray_object_queue_dispose (SgenGrayQueue *queue)
400 {
401         SGEN_ASSERT (0, sgen_gray_object_queue_is_empty (queue), "Why are we disposing a gray queue that's not empty?");
402
403         /* Free the extra sections allocated during the last collection */
404         sgen_gray_object_queue_trim_free_list (queue);
405
406         SGEN_ASSERT (0, !last_gray_queue_free_list, "Are we disposing two gray queues after another?");
407         last_gray_queue_free_list = queue->free_list;
408
409         /* just to make sure */
410         memset (queue, 0, sizeof (SgenGrayQueue));
411 }
412
413 void
414 sgen_gray_object_queue_deinit (SgenGrayQueue *queue)
415 {
416         g_assert (!queue->first);
417         while (queue->free_list) {
418                 GrayQueueSection *next = queue->free_list->next;
419                 STATE_TRANSITION (queue->free_list, GRAY_QUEUE_SECTION_STATE_FREE_LIST, GRAY_QUEUE_SECTION_STATE_FLOATING);
420                 sgen_gray_object_free_queue_section (queue->free_list);
421                 queue->free_list = next;
422         }
423 }
424
425 static void
426 lock_section_queue (SgenSectionGrayQueue *queue)
427 {
428         if (!queue->locked)
429                 return;
430
431         mono_os_mutex_lock (&queue->lock);
432 }
433
434 static void
435 unlock_section_queue (SgenSectionGrayQueue *queue)
436 {
437         if (!queue->locked)
438                 return;
439
440         mono_os_mutex_unlock (&queue->lock);
441 }
442
443 void
444 sgen_section_gray_queue_init (SgenSectionGrayQueue *queue, gboolean locked, GrayQueueEnqueueCheckFunc enqueue_check_func)
445 {
446         g_assert (sgen_section_gray_queue_is_empty (queue));
447
448         queue->locked = locked;
449         if (locked) {
450                 mono_os_mutex_init_recursive (&queue->lock);
451         }
452
453 #ifdef SGEN_CHECK_GRAY_OBJECT_ENQUEUE
454         queue->enqueue_check_func = enqueue_check_func;
455 #endif
456 }
457
458 gboolean
459 sgen_section_gray_queue_is_empty (SgenSectionGrayQueue *queue)
460 {
461         return !queue->first;
462 }
463
464 GrayQueueSection*
465 sgen_section_gray_queue_dequeue (SgenSectionGrayQueue *queue)
466 {
467         GrayQueueSection *section;
468
469         lock_section_queue (queue);
470
471         if (queue->first) {
472                 section = queue->first;
473                 queue->first = section->next;
474
475                 STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_ENQUEUED, GRAY_QUEUE_SECTION_STATE_FLOATING);
476
477                 section->next = NULL;
478         } else {
479                 section = NULL;
480         }
481
482         unlock_section_queue (queue);
483
484         return section;
485 }
486
487 void
488 sgen_section_gray_queue_enqueue (SgenSectionGrayQueue *queue, GrayQueueSection *section)
489 {
490         STATE_TRANSITION (section, GRAY_QUEUE_SECTION_STATE_FLOATING, GRAY_QUEUE_SECTION_STATE_ENQUEUED);
491
492         lock_section_queue (queue);
493
494         section->next = queue->first;
495         queue->first = section;
496 #ifdef SGEN_CHECK_GRAY_OBJECT_ENQUEUE
497         if (queue->enqueue_check_func) {
498                 int i;
499                 for (i = 0; i < section->size; ++i)
500                         queue->enqueue_check_func (section->entries [i].obj);
501         }
502 #endif
503
504         unlock_section_queue (queue);
505 }
506
507 void
508 sgen_init_gray_queues (void)
509 {
510 #ifdef HEAVY_STATISTICS
511         mono_counters_register ("Gray Queue alloc section", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gray_queue_section_alloc);
512         mono_counters_register ("Gray Queue free section", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gray_queue_section_free);
513         mono_counters_register ("Gray Queue enqueue fast path", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gray_queue_enqueue_fast_path);
514         mono_counters_register ("Gray Queue dequeue fast path", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gray_queue_dequeue_fast_path);
515         mono_counters_register ("Gray Queue enqueue slow path", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gray_queue_enqueue_slow_path);
516         mono_counters_register ("Gray Queue dequeue slow path", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gray_queue_dequeue_slow_path);
517 #endif
518 }
519 #endif