6 * Rodrigo Kumpera (kumpera@gmail.com)
8 * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
9 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
14 /* enable pthread extensions */
16 #define _DARWIN_C_SOURCE
19 #include <mono/utils/mono-compiler.h>
20 #include <mono/utils/mono-threads.h>
21 #include <mono/utils/mono-tls.h>
22 #include <mono/utils/hazard-pointer.h>
23 #include <mono/utils/mono-memory-model.h>
24 #include <mono/utils/mono-mmap.h>
25 #include <mono/utils/atomic.h>
26 #include <mono/utils/mono-time.h>
27 #include <mono/utils/mono-counters.h>
28 #include <mono/utils/mono-threads-coop.h>
29 #include <mono/utils/mono-threads-api.h>
30 #include <mono/utils/checked-build.h>
31 #include <mono/utils/mono-threads-debug.h>
34 #include <mono/utils/mach-support.h>
38 // TODO: Find MSVC replacement for __builtin_unwind_init
39 #define SAVE_REGS_ON_STACK g_assert_not_reached ();
40 #elif defined (HOST_WASM)
41 //TODO: figure out wasm stack scanning
42 #define SAVE_REGS_ON_STACK do {} while (0)
44 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
47 volatile size_t mono_polling_required;
49 // FIXME: This would be more efficient if instead of instantiating the stack it just pushed a simple depth counter up and down,
50 // perhaps with a per-thread cookie in the high bits.
51 #ifdef ENABLE_CHECKED_BUILD_GC
53 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
54 static MonoNativeTlsKey coop_reset_count_stack_key;
57 coop_tls_push (gpointer cookie)
61 stack = mono_native_tls_get_value (coop_reset_count_stack_key);
63 stack = g_array_new (FALSE, FALSE, sizeof(gpointer));
64 mono_native_tls_set_value (coop_reset_count_stack_key, stack);
67 g_array_append_val (stack, cookie);
71 coop_tls_pop (gpointer received_cookie)
74 gpointer expected_cookie;
76 stack = mono_native_tls_get_value (coop_reset_count_stack_key);
77 if (!stack || 0 == stack->len)
78 mono_fatal_with_history ("Received cookie %p but found no stack at all\n", received_cookie);
80 expected_cookie = g_array_index (stack, gpointer, stack->len - 1);
83 if (0 == stack->len) {
84 g_array_free (stack,TRUE);
85 mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
88 if (expected_cookie != received_cookie)
89 mono_fatal_with_history ("Received cookie %p but expected %p\n", received_cookie, expected_cookie);
95 check_info (MonoThreadInfo *info, const gchar *action, const gchar *state)
98 g_error ("Cannot %s GC %s region if the thread is not attached", action, state);
99 if (!mono_thread_info_is_current (info))
100 g_error ("[%p] Cannot %s GC %s region on a different thread", mono_thread_info_get_tid (info), action, state);
101 if (!mono_thread_info_is_live (info))
102 g_error ("[%p] Cannot %s GC %s region if the thread is not live", mono_thread_info_get_tid (info), action, state);
105 static int coop_reset_blocking_count;
106 static int coop_try_blocking_count;
107 static int coop_do_blocking_count;
108 static int coop_do_polling_count;
109 static int coop_save_count;
112 mono_threads_state_poll_with_info (MonoThreadInfo *info);
115 mono_threads_state_poll (void)
117 mono_threads_state_poll_with_info (mono_thread_info_current_unchecked ());
121 mono_threads_state_poll_with_info (MonoThreadInfo *info)
123 g_assert (mono_threads_is_blocking_transition_enabled ());
125 ++coop_do_polling_count;
130 THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
132 /* Fast check for pending suspend requests */
133 if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
137 mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
139 /* commit the saved state and notify others if needed */
140 switch (mono_threads_transition_state_poll (info)) {
141 case SelfSuspendResumed:
143 case SelfSuspendWait:
144 mono_thread_info_wait_for_resume (info);
146 case SelfSuspendNotifyAndWait:
147 mono_threads_notify_initiator_of_suspend (info);
148 mono_thread_info_wait_for_resume (info);
152 if (info->async_target) {
153 info->async_target (info->user_data);
154 info->async_target = NULL;
155 info->user_data = NULL;
159 static volatile gpointer* dummy_global;
161 static MONO_NEVER_INLINE
163 return_stack_ptr (gpointer *i)
170 copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
172 MonoThreadUnwindState *state;
175 void* stackdata_end = return_stack_ptr (&dummy);
179 state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
181 stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
183 if (((gsize) stackdata_begin & (SIZEOF_VOID_P - 1)) != 0)
184 g_error ("stackdata_begin (%p) must be %d-byte aligned", stackdata_begin, SIZEOF_VOID_P);
185 if (((gsize) stackdata_end & (SIZEOF_VOID_P - 1)) != 0)
186 g_error ("stackdata_end (%p) must be %d-byte aligned", stackdata_end, SIZEOF_VOID_P);
188 if (stackdata_size <= 0)
189 g_error ("stackdata_size = %d, but must be > 0, stackdata_begin = %p, stackdata_end = %p", stackdata_size, stackdata_begin, stackdata_end);
191 g_byte_array_set_size (info->stackdata, stackdata_size);
192 state->gc_stackdata = info->stackdata->data;
193 memcpy (state->gc_stackdata, stackdata_end, stackdata_size);
195 state->gc_stackdata_size = stackdata_size;
199 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata);
202 mono_threads_enter_gc_safe_region (gpointer *stackdata)
204 return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
208 mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo *info, gpointer *stackdata)
212 if (!mono_threads_is_blocking_transition_enabled ())
215 cookie = mono_threads_enter_gc_safe_region_unbalanced_with_info (info, stackdata);
217 #ifdef ENABLE_CHECKED_BUILD_GC
218 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
219 coop_tls_push (cookie);
226 mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackdata)
228 return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
232 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
234 if (!mono_threads_is_blocking_transition_enabled ())
237 ++coop_do_blocking_count;
239 check_info (info, "enter", "safe");
241 copy_stack_data (info, stackdata);
245 mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
247 switch (mono_threads_transition_do_blocking (info)) {
248 case DoBlockingContinue:
250 case DoBlockingPollAndRetry:
251 mono_threads_state_poll_with_info (info);
259 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
261 if (!mono_threads_is_blocking_transition_enabled ())
264 #ifdef ENABLE_CHECKED_BUILD_GC
265 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
266 coop_tls_pop (cookie);
269 mono_threads_exit_gc_safe_region_unbalanced (cookie, stackdata);
273 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackdata)
275 MonoThreadInfo *info;
277 if (!mono_threads_is_blocking_transition_enabled ())
280 info = (MonoThreadInfo *)cookie;
282 check_info (info, "exit", "safe");
284 switch (mono_threads_transition_done_blocking (info)) {
286 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
288 case DoneBlockingWait:
289 THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
290 mono_thread_info_wait_for_resume (info);
293 g_error ("Unknown thread state");
296 if (info->async_target) {
297 info->async_target (info->user_data);
298 info->async_target = NULL;
299 info->user_data = NULL;
304 mono_threads_assert_gc_safe_region (void)
306 MONO_REQ_GC_SAFE_MODE;
310 mono_threads_enter_gc_unsafe_region (gpointer *stackdata)
312 return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
316 mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE *info, gpointer *stackdata)
320 if (!mono_threads_is_blocking_transition_enabled ())
323 cookie = mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info, stackdata);
325 #ifdef ENABLE_CHECKED_BUILD_GC
326 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
327 coop_tls_push (cookie);
334 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackdata)
336 return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
340 mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
342 if (!mono_threads_is_blocking_transition_enabled ())
345 ++coop_reset_blocking_count;
347 check_info (info, "enter", "unsafe");
349 copy_stack_data (info, stackdata);
351 switch (mono_threads_transition_abort_blocking (info)) {
352 case AbortBlockingIgnore:
353 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
355 case AbortBlockingIgnoreAndPoll:
356 mono_threads_state_poll_with_info (info);
358 case AbortBlockingOk:
359 info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
361 case AbortBlockingWait:
362 mono_thread_info_wait_for_resume (info);
365 g_error ("Unknown thread state");
368 if (info->async_target) {
369 info->async_target (info->user_data);
370 info->async_target = NULL;
371 info->user_data = NULL;
378 mono_threads_enter_gc_unsafe_region_cookie (void)
380 MonoThreadInfo *info;
382 g_assert (mono_threads_is_blocking_transition_enabled ());
384 info = mono_thread_info_current_unchecked ();
386 check_info (info, "enter (cookie)", "unsafe");
388 #ifdef ENABLE_CHECKED_BUILD_GC
389 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
390 coop_tls_push (info);
397 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackdata)
399 if (!mono_threads_is_blocking_transition_enabled ())
402 #ifdef ENABLE_CHECKED_BUILD_GC
403 if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
404 coop_tls_pop (cookie);
407 mono_threads_exit_gc_unsafe_region_unbalanced (cookie, stackdata);
411 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackdata)
413 if (!mono_threads_is_blocking_transition_enabled ())
419 mono_threads_enter_gc_safe_region_unbalanced (stackdata);
423 mono_threads_assert_gc_unsafe_region (void)
425 MONO_REQ_GC_UNSAFE_MODE;
429 mono_threads_is_coop_enabled (void)
431 #if defined(USE_COOP_GC)
434 static int is_coop_enabled = -1;
435 if (G_UNLIKELY (is_coop_enabled == -1))
436 is_coop_enabled = g_hasenv ("MONO_ENABLE_COOP") ? 1 : 0;
437 return is_coop_enabled == 1;
442 mono_threads_is_blocking_transition_enabled (void)
444 #if defined(USE_COOP_GC)
447 static int is_blocking_transition_enabled = -1;
448 if (G_UNLIKELY (is_blocking_transition_enabled == -1))
449 is_blocking_transition_enabled = (g_hasenv ("MONO_ENABLE_COOP") || g_hasenv ("MONO_ENABLE_BLOCKING_TRANSITION")) ? 1 : 0;
450 return is_blocking_transition_enabled == 1;
456 mono_threads_coop_init (void)
458 if (!mono_threads_is_coop_enabled () && !mono_threads_is_blocking_transition_enabled ())
461 mono_counters_register ("Coop Reset Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_reset_blocking_count);
462 mono_counters_register ("Coop Try Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_try_blocking_count);
463 mono_counters_register ("Coop Do Blocking", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_blocking_count);
464 mono_counters_register ("Coop Do Polling", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_do_polling_count);
465 mono_counters_register ("Coop Save Count", MONO_COUNTER_GC | MONO_COUNTER_INT, &coop_save_count);
466 //See the above for what's wrong here.
468 #ifdef ENABLE_CHECKED_BUILD_GC
469 mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
474 mono_threads_coop_begin_global_suspend (void)
476 if (mono_threads_is_coop_enabled ())
477 mono_polling_required = 1;
481 mono_threads_coop_end_global_suspend (void)
483 if (mono_threads_is_coop_enabled ())
484 mono_polling_required = 0;