1 /**
2  * \file
3  * Coop threading
4  *
5  * Author:
6  *	Rodrigo Kumpera (kumpera@gmail.com)
7  *
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.
10  */
11 
12 #include <config.h>
13 
14 /* enable pthread extensions */
15 #ifdef TARGET_MACH
16 #define _DARWIN_C_SOURCE
17 #endif
18 
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>
32 
33 #ifdef TARGET_OSX
34 #include <mono/utils/mach-support.h>
35 #endif
36 
37 #ifdef _MSC_VER
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)
43 #else
44 #define SAVE_REGS_ON_STACK __builtin_unwind_init ();
45 #endif
46 
47 volatile size_t mono_polling_required;
48 
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
52 
53 // Maintains a single per-thread stack of ints, used to ensure nesting is not violated
54 static MonoNativeTlsKey coop_reset_count_stack_key;
55 
56 static void
coop_tls_push(gpointer cookie)57 coop_tls_push (gpointer cookie)
58 {
59 	GArray *stack;
60 
61 	stack = mono_native_tls_get_value (coop_reset_count_stack_key);
62 	if (!stack) {
63 		stack = g_array_new (FALSE, FALSE, sizeof(gpointer));
64 		mono_native_tls_set_value (coop_reset_count_stack_key, stack);
65 	}
66 
67 	g_array_append_val (stack, cookie);
68 }
69 
70 static void
coop_tls_pop(gpointer received_cookie)71 coop_tls_pop (gpointer received_cookie)
72 {
73 	GArray *stack;
74 	gpointer expected_cookie;
75 
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);
79 
80 	expected_cookie = g_array_index (stack, gpointer, stack->len - 1);
81 	stack->len --;
82 
83 	if (0 == stack->len) {
84 		g_array_free (stack,TRUE);
85 		mono_native_tls_set_value (coop_reset_count_stack_key, NULL);
86 	}
87 
88 	if (expected_cookie != received_cookie)
89 		mono_fatal_with_history ("Received cookie %p but expected %p\n", received_cookie, expected_cookie);
90 }
91 
92 #endif
93 
94 static void
check_info(MonoThreadInfo * info,const gchar * action,const gchar * state)95 check_info (MonoThreadInfo *info, const gchar *action, const gchar *state)
96 {
97 	if (!info)
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);
103 }
104 
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;
110 
111 static void
112 mono_threads_state_poll_with_info (MonoThreadInfo *info);
113 
114 void
mono_threads_state_poll(void)115 mono_threads_state_poll (void)
116 {
117 	mono_threads_state_poll_with_info (mono_thread_info_current_unchecked ());
118 }
119 
120 static void
mono_threads_state_poll_with_info(MonoThreadInfo * info)121 mono_threads_state_poll_with_info (MonoThreadInfo *info)
122 {
123 	g_assert (mono_threads_is_blocking_transition_enabled ());
124 
125 	++coop_do_polling_count;
126 
127 	if (!info)
128 		return;
129 
130 	THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", mono_thread_info_get_tid (info));
131 
132 	/* Fast check for pending suspend requests */
133 	if (!(info->thread_state & (STATE_ASYNC_SUSPEND_REQUESTED | STATE_SELF_SUSPEND_REQUESTED)))
134 		return;
135 
136 	++coop_save_count;
137 	mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
138 
139 	/* commit the saved state and notify others if needed */
140 	switch (mono_threads_transition_state_poll (info)) {
141 	case SelfSuspendResumed:
142 		break;
143 	case SelfSuspendWait:
144 		mono_thread_info_wait_for_resume (info);
145 		break;
146 	case SelfSuspendNotifyAndWait:
147 		mono_threads_notify_initiator_of_suspend (info);
148 		mono_thread_info_wait_for_resume (info);
149 		break;
150 	}
151 
152 	if (info->async_target) {
153 		info->async_target (info->user_data);
154 		info->async_target = NULL;
155 		info->user_data = NULL;
156 	}
157 }
158 
159 static volatile gpointer* dummy_global;
160 
161 static MONO_NEVER_INLINE
162 void*
return_stack_ptr(gpointer * i)163 return_stack_ptr (gpointer *i)
164 {
165 	dummy_global = i;
166 	return i;
167 }
168 
169 static void
copy_stack_data(MonoThreadInfo * info,gpointer * stackdata_begin)170 copy_stack_data (MonoThreadInfo *info, gpointer *stackdata_begin)
171 {
172 	MonoThreadUnwindState *state;
173 	int stackdata_size;
174 	gpointer dummy;
175 	void* stackdata_end = return_stack_ptr (&dummy);
176 
177 	SAVE_REGS_ON_STACK;
178 
179 	state = &info->thread_saved_state [SELF_SUSPEND_STATE_INDEX];
180 
181 	stackdata_size = (char*)stackdata_begin - (char*)stackdata_end;
182 
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);
187 
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);
190 
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);
194 
195 	state->gc_stackdata_size = stackdata_size;
196 }
197 
198 static gpointer
199 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata);
200 
201 gpointer
mono_threads_enter_gc_safe_region(gpointer * stackdata)202 mono_threads_enter_gc_safe_region (gpointer *stackdata)
203 {
204 	return mono_threads_enter_gc_safe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
205 }
206 
207 gpointer
mono_threads_enter_gc_safe_region_with_info(MonoThreadInfo * info,gpointer * stackdata)208 mono_threads_enter_gc_safe_region_with_info (MonoThreadInfo *info, gpointer *stackdata)
209 {
210 	gpointer cookie;
211 
212 	if (!mono_threads_is_blocking_transition_enabled ())
213 		return NULL;
214 
215 	cookie = mono_threads_enter_gc_safe_region_unbalanced_with_info (info, stackdata);
216 
217 #ifdef ENABLE_CHECKED_BUILD_GC
218 	if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
219 		coop_tls_push (cookie);
220 #endif
221 
222 	return cookie;
223 }
224 
225 gpointer
mono_threads_enter_gc_safe_region_unbalanced(gpointer * stackdata)226 mono_threads_enter_gc_safe_region_unbalanced (gpointer *stackdata)
227 {
228 	return mono_threads_enter_gc_safe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
229 }
230 
231 static gpointer
mono_threads_enter_gc_safe_region_unbalanced_with_info(MonoThreadInfo * info,gpointer * stackdata)232 mono_threads_enter_gc_safe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
233 {
234 	if (!mono_threads_is_blocking_transition_enabled ())
235 		return NULL;
236 
237 	++coop_do_blocking_count;
238 
239 	check_info (info, "enter", "safe");
240 
241 	copy_stack_data (info, stackdata);
242 
243 retry:
244 	++coop_save_count;
245 	mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
246 
247 	switch (mono_threads_transition_do_blocking (info)) {
248 	case DoBlockingContinue:
249 		break;
250 	case DoBlockingPollAndRetry:
251 		mono_threads_state_poll_with_info (info);
252 		goto retry;
253 	}
254 
255 	return info;
256 }
257 
258 void
mono_threads_exit_gc_safe_region(gpointer cookie,gpointer * stackdata)259 mono_threads_exit_gc_safe_region (gpointer cookie, gpointer *stackdata)
260 {
261 	if (!mono_threads_is_blocking_transition_enabled ())
262 		return;
263 
264 #ifdef ENABLE_CHECKED_BUILD_GC
265 	if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
266 		coop_tls_pop (cookie);
267 #endif
268 
269 	mono_threads_exit_gc_safe_region_unbalanced (cookie, stackdata);
270 }
271 
272 void
mono_threads_exit_gc_safe_region_unbalanced(gpointer cookie,gpointer * stackdata)273 mono_threads_exit_gc_safe_region_unbalanced (gpointer cookie, gpointer *stackdata)
274 {
275 	MonoThreadInfo *info;
276 
277 	if (!mono_threads_is_blocking_transition_enabled ())
278 		return;
279 
280 	info = (MonoThreadInfo *)cookie;
281 
282 	check_info (info, "exit", "safe");
283 
284 	switch (mono_threads_transition_done_blocking (info)) {
285 	case DoneBlockingOk:
286 		info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
287 		break;
288 	case DoneBlockingWait:
289 		THREADS_SUSPEND_DEBUG ("state polling done, notifying of resume\n");
290 		mono_thread_info_wait_for_resume (info);
291 		break;
292 	default:
293 		g_error ("Unknown thread state");
294 	}
295 
296 	if (info->async_target) {
297 		info->async_target (info->user_data);
298 		info->async_target = NULL;
299 		info->user_data = NULL;
300 	}
301 }
302 
303 void
mono_threads_assert_gc_safe_region(void)304 mono_threads_assert_gc_safe_region (void)
305 {
306 	MONO_REQ_GC_SAFE_MODE;
307 }
308 
309 gpointer
mono_threads_enter_gc_unsafe_region(gpointer * stackdata)310 mono_threads_enter_gc_unsafe_region (gpointer *stackdata)
311 {
312 	return mono_threads_enter_gc_unsafe_region_with_info (mono_thread_info_current_unchecked (), stackdata);
313 }
314 
315 gpointer
mono_threads_enter_gc_unsafe_region_with_info(THREAD_INFO_TYPE * info,gpointer * stackdata)316 mono_threads_enter_gc_unsafe_region_with_info (THREAD_INFO_TYPE *info, gpointer *stackdata)
317 {
318 	gpointer cookie;
319 
320 	if (!mono_threads_is_blocking_transition_enabled ())
321 		return NULL;
322 
323 	cookie = mono_threads_enter_gc_unsafe_region_unbalanced_with_info (info, stackdata);
324 
325 #ifdef ENABLE_CHECKED_BUILD_GC
326 	if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
327 		coop_tls_push (cookie);
328 #endif
329 
330 	return cookie;
331 }
332 
333 gpointer
mono_threads_enter_gc_unsafe_region_unbalanced(gpointer * stackdata)334 mono_threads_enter_gc_unsafe_region_unbalanced (gpointer *stackdata)
335 {
336 	return mono_threads_enter_gc_unsafe_region_unbalanced_with_info (mono_thread_info_current_unchecked (), stackdata);
337 }
338 
339 gpointer
mono_threads_enter_gc_unsafe_region_unbalanced_with_info(MonoThreadInfo * info,gpointer * stackdata)340 mono_threads_enter_gc_unsafe_region_unbalanced_with_info (MonoThreadInfo *info, gpointer *stackdata)
341 {
342 	if (!mono_threads_is_blocking_transition_enabled ())
343 		return NULL;
344 
345 	++coop_reset_blocking_count;
346 
347 	check_info (info, "enter", "unsafe");
348 
349 	copy_stack_data (info, stackdata);
350 
351 	switch (mono_threads_transition_abort_blocking (info)) {
352 	case AbortBlockingIgnore:
353 		info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
354 		return NULL;
355 	case AbortBlockingIgnoreAndPoll:
356 		mono_threads_state_poll_with_info (info);
357 		return NULL;
358 	case AbortBlockingOk:
359 		info->thread_saved_state [SELF_SUSPEND_STATE_INDEX].valid = FALSE;
360 		break;
361 	case AbortBlockingWait:
362 		mono_thread_info_wait_for_resume (info);
363 		break;
364 	default:
365 		g_error ("Unknown thread state");
366 	}
367 
368 	if (info->async_target) {
369 		info->async_target (info->user_data);
370 		info->async_target = NULL;
371 		info->user_data = NULL;
372 	}
373 
374 	return info;
375 }
376 
377 gpointer
mono_threads_enter_gc_unsafe_region_cookie(void)378 mono_threads_enter_gc_unsafe_region_cookie (void)
379 {
380 	MonoThreadInfo *info;
381 
382 	g_assert (mono_threads_is_blocking_transition_enabled ());
383 
384 	info = mono_thread_info_current_unchecked ();
385 
386 	check_info (info, "enter (cookie)", "unsafe");
387 
388 #ifdef ENABLE_CHECKED_BUILD_GC
389 	if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
390 		coop_tls_push (info);
391 #endif
392 
393 	return info;
394 }
395 
396 void
mono_threads_exit_gc_unsafe_region(gpointer cookie,gpointer * stackdata)397 mono_threads_exit_gc_unsafe_region (gpointer cookie, gpointer *stackdata)
398 {
399 	if (!mono_threads_is_blocking_transition_enabled ())
400 		return;
401 
402 #ifdef ENABLE_CHECKED_BUILD_GC
403 	if (mono_check_mode_enabled (MONO_CHECK_MODE_GC))
404 		coop_tls_pop (cookie);
405 #endif
406 
407 	mono_threads_exit_gc_unsafe_region_unbalanced (cookie, stackdata);
408 }
409 
410 void
mono_threads_exit_gc_unsafe_region_unbalanced(gpointer cookie,gpointer * stackdata)411 mono_threads_exit_gc_unsafe_region_unbalanced (gpointer cookie, gpointer *stackdata)
412 {
413 	if (!mono_threads_is_blocking_transition_enabled ())
414 		return;
415 
416 	if (!cookie)
417 		return;
418 
419 	mono_threads_enter_gc_safe_region_unbalanced (stackdata);
420 }
421 
422 void
mono_threads_assert_gc_unsafe_region(void)423 mono_threads_assert_gc_unsafe_region (void)
424 {
425 	MONO_REQ_GC_UNSAFE_MODE;
426 }
427 
428 gboolean
mono_threads_is_coop_enabled(void)429 mono_threads_is_coop_enabled (void)
430 {
431 #if defined(USE_COOP_GC)
432 	return TRUE;
433 #else
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;
438 #endif
439 }
440 
441 gboolean
mono_threads_is_blocking_transition_enabled(void)442 mono_threads_is_blocking_transition_enabled (void)
443 {
444 #if defined(USE_COOP_GC)
445 	return TRUE;
446 #else
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;
451 #endif
452 }
453 
454 
455 void
mono_threads_coop_init(void)456 mono_threads_coop_init (void)
457 {
458 	if (!mono_threads_is_coop_enabled () && !mono_threads_is_blocking_transition_enabled ())
459 		return;
460 
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.
467 
468 #ifdef ENABLE_CHECKED_BUILD_GC
469 	mono_native_tls_alloc (&coop_reset_count_stack_key, NULL);
470 #endif
471 }
472 
473 void
mono_threads_coop_begin_global_suspend(void)474 mono_threads_coop_begin_global_suspend (void)
475 {
476 	if (mono_threads_is_coop_enabled ())
477 		mono_polling_required = 1;
478 }
479 
480 void
mono_threads_coop_end_global_suspend(void)481 mono_threads_coop_end_global_suspend (void)
482 {
483 	if (mono_threads_is_coop_enabled ())
484 		mono_polling_required = 0;
485 }
486