Merge pull request #2903 from krytarowski/netbsd-support-4
[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 volatile gpointer* dummy_global;
149
150 static MONO_NEVER_INLINE
151 void*
152 return_stack_ptr (gpointer *i)
153 {
154         dummy_global = i;
155         return i;
156 }
157
158 static void
159 copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
160 {
161         MonoThreadUnwindState *state;
162         int stackdata_size;
163         gpointer dummy;
164         void* stackdata_end = return_stack_ptr (&dummy);
165
166         SAVE_REGS_ON_STACK;
167
168         state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
169
170         stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
171
172         if (((gsize) stackdata_begin & (SIZEOF_VOID_P - 1)) != 0)
173                 g_error ("stackdata_begin (%p) must be %d-byte aligned", stackdata_begin, SIZEOF_VOID_P);
174         if (((gsize) stackdata_end & (SIZEOF_VOID_P - 1)) != 0)
175                 g_error ("stackdata_end (%p) must be %d-byte aligned", stackdata_end, SIZEOF_VOID_P);
176
177         if (stackdata_size <= 0)
178                 g_error ("stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", stackdata_size, stackdata_begin, stackdata_end);
179
180         g_byte_array_set_size (info->stackdata, stackdata_size);
181         state->gc_stackdata = info->stackdata->data;
182         memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
183
184         state->gc_stackdata_size = stackdata_size;
185 }
186
187 static gpointer
188 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata);
189
190 gpointer
191 mono_threads_enter_gc_safe_region (gpointer *stackdata)
192 {
193         return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
194 }
195
196 gpointer
197 mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo *info, gpointer *stackdata)
198 {
199         gpointer cookie;
200
201         if (!mono_threads_is_coop_enabled ())
202                 return NULL;
203
204         cookie = mono_threads_enter_gc_safe_region_unbalanced_with_info (info, stackdata);
205
206 #ifdef ENABLE_CHECKED_BUILD_GC
207         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
208                 coop_tls_push (cookie);
209 #endif
210
211         return cookie;
212 }
213
214 gpointer
215 mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackdata)
216 {
217         return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
218 }
219
220 static gpointer
221 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
222 {
223         if (!mono_threads_is_coop_enabled ())
224                 return NULL;
225
226         ++coop_do_blocking_count;
227
228         check_info (info, "enter", "safe");
229
230         copy_stack_data (info, stackdata);
231
232 retry:
233         ++coop_save_count;
234         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
235
236         switch (mono_threads_transition_do_blocking (info)) {
237         case DoBlockingContinue:
238                 break;
239         case DoBlockingPollAndRetry:
240                 mono_threads_state_poll_with_info (info);
241                 goto retry;
242         }
243
244         return info;
245 }
246
247 void
248 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
249 {
250         if (!mono_threads_is_coop_enabled ())
251                 return;
252
253 #ifdef ENABLE_CHECKED_BUILD_GC
254         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
255                 coop_tls_pop (cookie);
256 #endif
257
258         mono_threads_exit_gc_safe_region_unbalanced (cookie, stackdata);
259 }
260
261 void
262 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackdata)
263 {
264         MonoThreadInfo *info;
265
266         if (!mono_threads_is_coop_enabled ())
267                 return;
268
269         info = (MonoThreadInfo *)cookie;
270
271         check_info (info, "exit", "safe");
272
273         switch (mono_threads_transition_done_blocking (info)) {
274         case DoneBlockingOk:
275                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
276                 break;
277         case DoneBlockingWait:
278                 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
279                 mono_thread_info_wait_for_resume (info);
280                 break;
281         default:
282                 g_error ("Unknown thread state");
283         }
284 }
285
286 void
287 mono_threads_assert_gc_safe_region (void)
288 {
289         MONO_REQ_GC_SAFE_MODE;
290 }
291
292 gpointer
293 mono_threads_enter_gc_unsafe_region (gpointer *stackdata)
294 {
295         return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
296 }
297
298 gpointer
299 mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE *info, gpointer *stackdata)
300 {
301         gpointer cookie;
302
303         if (!mono_threads_is_coop_enabled ())
304                 return NULL;
305
306         cookie = mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info, stackdata);
307
308 #ifdef ENABLE_CHECKED_BUILD_GC
309         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
310                 coop_tls_push (cookie);
311 #endif
312
313         return cookie;
314 }
315
316 gpointer
317 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackdata)
318 {
319         return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
320 }
321
322 gpointer
323 mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
324 {
325         if (!mono_threads_is_coop_enabled ())
326                 return NULL;
327
328         ++coop_reset_blocking_count;
329
330         check_info (info, "enter", "unsafe");
331
332         copy_stack_data (info, stackdata);
333
334         switch (mono_threads_transition_abort_blocking (info)) {
335         case AbortBlockingIgnore:
336                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
337                 return NULL;
338         case AbortBlockingIgnoreAndPoll:
339                 mono_threads_state_poll_with_info (info);
340                 return NULL;
341         case AbortBlockingOk:
342                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
343                 break;
344         case AbortBlockingWait:
345                 mono_thread_info_wait_for_resume (info);
346                 break;
347         default:
348                 g_error ("Unknown thread state");
349         }
350
351         return info;
352 }
353
354 gpointer
355 mono_threads_enter_gc_unsafe_region_cookie (void)
356 {
357         MonoThreadInfo *info;
358
359         g_assert (mono_threads_is_coop_enabled ());
360
361         info = mono_thread_info_current_unchecked ();
362
363         check_info (info, "enter (cookie)", "unsafe");
364
365 #ifdef ENABLE_CHECKED_BUILD_GC
366         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
367                 coop_tls_push (info);
368 #endif
369
370         return info;
371 }
372
373 void
374 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackdata)
375 {
376         if (!mono_threads_is_coop_enabled ())
377                 return;
378
379 #ifdef ENABLE_CHECKED_BUILD_GC
380         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
381                 coop_tls_pop (cookie);
382 #endif
383
384         mono_threads_exit_gc_unsafe_region_unbalanced (cookie, stackdata);
385 }
386
387 void
388 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackdata)
389 {
390         if (!mono_threads_is_coop_enabled ())
391                 return;
392
393         if (!cookie)
394                 return;
395
396         mono_threads_enter_gc_safe_region_unbalanced (stackdata);
397 }
398
399 void
400 mono_threads_assert_gc_unsafe_region (void)
401 {
402         MONO_REQ_GC_UNSAFE_MODE;
403 }
404
405 gboolean
406 mono_threads_is_coop_enabled (void)
407 {
408 #if defined(USE_COOP_GC)
409         return TRUE;
410 #else
411         static int is_coop_enabled = -1;
412         if (G_UNLIKELY (is_coop_enabled == -1))
413                 is_coop_enabled = g_getenv ("MONO_ENABLE_COOP") != NULL ? 1 : 0;
414         return is_coop_enabled == 1;
415 #endif
416 }
417
418
419 void
420 mono_threads_coop_init (void)
421 {
422         if (!mono_threads_is_coop_enabled ())
423                 return;
424
425         mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
426         mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
427         mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
428         mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
429         mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
430         //See the above for what's wrong here.
431
432 #ifdef ENABLE_CHECKED_BUILD_GC
433         mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
434 #endif
435 }
436
437 void
438 mono_threads_coop_begin_global_suspend (void)
439 {
440         if (mono_threads_is_coop_enabled ())
441                 mono_polling_required = 1;
442 }
443
444 void
445 mono_threads_coop_end_global_suspend (void)
446 {
447         if (mono_threads_is_coop_enabled ())
448                 mono_polling_required = 0;
449 }