Merge pull request #1949 from lewurm/fixtype
[mono.git] / mono / sgen / sgen-workers.c
1 /*
2  * sgen-workers.c: Worker threads for parallel and concurrent GC.
3  *
4  * Copyright 2001-2003 Ximian, Inc
5  * Copyright 2003-2010 Novell, Inc.
6  * Copyright (C) 2012 Xamarin Inc
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License 2.0 as published by the Free Software Foundation;
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License 2.0 along with this library; if not, write to the Free
19  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "config.h"
23 #ifdef HAVE_SGEN_GC
24
25 #include <string.h>
26
27 #include "mono/sgen/sgen-gc.h"
28 #include "mono/sgen/sgen-workers.h"
29 #include "mono/sgen/sgen-thread-pool.h"
30 #include "mono/utils/mono-membar.h"
31 #include "mono/sgen/sgen-client.h"
32
33 static int workers_num;
34 static WorkerData *workers_data;
35
36 static SgenSectionGrayQueue workers_distribute_gray_queue;
37 static gboolean workers_distribute_gray_queue_inited;
38
39 /*
40  * Allowed transitions:
41  *
42  * | from \ to          | NOT WORKING | WORKING | WORK ENQUEUED |
43  * |--------------------+-------------+---------+---------------+
44  * | NOT WORKING        | -           | -       | main          |
45  * | WORKING            | worker      | -       | main          |
46  * | WORK ENQUEUED      | -           | worker  | -             |
47  *
48  * The WORK ENQUEUED state guarantees that the worker thread will inspect the queue again at
49  * least once.  Only after looking at the queue will it go back to WORKING, and then,
50  * eventually, to NOT WORKING.  After enqueuing work the main thread transitions the state
51  * to WORK ENQUEUED.  Signalling the worker thread to wake up is only necessary if the old
52  * state was NOT WORKING.
53  */
54
55 enum {
56         STATE_NOT_WORKING,
57         STATE_WORKING,
58         STATE_WORK_ENQUEUED
59 };
60
61 typedef gint32 State;
62
63 static volatile State workers_state;
64
65 static SgenObjectOperations * volatile idle_func_object_ops;
66
67 static guint64 stat_workers_num_finished;
68
69 static gboolean
70 set_state (State old_state, State new_state)
71 {
72         SGEN_ASSERT (0, old_state != new_state, "Why are we transitioning to the same state?");
73         if (new_state == STATE_NOT_WORKING)
74                 SGEN_ASSERT (0, old_state == STATE_WORKING, "We can only transition to NOT WORKING from WORKING");
75         else if (new_state == STATE_WORKING)
76                 SGEN_ASSERT (0, old_state == STATE_WORK_ENQUEUED, "We can only transition to WORKING from WORK ENQUEUED");
77         if (new_state == STATE_NOT_WORKING || new_state == STATE_WORKING)
78                 SGEN_ASSERT (6, sgen_thread_pool_is_thread_pool_thread (mono_native_thread_id_get ()), "Only the worker thread is allowed to transition to NOT_WORKING or WORKING");
79
80         return InterlockedCompareExchange (&workers_state, new_state, old_state) == old_state;
81 }
82
83 static gboolean
84 state_is_working_or_enqueued (State state)
85 {
86         return state == STATE_WORKING || state == STATE_WORK_ENQUEUED;
87 }
88
89 void
90 sgen_workers_ensure_awake (void)
91 {
92         State old_state;
93         gboolean did_set_state;
94
95         do {
96                 old_state = workers_state;
97
98                 if (old_state == STATE_WORK_ENQUEUED)
99                         break;
100
101                 did_set_state = set_state (old_state, STATE_WORK_ENQUEUED);
102         } while (!did_set_state);
103
104         if (!state_is_working_or_enqueued (old_state))
105                 sgen_thread_pool_idle_signal ();
106 }
107
108 static void
109 worker_try_finish (void)
110 {
111         State old_state;
112
113         ++stat_workers_num_finished;
114
115         do {
116                 old_state = workers_state;
117
118                 SGEN_ASSERT (0, old_state != STATE_NOT_WORKING, "How did we get from doing idle work to NOT WORKING without setting it ourselves?");
119                 if (old_state == STATE_WORK_ENQUEUED)
120                         return;
121                 SGEN_ASSERT (0, old_state == STATE_WORKING, "What other possibility is there?");
122
123                 /* We are the last thread to go to sleep. */
124         } while (!set_state (old_state, STATE_NOT_WORKING));
125 }
126
127 static gboolean
128 collection_needs_workers (void)
129 {
130         return sgen_collection_is_concurrent ();
131 }
132
133 void
134 sgen_workers_enqueue_job (SgenThreadPoolJob *job)
135 {
136         if (!collection_needs_workers ()) {
137                 job->func (NULL, job);
138                 sgen_thread_pool_job_free (job);
139                 return;
140         }
141
142         sgen_thread_pool_job_enqueue (job);
143 }
144
145 void
146 sgen_workers_wait_for_jobs_finished (void)
147 {
148         sgen_thread_pool_wait_for_all_jobs ();
149         /*
150          * If the idle task was never triggered or it finished before the last job did and
151          * then didn't get triggered again, we might end up in the situation of having
152          * something in the gray queue yet the idle task not working.  The easiest way to
153          * make sure this doesn't stay that way is to just trigger it again after all jobs
154          * have finished.
155          */
156         sgen_workers_ensure_awake ();
157 }
158
159 static gboolean
160 workers_get_work (WorkerData *data)
161 {
162         SgenMajorCollector *major;
163
164         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
165
166         /* If we're concurrent, steal from the workers distribute gray queue. */
167         major = sgen_get_major_collector ();
168         if (major->is_concurrent) {
169                 GrayQueueSection *section = sgen_section_gray_queue_dequeue (&workers_distribute_gray_queue);
170                 if (section) {
171                         sgen_gray_object_enqueue_section (&data->private_gray_queue, section);
172                         return TRUE;
173                 }
174         }
175
176         /* Nobody to steal from */
177         g_assert (sgen_gray_object_queue_is_empty (&data->private_gray_queue));
178         return FALSE;
179 }
180
181 static void
182 concurrent_enqueue_check (GCObject *obj)
183 {
184         g_assert (sgen_concurrent_collection_in_progress ());
185         g_assert (!sgen_ptr_in_nursery (obj));
186         g_assert (SGEN_LOAD_VTABLE (obj));
187 }
188
189 static void
190 init_private_gray_queue (WorkerData *data)
191 {
192         sgen_gray_object_queue_init (&data->private_gray_queue,
193                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
194 }
195
196 static void
197 thread_pool_init_func (void *data_untyped)
198 {
199         WorkerData *data = data_untyped;
200         SgenMajorCollector *major = sgen_get_major_collector ();
201
202         sgen_client_thread_register_worker ();
203
204         if (!major->is_concurrent)
205                 return;
206
207         init_private_gray_queue (data);
208 }
209
210 static gboolean
211 continue_idle_func (void)
212 {
213         return state_is_working_or_enqueued (workers_state);
214 }
215
216 static void
217 marker_idle_func (void *data_untyped)
218 {
219         WorkerData *data = data_untyped;
220
221         SGEN_ASSERT (0, continue_idle_func (), "Why are we called when we're not supposed to work?");
222         SGEN_ASSERT (0, sgen_concurrent_collection_in_progress (), "The worker should only mark in concurrent collections.");
223
224         if (workers_state == STATE_WORK_ENQUEUED) {
225                 set_state (STATE_WORK_ENQUEUED, STATE_WORKING);
226                 SGEN_ASSERT (0, workers_state != STATE_NOT_WORKING, "How did we get from WORK ENQUEUED to NOT WORKING?");
227         }
228
229         if (!sgen_gray_object_queue_is_empty (&data->private_gray_queue) || workers_get_work (data)) {
230                 ScanCopyContext ctx = CONTEXT_FROM_OBJECT_OPERATIONS (idle_func_object_ops, &data->private_gray_queue);
231
232                 SGEN_ASSERT (0, !sgen_gray_object_queue_is_empty (&data->private_gray_queue), "How is our gray queue empty if we just got work?");
233
234                 sgen_drain_gray_stack (32, ctx);
235         } else {
236                 worker_try_finish ();
237         }
238 }
239
240 static void
241 init_distribute_gray_queue (void)
242 {
243         if (workers_distribute_gray_queue_inited) {
244                 g_assert (sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue));
245                 g_assert (workers_distribute_gray_queue.locked);
246                 return;
247         }
248
249         sgen_section_gray_queue_init (&workers_distribute_gray_queue, TRUE,
250                         sgen_get_major_collector ()->is_concurrent ? concurrent_enqueue_check : NULL);
251         workers_distribute_gray_queue_inited = TRUE;
252 }
253
254 void
255 sgen_workers_init_distribute_gray_queue (void)
256 {
257         SGEN_ASSERT (0, sgen_get_major_collector ()->is_concurrent && collection_needs_workers (),
258                         "Why should we init the distribute gray queue if we don't need it?");
259         init_distribute_gray_queue ();
260 }
261
262 void
263 sgen_workers_init (int num_workers)
264 {
265         int i;
266         void **workers_data_ptrs = alloca(num_workers * sizeof(void *));
267
268         if (!sgen_get_major_collector ()->is_concurrent) {
269                 sgen_thread_pool_init (num_workers, thread_pool_init_func, NULL, NULL, NULL);
270                 return;
271         }
272
273         //g_print ("initing %d workers\n", num_workers);
274
275         workers_num = num_workers;
276
277         workers_data = sgen_alloc_internal_dynamic (sizeof (WorkerData) * num_workers, INTERNAL_MEM_WORKER_DATA, TRUE);
278         memset (workers_data, 0, sizeof (WorkerData) * num_workers);
279
280         init_distribute_gray_queue ();
281
282         for (i = 0; i < workers_num; ++i)
283                 workers_data_ptrs [i] = (void *) &workers_data [i];
284
285         sgen_thread_pool_init (num_workers, thread_pool_init_func, marker_idle_func, continue_idle_func, workers_data_ptrs);
286
287         mono_counters_register ("# workers finished", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_workers_num_finished);
288 }
289
290 void
291 sgen_workers_start_all_workers (SgenObjectOperations *object_ops)
292 {
293         if (!collection_needs_workers ())
294                 return;
295
296         idle_func_object_ops = object_ops;
297         mono_memory_write_barrier ();
298
299         sgen_workers_ensure_awake ();
300 }
301
302 void
303 sgen_workers_join (void)
304 {
305         int i;
306
307         if (!collection_needs_workers ())
308                 return;
309
310         sgen_thread_pool_wait_for_all_jobs ();
311         sgen_thread_pool_idle_wait ();
312         SGEN_ASSERT (0, workers_state == STATE_NOT_WORKING, "Can only signal enqueue work when in no work state");
313
314         /* At this point all the workers have stopped. */
315
316         SGEN_ASSERT (0, sgen_section_gray_queue_is_empty (&workers_distribute_gray_queue), "Why is there still work left to do?");
317         for (i = 0; i < workers_num; ++i)
318                 SGEN_ASSERT (0, sgen_gray_object_queue_is_empty (&workers_data [i].private_gray_queue), "Why is there still work left to do?");
319 }
320
321 gboolean
322 sgen_workers_all_done (void)
323 {
324         return workers_state == STATE_NOT_WORKING;
325 }
326
327 /* Must only be used for debugging */
328 gboolean
329 sgen_workers_are_working (void)
330 {
331         return state_is_working_or_enqueued (workers_state);
332 }
333
334 void
335 sgen_workers_wait (void)
336 {
337         sgen_thread_pool_idle_wait ();
338         SGEN_ASSERT (0, sgen_workers_all_done (), "Why are the workers not done after we wait for them?");
339 }
340
341 SgenSectionGrayQueue*
342 sgen_workers_get_distribute_section_gray_queue (void)
343 {
344         return &workers_distribute_gray_queue;
345 }
346
347 #endif