[coop] Do not pass current thread to mono_threads_enter_gc_unsafe_region_cookie
[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 void
107 mono_threads_state_poll (void)
108 {
109         MonoThreadInfo *info;
110
111         g_assert (mono_threads_is_coop_enabled ());
112
113         ++coop_do_polling_count;
114
115         info = mono_thread_info_current_unchecked ();
116         if (!info)
117                 return;
118         THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
119
120         /* Fast check for pending suspend requests */
121         if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
122                 return;
123
124         ++coop_save_count;
125         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
126
127         /* commit the saved state and notify others if needed */
128         switch (mono_threads_transition_state_poll (info)) {
129         case SelfSuspendResumed:
130                 return;
131         case SelfSuspendWait:
132                 mono_thread_info_wait_for_resume (info);
133                 break;
134         case SelfSuspendNotifyAndWait:
135                 mono_threads_notify_initiator_of_suspend (info);
136                 mono_thread_info_wait_for_resume (info);
137                 break;
138         }
139 }
140
141 static void *
142 return_stack_ptr ()
143 {
144         gpointer i;
145         return &i;
146 }
147
148 static void
149 copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
150 {
151         MonoThreadUnwindState *state;
152         int stackdata_size;
153         void* stackdata_end = return_stack_ptr ();
154
155         SAVE_REGS_ON_STACK;
156
157         state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
158
159         stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
160
161         if (((gsize) stackdata_begin & (SIZEOF_VOID_P - 1)) != 0)
162                 g_error ("stackdata_begin (%p) must be %d-byte aligned", stackdata_begin, SIZEOF_VOID_P);
163         if (((gsize) stackdata_end & (SIZEOF_VOID_P - 1)) != 0)
164                 g_error ("stackdata_end (%p) must be %d-byte aligned", stackdata_end, SIZEOF_VOID_P);
165
166         if (stackdata_size <= 0)
167                 g_error ("stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", stackdata_size, stackdata_begin, stackdata_end);
168
169         g_byte_array_set_size (info->stackdata, stackdata_size);
170         state->gc_stackdata = info->stackdata->data;
171         memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
172
173         state->gc_stackdata_size = stackdata_size;
174 }
175
176 static gpointer
177 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata);
178
179 gpointer
180 mono_threads_enter_gc_safe_region (gpointer *stackdata)
181 {
182         return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
183 }
184
185 gpointer
186 mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo *info, gpointer *stackdata)
187 {
188         gpointer cookie;
189
190         if (!mono_threads_is_coop_enabled ())
191                 return NULL;
192
193         cookie = mono_threads_enter_gc_safe_region_unbalanced_with_info (info, stackdata);
194
195 #ifdef ENABLE_CHECKED_BUILD_GC
196         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
197                 coop_tls_push (cookie);
198 #endif
199
200         return cookie;
201 }
202
203 gpointer
204 mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackdata)
205 {
206         return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
207 }
208
209 static gpointer
210 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
211 {
212         if (!mono_threads_is_coop_enabled ())
213                 return NULL;
214
215         ++coop_do_blocking_count;
216
217         check_info (info, "enter", "safe");
218
219         copy_stack_data (info, stackdata);
220
221 retry:
222         ++coop_save_count;
223         mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
224
225         switch (mono_threads_transition_do_blocking (info)) {
226         case DoBlockingContinue:
227                 break;
228         case DoBlockingPollAndRetry:
229                 mono_threads_state_poll ();
230                 goto retry;
231         }
232
233         return info;
234 }
235
236 void
237 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
238 {
239         if (!mono_threads_is_coop_enabled ())
240                 return;
241
242 #ifdef ENABLE_CHECKED_BUILD_GC
243         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
244                 coop_tls_pop (cookie);
245 #endif
246
247         mono_threads_exit_gc_safe_region_unbalanced (cookie, stackdata);
248 }
249
250 void
251 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackdata)
252 {
253         MonoThreadInfo *info;
254
255         if (!mono_threads_is_coop_enabled ())
256                 return;
257
258         info = (MonoThreadInfo *)cookie;
259
260         check_info (info, "exit", "safe");
261
262         switch (mono_threads_transition_done_blocking (info)) {
263         case DoneBlockingOk:
264                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
265                 break;
266         case DoneBlockingWait:
267                 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
268                 mono_thread_info_wait_for_resume (info);
269                 break;
270         default:
271                 g_error ("Unknown thread state");
272         }
273 }
274
275 void
276 mono_threads_assert_gc_safe_region (void)
277 {
278         MONO_REQ_GC_SAFE_MODE;
279 }
280
281 gpointer
282 mono_threads_enter_gc_unsafe_region (gpointer *stackdata)
283 {
284         return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
285 }
286
287 gpointer
288 mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE *info, gpointer *stackdata)
289 {
290         gpointer cookie;
291
292         if (!mono_threads_is_coop_enabled ())
293                 return NULL;
294
295         cookie = mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info, stackdata);
296
297 #ifdef ENABLE_CHECKED_BUILD_GC
298         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
299                 coop_tls_push (cookie);
300 #endif
301
302         return cookie;
303 }
304
305 gpointer
306 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackdata)
307 {
308         return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
309 }
310
311 gpointer
312 mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
313 {
314         if (!mono_threads_is_coop_enabled ())
315                 return NULL;
316
317         ++coop_reset_blocking_count;
318
319         check_info (info, "enter", "unsafe");
320
321         copy_stack_data (info, stackdata);
322
323         switch (mono_threads_transition_abort_blocking (info)) {
324         case AbortBlockingIgnore:
325                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
326                 return NULL;
327         case AbortBlockingIgnoreAndPoll:
328                 mono_threads_state_poll ();
329                 return NULL;
330         case AbortBlockingOk:
331                 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
332                 break;
333         case AbortBlockingWait:
334                 mono_thread_info_wait_for_resume (info);
335                 break;
336         default:
337                 g_error ("Unknown thread state");
338         }
339
340         return info;
341 }
342
343 gpointer
344 mono_threads_enter_gc_unsafe_region_cookie (void)
345 {
346         MonoThreadInfo *info;
347
348         g_assert (mono_threads_is_coop_enabled ());
349
350         info = mono_thread_info_current_unchecked ();
351
352         check_info (info, "enter (cookie)", "unsafe");
353
354 #ifdef ENABLE_CHECKED_BUILD_GC
355         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
356                 coop_tls_push (info);
357 #endif
358
359         return info;
360 }
361
362 void
363 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackdata)
364 {
365         if (!mono_threads_is_coop_enabled ())
366                 return;
367
368 #ifdef ENABLE_CHECKED_BUILD_GC
369         if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
370                 coop_tls_pop (cookie);
371 #endif
372
373         mono_threads_exit_gc_unsafe_region_unbalanced (cookie, stackdata);
374 }
375
376 void
377 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackdata)
378 {
379         if (!mono_threads_is_coop_enabled ())
380                 return;
381
382         if (!cookie)
383                 return;
384
385 #ifdef ENABLE_CHECKED_BUILD_GC
386         if (!mono_check_mode_enabled (MONO_CHECK_MODE_GC))
387 #endif
388         {
389                 g_assert (((MonoThreadInfo *)cookie) == mono_thread_info_current_unchecked ());
390         }
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 }