Merge pull request #3040 from xmcclure/debugger-step-recursive
[mono.git] / mono / utils / mono-threads-coop.c
1  /*
2  * mono-threads.c: Coop threading
3  *
4  * Author:
5  *      Rodrigo Kumpera (kumpera@gmail.com)
6  *
7  * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
8  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
9  */
10
11 #include <config.h>
12
13 /* enable pthread extensions */
14 #ifdef TARGET_MACH
15 #define _DARWIN_C_SOURCE
16 #endif
17
18 #include <mono/utils/mono-compiler.h>
19 #include <mono/utils/mono-threads.h>
20 #include <mono/utils/mono-tls.h>
21 #include <mono/utils/hazard-pointer.h>
22 #include <mono/utils/mono-memory-model.h>
23 #include <mono/utils/mono-mmap.h>
24 #include <mono/utils/atomic.h>
25 #include <mono/utils/mono-time.h>
26 #include <mono/utils/mono-counters.h>
27 #include <mono/utils/mono-threads-coop.h>
28 #include <mono/utils/mono-threads-api.h>
29 #include <mono/utils/checked-build.h>
30
31 #ifdef TARGET_OSX
32 #include <mono/utils/mach-support.h>
33 #endif
34
35 #ifdef _MSC_VER
36 // TODO: Find MSVC replacement for __builtin_unwind_init
37 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
38 #else 
39 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
40 #endif
41
42 volatile size_t mono_polling_required;
43
44 // FIXME: This would be more efficient if instead of instantiating the stack it just pushed a simple depth counter up and down,
45 // perhaps with a per-thread cookie in the high bits.
46 #ifdef ENABLE_CHECKED_BUILD_GC
47
48 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
49 static MonoNativeTlsKey coop_reset_count_stack_key;
50
51 static void
52 coop_tls_push (gpointer cookie)
53 {
54         GArray *stack;
55
56         stack = mono_native_tls_get_value (coop_reset_count_stack_key);
57         if (!stack) {
58                 stack = g_array_new (FALSE, FALSE, sizeof(gpointer));
59                 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
60         }
61
62         g_array_append_val (stack, cookie);
63 }
64
65 static void
66 coop_tls_pop (gpointer received_cookie)
67 {
68         GArray *stack;
69         gpointer expected_cookie;
70
71         stack = mono_native_tls_get_value (coop_reset_count_stack_key);
72         if (!stack || 0 == stack->len)
73                 mono_fatal_with_history ("Received cookie %p but found no stack at all, %x\n", received_cookie);
74
75         expected_cookie = g_array_index (stack, gpointer, stack->len - 1);
76         stack->len --;
77
78         if (0 == stack->len) {
79                 g_array_free (stack,TRUE);
80                 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
81         }
82
83         if (expected_cookie != received_cookie)
84                 mono_fatal_with_history ("Received cookie %p but expected %p\n", received_cookie, expected_cookie);
85 }
86
87 #endif
88
89 static void
90 check_info (MonoThreadInfo *info, const gchar *action, const gchar *state)
91 {
92         if (!info)
93                 g_error ("Cannot %s GC %s region if the thread is not attached", action, state);
94         if (!mono_thread_info_is_current (info))
95                 g_error ("[%p] Cannot %s GC %s region on a different thread", mono_thread_info_get_tid (info), action, state);
96         if (!mono_thread_info_is_live (info))
97                 g_error ("[%p] Cannot %s GC %s region if the thread is not live", mono_thread_info_get_tid (info), action, state);
98 }
99
100 static int coop_reset_blocking_count;
101 static int coop_try_blocking_count;
102 static int coop_do_blocking_count;
103 static int coop_do_polling_count;
104 static int coop_save_count;
105
106 static void
107 mono_threads_state_poll_with_info (MonoThreadInfo *info);
108
109 void
110 mono_threads_state_poll (void)
111 {
112         mono_threads_state_poll_with_info (mono_thread_info_current_unchecked ());
113 }
114
115 static void
116 mono_threads_state_poll_with_info (MonoThreadInfo *info)
117 {
118         g_assert (mono_threads_is_coop_enabled ());
119
120         ++coop_do_polling_count;
121
122         if (!info)
123                 return;
124
125         THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
126
127         /* Fast check for pending suspend requests */
128         if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
129                 return;
130
131         ++coop_save_count;
132         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
133
134         /* commit the saved state and notify others if needed */
135         switch (mono_threads_transition_state_poll (info)) {
136         case SelfSuspendResumed:
137                 return;
138         case SelfSuspendWait:
139                 mono_thread_info_wait_for_resume (info);
140                 break;
141         case SelfSuspendNotifyAndWait:
142                 mono_threads_notify_initiator_of_suspend (info);
143                 mono_thread_info_wait_for_resume (info);
144                 break;
145         }
146 }
147
148 static void *
149 return_stack_ptr ()
150 {
151         gpointer i;
152         return &i;
153 }
154
155 static void
156 copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
157 {
158         MonoThreadUnwindState *state;
159         int stackdata_size;
160         void* stackdata_end = return_stack_ptr ();
161
162         SAVE_REGS_ON_STACK;
163
164         state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
165
166         stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
167
168         if (((gsize) stackdata_begin & (SIZEOF_VOID_P - 1)) != 0)
169                 g_error ("stackdata_begin (%p) must be %d-byte aligned", stackdata_begin, SIZEOF_VOID_P);
170         if (((gsize) stackdata_end & (SIZEOF_VOID_P - 1)) != 0)
171                 g_error ("stackdata_end (%p) must be %d-byte aligned", stackdata_end, SIZEOF_VOID_P);
172
173         if (stackdata_size <= 0)
174                 g_error ("stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", stackdata_size, stackdata_begin, stackdata_end);
175
176         g_byte_array_set_size (info->stackdata, stackdata_size);
177         state->gc_stackdata = info->stackdata->data;
178         memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
179
180         state->gc_stackdata_size = stackdata_size;
181 }
182
183 static gpointer
184 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata);
185
186 gpointer
187 mono_threads_enter_gc_safe_region (gpointer *stackdata)
188 {
189         return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
190 }
191
192 gpointer
193 mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo *info, gpointer *stackdata)
194 {
195         gpointer cookie;
196
197         if (!mono_threads_is_coop_enabled ())
198                 return NULL;
199
200         cookie = mono_threads_enter_gc_safe_region_unbalanced_with_info (info, stackdata);
201
202 #ifdef ENABLE_CHECKED_BUILD_GC
203         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
204                 coop_tls_push (cookie);
205 #endif
206
207         return cookie;
208 }
209
210 gpointer
211 mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackdata)
212 {
213         return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
214 }
215
216 static gpointer
217 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
218 {
219         if (!mono_threads_is_coop_enabled ())
220                 return NULL;
221
222         ++coop_do_blocking_count;
223
224         check_info (info, "enter", "safe");
225
226         copy_stack_data (info, stackdata);
227
228 retry:
229         ++coop_save_count;
230         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
231
232         switch (mono_threads_transition_do_blocking (info)) {
233         case DoBlockingContinue:
234                 break;
235         case DoBlockingPollAndRetry:
236                 mono_threads_state_poll_with_info (info);
237                 goto retry;
238         }
239
240         return info;
241 }
242
243 void
244 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
245 {
246         if (!mono_threads_is_coop_enabled ())
247                 return;
248
249 #ifdef ENABLE_CHECKED_BUILD_GC
250         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
251                 coop_tls_pop (cookie);
252 #endif
253
254         mono_threads_exit_gc_safe_region_unbalanced (cookie, stackdata);
255 }
256
257 void
258 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackdata)
259 {
260         MonoThreadInfo *info;
261
262         if (!mono_threads_is_coop_enabled ())
263                 return;
264
265         info = (MonoThreadInfo *)cookie;
266
267         check_info (info, "exit", "safe");
268
269         switch (mono_threads_transition_done_blocking (info)) {
270         case DoneBlockingOk:
271                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
272                 break;
273         case DoneBlockingWait:
274                 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
275                 mono_thread_info_wait_for_resume (info);
276                 break;
277         default:
278                 g_error ("Unknown thread state");
279         }
280 }
281
282 void
283 mono_threads_assert_gc_safe_region (void)
284 {
285         MONO_REQ_GC_SAFE_MODE;
286 }
287
288 gpointer
289 mono_threads_enter_gc_unsafe_region (gpointer *stackdata)
290 {
291         return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
292 }
293
294 gpointer
295 mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE *info, gpointer *stackdata)
296 {
297         gpointer cookie;
298
299         if (!mono_threads_is_coop_enabled ())
300                 return NULL;
301
302         cookie = mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info, stackdata);
303
304 #ifdef ENABLE_CHECKED_BUILD_GC
305         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
306                 coop_tls_push (cookie);
307 #endif
308
309         return cookie;
310 }
311
312 gpointer
313 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackdata)
314 {
315         return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
316 }
317
318 gpointer
319 mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
320 {
321         if (!mono_threads_is_coop_enabled ())
322                 return NULL;
323
324         ++coop_reset_blocking_count;
325
326         check_info (info, "enter", "unsafe");
327
328         copy_stack_data (info, stackdata);
329
330         switch (mono_threads_transition_abort_blocking (info)) {
331         case AbortBlockingIgnore:
332                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
333                 return NULL;
334         case AbortBlockingIgnoreAndPoll:
335                 mono_threads_state_poll_with_info (info);
336                 return NULL;
337         case AbortBlockingOk:
338                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
339                 break;
340         case AbortBlockingWait:
341                 mono_thread_info_wait_for_resume (info);
342                 break;
343         default:
344                 g_error ("Unknown thread state");
345         }
346
347         return info;
348 }
349
350 gpointer
351 mono_threads_enter_gc_unsafe_region_cookie (void)
352 {
353         MonoThreadInfo *info;
354
355         g_assert (mono_threads_is_coop_enabled ());
356
357         info = mono_thread_info_current_unchecked ();
358
359         check_info (info, "enter (cookie)", "unsafe");
360
361 #ifdef ENABLE_CHECKED_BUILD_GC
362         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
363                 coop_tls_push (info);
364 #endif
365
366         return info;
367 }
368
369 void
370 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackdata)
371 {
372         if (!mono_threads_is_coop_enabled ())
373                 return;
374
375 #ifdef ENABLE_CHECKED_BUILD_GC
376         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
377                 coop_tls_pop (cookie);
378 #endif
379
380         mono_threads_exit_gc_unsafe_region_unbalanced (cookie, stackdata);
381 }
382
383 void
384 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackdata)
385 {
386         if (!mono_threads_is_coop_enabled ())
387                 return;
388
389         if (!cookie)
390                 return;
391
392         mono_threads_enter_gc_safe_region_unbalanced (stackdata);
393 }
394
395 void
396 mono_threads_assert_gc_unsafe_region (void)
397 {
398         MONO_REQ_GC_UNSAFE_MODE;
399 }
400
401 gboolean
402 mono_threads_is_coop_enabled (void)
403 {
404 #if defined(USE_COOP_GC)
405         return TRUE;
406 #else
407         static int is_coop_enabled = -1;
408         if (G_UNLIKELY (is_coop_enabled == -1))
409                 is_coop_enabled = g_getenv ("MONO_ENABLE_COOP") != NULL ? 1 : 0;
410         return is_coop_enabled == 1;
411 #endif
412 }
413
414
415 void
416 mono_threads_init_coop (void)
417 {
418         if (!mono_threads_is_coop_enabled ())
419                 return;
420
421         mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
422         mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
423         mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
424         mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
425         mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
426         //See the above for what's wrong here.
427
428 #ifdef ENABLE_CHECKED_BUILD_GC
429         mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
430 #endif
431 }
432
433 void
434 mono_threads_coop_begin_global_suspend (void)
435 {
436         if (mono_threads_is_coop_enabled ())
437                 mono_polling_required = 1;
438 }
439
440 void
441 mono_threads_coop_end_global_suspend (void)
442 {
443         if (mono_threads_is_coop_enabled ())
444                 mono_polling_required = 0;
445 }