1 /*
2 * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
3 * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
4 * Copyright (c) 1998 by Fergus Henderson. All rights reserved.
5 * Copyright (c) 2000-2009 by Hewlett-Packard Development Company.
6 * All rights reserved.
7 * Copyright (c) 2009-2022 Ivan Maidanski
8 *
9 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
10 * OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
11 *
12 * Permission is hereby granted to use or copy this program
13 * for any purpose, provided the above notices are retained on all copies.
14 * Permission to modify the code and to distribute modified code is granted,
15 * provided the above notices are retained, and a notice that the code was
16 * modified is included with the above copyright notice.
17 */
18 19 /* Private declarations for thread support. */
20 21 #ifndef GC_PTHREAD_SUPPORT_H
22 #define GC_PTHREAD_SUPPORT_H
23 24 #include "gc_priv.h"
25 26 #ifdef THREADS
27 28 # if defined(GC_PTHREADS) || defined(GC_PTHREADS_PARAMARK)
29 # include <pthread.h>
30 # endif
31 32 # ifdef DARWIN
33 # include <mach/mach.h>
34 # include <mach/thread_act.h>
35 # endif
36 37 # ifdef THREAD_LOCAL_ALLOC
38 # include "thread_local_alloc.h"
39 # endif
40 41 # ifdef THREAD_SANITIZER
42 # include "dbg_mlc.h" /*< for `oh` type */
43 # endif
44 45 EXTERN_C_BEGIN
46 47 # ifdef WOW64_THREAD_CONTEXT_WORKAROUND
48 # if defined(__WATCOMC__) && defined(GC_WIN32_THREADS)
49 /*
50 * Open Watcom (as of v2 2024-11-02 Build) does not define `NT_TIB`
51 * in the platform `winnt.h` file.
52 */
53 struct GC_NT_TIB_s {
54 /* `EXCEPTION_REGISTRATION_RECORD *` */ void *ExceptionList; /*< unused */
55 PVOID StackBase;
56 PVOID StackLimit;
57 /* The remaining fields are unused. */
58 };
59 typedef struct GC_NT_TIB_s GC_NT_TIB;
60 # else
61 # define GC_NT_TIB NT_TIB
62 # endif
63 # endif
64 65 struct GC_StackContext_Rep {
66 # if defined(THREAD_SANITIZER) && defined(SIGNAL_BASED_STOP_WORLD)
67 /*
68 * A dummy field to avoid TSan false positive about the race
69 * between `GC_has_other_debug_info` and `GC_suspend_handler_inner`
70 * (the latter one sets `stack_ptr`).
71 */
72 char dummy[sizeof(oh)];
73 # endif
74 75 /*
76 * Cold end of the stack (except for main thread on non-Windows).
77 * On Windows: 0 means entry invalid; not `in_use` implies `stack_end`
78 * is zero.
79 */
80 # if !defined(GC_NO_THREADS_DISCOVERY) && defined(GC_WIN32_THREADS)
81 volatile
82 # endif
83 ptr_t stack_end;
84 85 /* Valid only in some platform-specific states. */
86 ptr_t stack_ptr;
87 88 # ifdef GC_WIN32_THREADS
89 # define ADDR_LIMIT ((ptr_t)GC_WORD_MAX)
90 /*
91 * Last known minimum (hottest) address in stack or `ADDR_LIMIT`
92 * if unset.
93 */
94 ptr_t last_stack_min;
95 # ifdef I386
96 /*
97 * The cold end of the stack saved by `GC_record_stack_base` (never
98 * modified by `GC_set_stackbottom()`); used for the old way of the
99 * coroutines support.
100 */
101 ptr_t initial_stack_base;
102 # endif
103 # elif defined(DARWIN) && !defined(DARWIN_DONT_PARSE_STACK)
104 /*
105 * Result of `GC_FindTopOfStack(0)`; valid only if the thread is blocked;
106 * non-`NULL` value means already set.
107 */
108 ptr_t topOfStack;
109 # endif
110 111 # if defined(E2K) || defined(IA64)
112 ptr_t backing_store_end;
113 ptr_t backing_store_ptr;
114 # endif
115 116 # ifdef GC_WIN32_THREADS
117 /* For now, alt-stack is not implemented for Win32. */
118 # else
119 /* The start of the alt-stack if there is one, `NULL` otherwise. */
120 ptr_t altstack;
121 122 /* Same for the "normal" stack (set by `GC_register_altstack`). */
123 ptr_t normstack;
124 125 /* The size of the alt-stack if exists. */
126 size_t altstack_size;
127 128 size_t normstack_size;
129 # endif
130 131 # ifdef E2K
132 /* The current offset in the procedure stack. */
133 size_t ps_ofs;
134 # endif
135 136 # ifndef GC_NO_FINALIZATION
137 unsigned char finalizer_nested;
138 139 /*
140 * Used by `GC_check_finalizer_nested()` to minimize the level of recursion
141 * when a client finalizer allocates memory. Initially it is zero and
142 * `finalizer_nested` is zero.
143 */
144 unsigned short finalizer_skipped;
145 # endif
146 147 /*
148 * Points to the "frame" data held in stack by the innermost
149 * `GC_call_with_gc_active()` of this stack (thread); may be `NULL`.
150 */
151 struct GC_traced_stack_sect_s *traced_stack_sect;
152 };
153 typedef struct GC_StackContext_Rep *GC_stack_context_t;
154 155 # ifdef GC_WIN32_THREADS
156 typedef DWORD thread_id_t;
157 # define thread_id_self() GetCurrentThreadId()
158 # define THREAD_ID_EQUAL(id1, id2) ((id1) == (id2))
159 /*
160 * Convert a platform-specific thread `id` (of `thread_id_t` type) to some
161 * pointer identifying the thread. This is used mostly for a debugging
162 * purpose when printing thread id.
163 */
164 # define THREAD_ID_TO_VPTR(id) CAST_THRU_UINTPTR(void *, id)
165 # else
166 typedef pthread_t thread_id_t;
167 # define thread_id_self() pthread_self()
168 # define THREAD_ID_EQUAL(id1, id2) THREAD_EQUAL(id1, id2)
169 # define THREAD_ID_TO_VPTR(id) PTHREAD_TO_VPTR(id)
170 # endif
171 172 struct GC_Thread_Rep {
173 union {
174 # if !defined(GC_NO_THREADS_DISCOVERY) && defined(GC_WIN32_THREADS)
175 /*
176 * Updated without a lock. We assert that each unused entry has
177 * invalid `id` of zero and zero `stack_end`.
178 * Used only if `GC_win32_dll_threads`.
179 */
180 volatile AO_t in_use;
181 182 /*
183 * The same but of the type that matches the first argument of
184 * `InterlockedExchange()`; `volatile` is omitted because the
185 * ancient version of the function prototype lacked the qualifier.
186 */
187 LONG long_in_use;
188 # endif
189 190 /*
191 * Hash table link without `GC_win32_dll_threads`. More recently
192 * allocated threads with a given `pthreads` id come first.
193 * (All but the first are guaranteed to be dead, but we may not yet
194 * have registered the join.)
195 */
196 struct GC_Thread_Rep *next;
197 } tm; /*< table_management */
198 199 GC_stack_context_t crtn;
200 201 thread_id_t id; /*< hash table key */
202 # ifdef DARWIN
203 mach_port_t mach_thread;
204 # elif defined(GC_WIN32_THREADS) && defined(GC_PTHREADS)
205 pthread_t pthread_id;
206 # elif defined(USE_TKILL_ON_ANDROID)
207 pid_t kernel_id;
208 # endif
209 210 # ifdef MSWINCE
211 /*
212 * According to MSDN docs for WinCE targets:
213 * - `DuplicateHandle()` is not applicable to thread handles;
214 * - the value returned by `GetCurrentThreadId()` could be used as
215 * a "real" thread handle (for `SuspendThread()`, `ResumeThread()`
216 * and `GetThreadContext()`).
217 */
218 # define THREAD_HANDLE(p) ((HANDLE)(word)(p)->id)
219 # elif defined(GC_WIN32_THREADS)
220 HANDLE handle;
221 # define THREAD_HANDLE(p) ((p)->handle)
222 # endif /* GC_WIN32_THREADS && !MSWINCE */
223 224 /* Note: this is protected by the allocator lock. */
225 unsigned char flags;
226 227 /* Thread has exited (`pthreads` only). */
228 # define FINISHED 0x1
229 # ifndef GC_PTHREADS
230 # define KNOWN_FINISHED(p) FALSE
231 # else
232 # define KNOWN_FINISHED(p) (((p)->flags & FINISHED) != 0)
233 /*
234 * Thread is treated as detached. Thread may really be detached,
235 * or it may have been explicitly registered, in which case we can
236 * deallocate its `GC_Thread_Rep` entity once it unregisters itself,
237 * since it may not return a pointer to the collector heap.
238 */
239 # define DETACHED 0x2
240 # endif
241 # if (defined(GC_HAVE_PTHREAD_EXIT) || !defined(GC_NO_PTHREAD_CANCEL)) \
242 && defined(GC_PTHREADS)
243 /* Collections are disabled while the thread is exiting. */
244 # define DISABLED_GC 0x10
245 # endif
246 /*
247 * Thread is in the do-blocking state. If the flag is set, the thread
248 * has stored its stack pointer value and will acquire the allocator lock
249 * before any pointer manipulation. Thus, it does not need a signal sent
250 * to stop it.
251 */
252 # define DO_BLOCKING 0x20
253 # ifdef GC_WIN32_THREADS
254 /* Thread is suspended by `SuspendThread()`. */
255 # define IS_SUSPENDED 0x40
256 # endif
257 258 # ifdef SIGNAL_BASED_STOP_WORLD
259 /*
260 * The value of `GC_stop_count` when the thread last successfully
261 * handled a suspend signal.
262 */
263 volatile AO_t last_stop_count;
264 # ifdef GC_ENABLE_SUSPEND_THREAD
265 /*
266 * Note: an odd value means thread was suspended externally;
267 * incremented on every call of `GC_suspend_thread()` and
268 * `GC_resume_thread()`; updated with the allocator lock held, but
269 * could be read from a signal handler.
270 */
271 volatile AO_t ext_suspend_cnt;
272 # endif
273 # endif
274 275 # ifdef GC_PTHREADS
276 /*
277 * The value returned from the thread. Used only to avoid premature
278 * reclamation of any data it might reference. This is unfortunately
279 * also the reason we need to intercept join and detach.
280 */
281 void *status;
282 # endif
283 284 # ifdef THREAD_LOCAL_ALLOC
285 struct thread_local_freelists tlfs GC_ATTR_PTRT_ALIGNED;
286 # endif
287 288 # ifdef NACL
289 /*
290 * Grab `NACL_GC_REG_STORAGE_SIZE` pointers off the stack when going
291 * into a `syscall()`. 20 is more than we need, but it is
292 * an overestimate in case the instrumented function uses any
293 * callee-saved registers, they may be pushed to the stack much
294 * earlier. Also, on x86_64 `push` puts 8 bytes on the stack even
295 * though our pointers have 4 bytes in length.
296 */
297 # ifdef ARM32
298 /* Space for `r4` ... `r8`, `r10`, ... `r12`, `r14`. */
299 # define NACL_GC_REG_STORAGE_SIZE 9
300 # else
301 # define NACL_GC_REG_STORAGE_SIZE 20
302 # endif
303 ptr_t reg_storage[NACL_GC_REG_STORAGE_SIZE];
304 # elif defined(PLATFORM_HAVE_GC_REG_STORAGE_SIZE)
305 word registers[PLATFORM_GC_REG_STORAGE_SIZE]; /*< used externally */
306 # endif
307 308 # if defined(WOW64_THREAD_CONTEXT_WORKAROUND) && defined(MSWINRT_FLAVOR)
309 GC_NT_TIB *tib;
310 # endif
311 312 # ifdef RETRY_GET_THREAD_CONTEXT /* `&& GC_WIN32_THREADS` */
313 ptr_t context_sp;
314 315 /*
316 * Populated as part of `GC_suspend()` as resume/suspend loop may be
317 * needed for `GetThreadContext()` to succeed.
318 */
319 word context_regs[PUSHED_REGS_COUNT];
320 # endif
321 };
322 typedef struct GC_Thread_Rep *GC_thread;
323 324 # if defined(GC_PTHREADS) || defined(GC_PTHREADS_PARAMARK)
325 /* Convert an opaque `pthread_t` value to a pointer identifying the thread. */
326 # if defined(GC_WIN32_THREADS) && !defined(CYGWIN32) \
327 && (defined(GC_WIN32_PTHREADS) || defined(GC_PTHREADS_PARAMARK)) \
328 && !defined(__WINPTHREADS_VERSION_MAJOR)
329 /*
330 * Using documented internal details of pthreads-win32 library.
331 * `pthread_t` is a structure.
332 */
333 # define PTHREAD_TO_VPTR(id) ((void *)(id).p)
334 # else
335 # define PTHREAD_TO_VPTR(id) CAST_THRU_UINTPTR(void *, id)
336 # endif
337 # endif
338 339 # ifndef THREAD_TABLE_SZ
340 /* Note: this is a power of 2 (for speed). */
341 # define THREAD_TABLE_SZ 256
342 # endif
343 344 # ifdef GC_WIN32_THREADS
345 # define THREAD_TABLE_INDEX(id) /*< `id` is of `DWORD` type */ \
346 (int)((((id) >> 8) ^ (id)) % THREAD_TABLE_SZ)
347 # elif CPP_WORDSZ > 32
348 # define THREAD_TABLE_INDEX(id) \
349 (int)(((((NUMERIC_THREAD_ID(id) >> 8) ^ NUMERIC_THREAD_ID(id)) >> 16) \
350 ^ ((NUMERIC_THREAD_ID(id) >> 8) ^ NUMERIC_THREAD_ID(id))) \
351 % THREAD_TABLE_SZ)
352 # else
353 # define THREAD_TABLE_INDEX(id) \
354 (int)(((NUMERIC_THREAD_ID(id) >> 16) ^ (NUMERIC_THREAD_ID(id) >> 8) \
355 ^ NUMERIC_THREAD_ID(id)) \
356 % THREAD_TABLE_SZ)
357 # endif
358 359 /*
360 * The set (hash table) of all known threads. We intercept thread
361 * creation and join/detach. Protected by the allocator lock.
362 * Not used if `GC_win32_dll_threads`.
363 */
364 GC_EXTERN GC_thread GC_threads[THREAD_TABLE_SZ];
365 366 # ifndef MAX_MARKERS
367 # define MAX_MARKERS 16
368 # endif
369 370 # ifdef GC_ASSERTIONS
371 GC_EXTERN GC_bool GC_thr_initialized;
372 # endif
373 374 # ifdef STACKPTR_CORRECTOR_AVAILABLE
375 GC_EXTERN GC_sp_corrector_proc GC_sp_corrector;
376 # endif
377 378 GC_EXTERN GC_on_thread_event_proc GC_on_thread_event;
379 380 # ifdef GC_WIN32_THREADS
381 382 # ifdef GC_NO_THREADS_DISCOVERY
383 # define GC_win32_dll_threads FALSE
384 # elif defined(GC_DISCOVER_TASK_THREADS)
385 # define GC_win32_dll_threads TRUE
386 # else
387 /*
388 * `GC_win32_dll_threads` must be set (if needed) at the application
389 * initialization time, i.e. before any collector or thread calls.
390 * We make it a "dynamic" option only to avoid multiple library versions.
391 */
392 GC_EXTERN GC_bool GC_win32_dll_threads;
393 # endif
394 395 # ifdef PARALLEL_MARK
396 GC_EXTERN int GC_available_markers_m1;
397 398 /*
399 * The desired amount of marker threads (including the initiating one).
400 * Note: the default value (0) means the number of markers should be
401 * selected automatically.
402 */
403 GC_EXTERN unsigned GC_required_markers_cnt;
404 405 /* The cold end of the stack for markers. */
406 GC_EXTERN ptr_t GC_marker_sp[MAX_MARKERS - 1];
407 408 /*
409 * Last known minimum (hottest) address in stack (or `ADDR_LIMIT` if
410 * unset) for markers.
411 */
412 GC_EXTERN ptr_t GC_marker_last_stack_min[MAX_MARKERS - 1];
413 414 # ifndef GC_PTHREADS_PARAMARK
415 /*
416 * A table mapping the helper thread id to the mark helper index
417 * (linear search is used since the mapping contains only a few entries).
418 */
419 GC_EXTERN thread_id_t GC_marker_Id[MAX_MARKERS - 1];
420 # endif
421 422 # if !defined(HAVE_PTHREAD_SETNAME_NP_WITH_TID) && !defined(MSWINCE)
423 GC_INNER void GC_init_win32_thread_naming(HMODULE hK32);
424 # endif
425 # ifdef GC_PTHREADS_PARAMARK
426 GC_INNER void *GC_mark_thread(void *);
427 # elif defined(MSWINCE)
428 GC_INNER DWORD WINAPI GC_mark_thread(LPVOID);
429 # else
430 GC_INNER unsigned __stdcall GC_mark_thread(void *);
431 # endif
432 # endif /* PARALLEL_MARK */
433 434 /*
435 * Add a thread to `GC_threads`. We assume it was not already there.
436 * Note: the `id` field should be set by the caller.
437 */
438 GC_INNER GC_thread GC_new_thread(thread_id_t);
439 440 GC_INNER void GC_record_stack_base(GC_stack_context_t crtn,
441 const struct GC_stack_base *sb);
442 443 /*
444 * This may be called from `DllMain`, and hence operates under unusual
445 * constraints. In particular, it must be lock-free if
446 * `GC_win32_dll_threads`. Always called from the thread being added.
447 * If not `GC_win32_dll_threads`, then we already hold the allocator
448 * lock except possibly during single-threaded startup code.
449 * Does not initialize thread-local free lists.
450 */
451 GC_INNER GC_thread GC_register_my_thread_inner(const struct GC_stack_base *sb,
452 thread_id_t self_id);
453 454 # ifdef GC_PTHREADS
455 GC_INNER void GC_win32_cache_self_pthread(thread_id_t);
456 # else
457 /*
458 * Delete a thread from `GC_threads`. We assume it is there.
459 * (The code intentionally traps if it was not.) It is also safe to delete
460 * the main thread. If `GC_win32_dll_threads`, it should be called only
461 * from the thread being deleted (except for `DLL_PROCESS_DETACH` case).
462 * If a thread has been joined, but we have not yet been notified, then
463 * there may be more than one thread in the table with the same thread
464 * id - this is OK because we delete a specific one.
465 */
466 GC_INNER void GC_delete_thread(GC_thread);
467 # endif
468 469 # ifdef CAN_HANDLE_FORK
470 /* Prepare for a process fork if requested. */
471 GC_INNER void GC_setup_atfork(void);
472 # endif
473 474 # if !defined(DONT_USE_ATEXIT) || !defined(GC_NO_THREADS_DISCOVERY)
475 GC_EXTERN thread_id_t GC_main_thread_id;
476 # endif
477 478 # ifndef GC_NO_THREADS_DISCOVERY
479 /*
480 * Search in `dll_thread_table` and return the `GC_thread[]` entity
481 * corresponding to the given thread `id`.
482 * May be called without a lock, but should be called in contexts in
483 * those the requested thread cannot be asynchronously deleted, e.g.
484 * from the thread itself.
485 */
486 GC_INNER GC_thread GC_win32_dll_lookup_thread(thread_id_t id);
487 # endif
488 489 # ifdef MPROTECT_VDB
490 /*
491 * Make sure given thread descriptor is not protected by the VDB
492 * implementation. Used to prevent write faults when the world is
493 * (partially) stopped, since it may have been stopped with a system
494 * lock held, and that lock may be required for fault handling.
495 */
496 GC_INNER void GC_win32_unprotect_thread(GC_thread);
497 # else
498 # define GC_win32_unprotect_thread(t) (void)(t)
499 # endif /* !MPROTECT_VDB */
500 501 # else
502 # define GC_win32_dll_threads FALSE
503 # endif /* !GC_WIN32_THREADS */
504 505 # ifdef GC_PTHREADS
506 # ifdef GC_WIN32_THREADS
507 /*
508 * Return a `GC_thread` corresponding to a given `pthread_t`, or
509 * `NULL` if it is not there. We assume that this is only called for
510 * `pthreads` ids that have not yet terminated or are still joinable,
511 * and cannot be terminated concurrently.
512 */
513 GC_INNER GC_thread GC_lookup_by_pthread(pthread_t);
514 # else
515 # define GC_lookup_by_pthread(t) GC_lookup_thread(t)
516 # endif
517 # endif /* GC_PTHREADS */
518 519 /*
520 * Return a `GC_thread` corresponding to a given thread `id`, or
521 * `NULL` if it is not there. Caller holds the allocator lock in the
522 * reader mode, at least, or otherwise inhibits updates. If there is more
523 * than one thread with the given `id`, we return the most recent one.
524 */
525 GC_INNER GC_thread GC_lookup_thread(thread_id_t id);
526 527 # define GC_self_thread_inner() GC_lookup_thread(thread_id_self())
528 529 /*
530 * Wait until an in-progress collection has finished.
531 * We hold the allocator lock and repeatedly release it in order to wait.
532 * If `wait_for_all`, then we exit with the allocator lock held and
533 * no collection is in progress; otherwise we just wait for the current
534 * collection to finish.
535 */
536 GC_INNER void GC_wait_for_gc_completion(GC_bool wait_for_all);
537 538 # ifdef NACL
539 GC_INNER void GC_nacl_initialize_gc_thread(GC_thread);
540 GC_INNER void GC_nacl_shutdown_gc_thread(void);
541 # endif
542 543 # if defined(PTHREAD_STOP_WORLD_IMPL) \
544 && !defined(NO_SIGNALS_UNBLOCK_IN_MAIN) \
545 || defined(GC_EXPLICIT_SIGNALS_UNBLOCK)
546 /*
547 * Some targets (e.g., Solaris) might require this to be called when
548 * doing thread registering from the thread destructor.
549 */
550 GC_INNER void GC_unblock_gc_signals(void);
551 # endif
552 553 # if defined(GC_ENABLE_SUSPEND_THREAD) && defined(SIGNAL_BASED_STOP_WORLD)
554 GC_INNER void GC_suspend_self_inner(GC_thread me, size_t suspend_cnt);
555 556 /*
557 * A wrapper over `GC_suspend_self_inner()`. Similar to
558 * `GC_do_blocking_inner()` but assuming the allocator lock is held and
559 * `fn` is `GC_suspend_self_inner`.
560 */
561 GC_INNER void GC_suspend_self_blocked(ptr_t thread_me, void *context);
562 # endif
563 564 # if defined(GC_PTHREADS) && !defined(PLATFORM_THREADS) \
565 && !defined(SN_TARGET_PSP2)
566 567 # ifdef GC_PTHREAD_START_STANDALONE
568 # define GC_INNER_PTHRSTART /*< empty */
569 # else
570 # define GC_INNER_PTHRSTART GC_INNER
571 # endif
572 573 GC_INNER_PTHRSTART void *GC_CALLBACK
574 GC_pthread_start_inner(struct GC_stack_base *sb, void *arg);
575 576 /*
577 * Called from `GC_pthread_start_inner()`. Defined in `pthread_support.c`
578 * file to minimize the number of files included from `pthread_start.c` file
579 * (because `sem_t` and `sem_post()` are not used in that file directly).
580 */
581 GC_INNER_PTHRSTART GC_thread
582 GC_start_rtn_prepare_thread(void *(**pstart)(void *), void **pstart_arg,
583 struct GC_stack_base *sb, void *arg);
584 585 /*
586 * Called at thread exit. Never called for main thread. That is OK,
587 * since it results in at most a tiny one-time leak. And LinuxThreads
588 * implementation does not reclaim the primordial (main) thread resources
589 * or id anyway.
590 */
591 GC_INNER_PTHRSTART void GC_thread_exit_proc(void *);
592 # endif /* GC_PTHREADS */
593 594 # ifdef DARWIN
595 # ifndef DARWIN_DONT_PARSE_STACK
596 GC_INNER ptr_t GC_FindTopOfStack(unsigned long);
597 # endif
598 # if defined(PARALLEL_MARK) && !defined(GC_NO_THREADS_DISCOVERY)
599 /* Note: this is used only by `GC_suspend_thread_list()`. */
600 GC_INNER GC_bool GC_is_mach_marker(thread_act_t);
601 # endif
602 # endif /* DARWIN */
603 604 # ifdef PTHREAD_STOP_WORLD_IMPL
605 GC_INNER void GC_stop_init(void);
606 # endif
607 608 EXTERN_C_END
609 610 #endif /* THREADS */
611 612 #endif /* GC_PTHREAD_SUPPORT_H */
613