* sparc/sol2.h (CPLUSPLUS_CPP_SPEC): Add all of CPP_SPEC instead
[official-gcc.git] / boehm-gc / win32_threads.c
blobe0d19d51b35c8c59ceb2d1fe8896bce2ca4638d2
1 #if defined(GC_WIN32_THREADS) || defined(WIN32_THREADS)
3 #include "private/gc_priv.h"
5 #if 0
6 #define STRICT
7 #include <windows.h>
8 #endif
10 #define MAX_THREADS 64
12 struct thread_entry {
13 LONG in_use;
14 DWORD id;
15 HANDLE handle;
16 void *stack; /* The cold end of the stack. */
17 /* 0 ==> entry not valid. */
18 /* !in_use ==> stack == 0 */
19 CONTEXT context;
20 GC_bool suspended;
23 volatile GC_bool GC_please_stop = FALSE;
25 volatile struct thread_entry thread_table[MAX_THREADS];
27 void GC_push_thread_structures GC_PROTO((void))
29 /* Unlike the other threads implementations, the thread table here */
30 /* contains no pointers to the collectable heap. Thus we have */
31 /* no private structures we need to preserve. */
34 void GC_stop_world()
36 DWORD thread_id = GetCurrentThreadId();
37 int i;
39 GC_please_stop = TRUE;
40 for (i = 0; i < MAX_THREADS; i++)
41 if (thread_table[i].stack != 0
42 && thread_table[i].id != thread_id) {
43 # ifdef MSWINCE
44 /* SuspendThread will fail if thread is running kernel code */
45 while (SuspendThread(thread_table[i].handle) == (DWORD)-1)
46 Sleep(10);
47 # else
48 /* Apparently the Windows 95 GetOpenFileName call creates */
49 /* a thread that does not properly get cleaned up, and */
50 /* SuspendThread on its descriptor may provoke a crash. */
51 /* This reduces the probability of that event, though it still */
52 /* appears there's a race here. */
53 DWORD exitCode;
54 if (GetExitCodeThread(thread_table[i].handle,&exitCode) &&
55 exitCode != STILL_ACTIVE) {
56 thread_table[i].stack = 0;
57 thread_table[i].in_use = FALSE;
58 CloseHandle(thread_table[i].handle);
59 BZERO((void *)(&thread_table[i].context), sizeof(CONTEXT));
60 continue;
62 if (SuspendThread(thread_table[i].handle) == (DWORD)-1)
63 ABORT("SuspendThread failed");
64 # endif
65 thread_table[i].suspended = TRUE;
69 void GC_start_world()
71 DWORD thread_id = GetCurrentThreadId();
72 int i;
73 for (i = 0; i < MAX_THREADS; i++)
74 if (thread_table[i].stack != 0 && thread_table[i].suspended
75 && thread_table[i].id != thread_id) {
76 if (ResumeThread(thread_table[i].handle) == (DWORD)-1)
77 ABORT("ResumeThread failed");
78 thread_table[i].suspended = FALSE;
80 GC_please_stop = FALSE;
83 # ifdef _MSC_VER
84 # pragma warning(disable:4715)
85 # endif
86 ptr_t GC_current_stackbottom()
88 DWORD thread_id = GetCurrentThreadId();
89 int i;
90 for (i = 0; i < MAX_THREADS; i++)
91 if (thread_table[i].stack && thread_table[i].id == thread_id)
92 return thread_table[i].stack;
93 ABORT("no thread table entry for current thread");
95 # ifdef _MSC_VER
96 # pragma warning(default:4715)
97 # endif
99 # ifdef MSWINCE
100 /* The VirtualQuery calls below won't work properly on WinCE, but */
101 /* since each stack is restricted to an aligned 64K region of */
102 /* virtual memory we can just take the next lowest multiple of 64K. */
103 # define GC_get_lo_stack_addr(s) \
104 ((ptr_t)(((DWORD)(s) - 1) & 0xFFFF0000))
105 # else
106 static ptr_t GC_get_lo_stack_addr(ptr_t s)
108 ptr_t bottom;
109 MEMORY_BASIC_INFORMATION info;
110 VirtualQuery(s, &info, sizeof(info));
111 do {
112 bottom = info.BaseAddress;
113 VirtualQuery(bottom - 1, &info, sizeof(info));
114 } while ((info.Protect & PAGE_READWRITE)
115 && !(info.Protect & PAGE_GUARD));
116 return(bottom);
118 # endif
120 void GC_push_all_stacks()
122 DWORD thread_id = GetCurrentThreadId();
123 int i;
124 for (i = 0; i < MAX_THREADS; i++)
125 if (thread_table[i].stack) {
126 ptr_t bottom = GC_get_lo_stack_addr(thread_table[i].stack);
127 if (thread_table[i].id == thread_id)
128 GC_push_all_stack((ptr_t)&i, thread_table[i].stack);
129 else {
130 thread_table[i].context.ContextFlags
131 = (CONTEXT_INTEGER|CONTEXT_CONTROL);
132 if (!GetThreadContext(thread_table[i].handle,
133 /* cast away volatile qualifier */
134 (LPCONTEXT)&thread_table[i].context))
135 ABORT("GetThreadContext failed");
136 # ifdef I386
137 if (thread_table[i].context.Esp >= (DWORD)thread_table[i].stack
138 || thread_table[i].context.Esp < (DWORD)bottom)
139 ABORT("Thread stack pointer out of range");
140 GC_push_one ((word) thread_table[i].context.Edi);
141 GC_push_one ((word) thread_table[i].context.Esi);
142 GC_push_one ((word) thread_table[i].context.Ebp);
143 GC_push_one ((word) thread_table[i].context.Ebx);
144 GC_push_one ((word) thread_table[i].context.Edx);
145 GC_push_one ((word) thread_table[i].context.Ecx);
146 GC_push_one ((word) thread_table[i].context.Eax);
147 GC_push_all_stack((char *) thread_table[i].context.Esp,
148 thread_table[i].stack);
149 # else
150 # ifdef ARM32
151 if (thread_table[i].context.Sp >= (DWORD)thread_table[i].stack
152 || thread_table[i].context.Sp < (DWORD)bottom)
153 ABORT("Thread stack pointer out of range");
154 GC_push_one ((word) thread_table[i].context.R0);
155 GC_push_one ((word) thread_table[i].context.R1);
156 GC_push_one ((word) thread_table[i].context.R2);
157 GC_push_one ((word) thread_table[i].context.R3);
158 GC_push_one ((word) thread_table[i].context.R4);
159 GC_push_one ((word) thread_table[i].context.R5);
160 GC_push_one ((word) thread_table[i].context.R6);
161 GC_push_one ((word) thread_table[i].context.R7);
162 GC_push_one ((word) thread_table[i].context.R8);
163 GC_push_one ((word) thread_table[i].context.R9);
164 GC_push_one ((word) thread_table[i].context.R10);
165 GC_push_one ((word) thread_table[i].context.R11);
166 GC_push_one ((word) thread_table[i].context.R12);
167 GC_push_all_stack((char *) thread_table[i].context.Sp,
168 thread_table[i].stack);
169 # else
170 # ifdef SHx
171 if (thread_table[i].context.R15 >= (DWORD)thread_table[i].stack
172 || thread_table[i].context.R15 < (DWORD)bottom)
173 ABORT("Thread stack pointer out of range");
174 GC_push_one ((word) thread_table[i].context.R0);
175 GC_push_one ((word) thread_table[i].context.R1);
176 GC_push_one ((word) thread_table[i].context.R2);
177 GC_push_one ((word) thread_table[i].context.R3);
178 GC_push_one ((word) thread_table[i].context.R4);
179 GC_push_one ((word) thread_table[i].context.R5);
180 GC_push_one ((word) thread_table[i].context.R6);
181 GC_push_one ((word) thread_table[i].context.R7);
182 GC_push_one ((word) thread_table[i].context.R8);
183 GC_push_one ((word) thread_table[i].context.R9);
184 GC_push_one ((word) thread_table[i].context.R10);
185 GC_push_one ((word) thread_table[i].context.R11);
186 GC_push_one ((word) thread_table[i].context.R12);
187 GC_push_one ((word) thread_table[i].context.R13);
188 GC_push_one ((word) thread_table[i].context.R14);
189 GC_push_all_stack((char *) thread_table[i].context.R15,
190 thread_table[i].stack);
191 # else
192 # ifdef MIPS
193 if (thread_table[i].context.IntSp >= (DWORD)thread_table[i].stack
194 || thread_table[i].context.IntSp < (DWORD)bottom)
195 ABORT("Thread stack pointer out of range");
196 GC_push_one ((word) thread_table[i].context.IntAt);
197 GC_push_one ((word) thread_table[i].context.IntV0);
198 GC_push_one ((word) thread_table[i].context.IntV1);
199 GC_push_one ((word) thread_table[i].context.IntA0);
200 GC_push_one ((word) thread_table[i].context.IntA1);
201 GC_push_one ((word) thread_table[i].context.IntA2);
202 GC_push_one ((word) thread_table[i].context.IntA3);
203 GC_push_one ((word) thread_table[i].context.IntT0);
204 GC_push_one ((word) thread_table[i].context.IntT1);
205 GC_push_one ((word) thread_table[i].context.IntT2);
206 GC_push_one ((word) thread_table[i].context.IntT3);
207 GC_push_one ((word) thread_table[i].context.IntT4);
208 GC_push_one ((word) thread_table[i].context.IntT5);
209 GC_push_one ((word) thread_table[i].context.IntT6);
210 GC_push_one ((word) thread_table[i].context.IntT7);
211 GC_push_one ((word) thread_table[i].context.IntS0);
212 GC_push_one ((word) thread_table[i].context.IntS1);
213 GC_push_one ((word) thread_table[i].context.IntS2);
214 GC_push_one ((word) thread_table[i].context.IntS3);
215 GC_push_one ((word) thread_table[i].context.IntS4);
216 GC_push_one ((word) thread_table[i].context.IntS5);
217 GC_push_one ((word) thread_table[i].context.IntS6);
218 GC_push_one ((word) thread_table[i].context.IntS7);
219 GC_push_one ((word) thread_table[i].context.IntT8);
220 GC_push_one ((word) thread_table[i].context.IntT9);
221 GC_push_one ((word) thread_table[i].context.IntK0);
222 GC_push_one ((word) thread_table[i].context.IntK1);
223 GC_push_one ((word) thread_table[i].context.IntS8);
224 GC_push_all_stack((char *) thread_table[i].context.IntSp,
225 thread_table[i].stack);
226 # else
227 # ifdef PPC
228 if (thread_table[i].context.Gpr1 >= (DWORD)thread_table[i].stack
229 || thread_table[i].context.Gpr1 < (DWORD)bottom)
230 ABORT("Thread stack pointer out of range");
231 GC_push_one ((word) thread_table[i].context.Gpr0);
232 /* Gpr1 is stack pointer */
233 /* Gpr2 is global pointer */
234 GC_push_one ((word) thread_table[i].context.Gpr3);
235 GC_push_one ((word) thread_table[i].context.Gpr4);
236 GC_push_one ((word) thread_table[i].context.Gpr5);
237 GC_push_one ((word) thread_table[i].context.Gpr6);
238 GC_push_one ((word) thread_table[i].context.Gpr7);
239 GC_push_one ((word) thread_table[i].context.Gpr8);
240 GC_push_one ((word) thread_table[i].context.Gpr9);
241 GC_push_one ((word) thread_table[i].context.Gpr10);
242 GC_push_one ((word) thread_table[i].context.Gpr11);
243 GC_push_one ((word) thread_table[i].context.Gpr12);
244 /* Gpr13 is reserved for the kernel */
245 GC_push_one ((word) thread_table[i].context.Gpr14);
246 GC_push_one ((word) thread_table[i].context.Gpr15);
247 GC_push_one ((word) thread_table[i].context.Gpr16);
248 GC_push_one ((word) thread_table[i].context.Gpr17);
249 GC_push_one ((word) thread_table[i].context.Gpr18);
250 GC_push_one ((word) thread_table[i].context.Gpr19);
251 GC_push_one ((word) thread_table[i].context.Gpr20);
252 GC_push_one ((word) thread_table[i].context.Gpr21);
253 GC_push_one ((word) thread_table[i].context.Gpr22);
254 GC_push_one ((word) thread_table[i].context.Gpr23);
255 GC_push_one ((word) thread_table[i].context.Gpr24);
256 GC_push_one ((word) thread_table[i].context.Gpr25);
257 GC_push_one ((word) thread_table[i].context.Gpr26);
258 GC_push_one ((word) thread_table[i].context.Gpr27);
259 GC_push_one ((word) thread_table[i].context.Gpr28);
260 GC_push_one ((word) thread_table[i].context.Gpr29);
261 GC_push_one ((word) thread_table[i].context.Gpr30);
262 GC_push_one ((word) thread_table[i].context.Gpr31);
263 GC_push_all_stack((char *) thread_table[i].context.Gpr1,
264 thread_table[i].stack);
265 # else
266 # ifdef ALPHA
267 if (thread_table[i].context.IntSp >= (DWORD)thread_table[i].stack
268 || thread_table[i].context.IntSp < (DWORD)bottom)
269 ABORT("Thread stack pointer out of range");
270 GC_push_one ((word) thread_table[i].context.IntV0);
271 GC_push_one ((word) thread_table[i].context.IntT0);
272 GC_push_one ((word) thread_table[i].context.IntT1);
273 GC_push_one ((word) thread_table[i].context.IntT2);
274 GC_push_one ((word) thread_table[i].context.IntT3);
275 GC_push_one ((word) thread_table[i].context.IntT4);
276 GC_push_one ((word) thread_table[i].context.IntT5);
277 GC_push_one ((word) thread_table[i].context.IntT6);
278 GC_push_one ((word) thread_table[i].context.IntT7);
279 GC_push_one ((word) thread_table[i].context.IntS0);
280 GC_push_one ((word) thread_table[i].context.IntS1);
281 GC_push_one ((word) thread_table[i].context.IntS2);
282 GC_push_one ((word) thread_table[i].context.IntS3);
283 GC_push_one ((word) thread_table[i].context.IntS4);
284 GC_push_one ((word) thread_table[i].context.IntS5);
285 GC_push_one ((word) thread_table[i].context.IntFp);
286 GC_push_one ((word) thread_table[i].context.IntA0);
287 GC_push_one ((word) thread_table[i].context.IntA1);
288 GC_push_one ((word) thread_table[i].context.IntA2);
289 GC_push_one ((word) thread_table[i].context.IntA3);
290 GC_push_one ((word) thread_table[i].context.IntA4);
291 GC_push_one ((word) thread_table[i].context.IntA5);
292 GC_push_one ((word) thread_table[i].context.IntT8);
293 GC_push_one ((word) thread_table[i].context.IntT9);
294 GC_push_one ((word) thread_table[i].context.IntT10);
295 GC_push_one ((word) thread_table[i].context.IntT11);
296 GC_push_one ((word) thread_table[i].context.IntT12);
297 GC_push_one ((word) thread_table[i].context.IntAt);
298 GC_push_all_stack((char *) thread_table[i].context.IntSp,
299 thread_table[i].stack);
300 # else
301 --> architecture not supported
302 # endif /* !ALPHA */
303 # endif /* !PPC */
304 # endif /* !MIPS */
305 # endif /* !SHx */
306 # endif /* !ARM32 */
307 # endif /* !I386 */
312 void GC_get_next_stack(char *start, char **lo, char **hi)
314 int i;
315 # define ADDR_LIMIT (char *)(-1L)
316 char * current_min = ADDR_LIMIT;
318 for (i = 0; i < MAX_THREADS; i++) {
319 char * s = (char *)thread_table[i].stack;
321 if (0 != s && s > start && s < current_min) {
322 current_min = s;
325 *hi = current_min;
326 if (current_min == ADDR_LIMIT) {
327 *lo = ADDR_LIMIT;
328 return;
330 *lo = GC_get_lo_stack_addr(current_min);
331 if (*lo < start) *lo = start;
335 # ifdef MSWINCE
337 typedef struct {
338 HANDLE child_ready_h, parent_ready_h;
339 volatile struct thread_entry * entry;
340 LPTHREAD_START_ROUTINE start;
341 LPVOID param;
342 } thread_args;
344 DWORD WINAPI thread_start(LPVOID arg);
346 HANDLE WINAPI GC_CreateThread(
347 LPSECURITY_ATTRIBUTES lpThreadAttributes,
348 DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
349 LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId )
351 HANDLE thread_h = NULL;
352 HANDLE child_ready_h, parent_ready_h;
354 int i;
355 thread_args args;
357 /* allocate thread slot */
358 LOCK();
359 for (i = 0; i != MAX_THREADS && thread_table[i].in_use; i++)
361 if (i != MAX_THREADS) {
362 thread_table[i].in_use = TRUE;
364 UNLOCK();
366 if (i != MAX_THREADS) {
368 /* create unnamed unsignalled events */
369 if (child_ready_h = CreateEvent(NULL, FALSE, FALSE, NULL)) {
370 if (parent_ready_h = CreateEvent(NULL, FALSE, FALSE, NULL)) {
372 /* set up thread arguments */
373 args.child_ready_h = child_ready_h;
374 args.parent_ready_h = parent_ready_h;
375 args.entry = &thread_table[i];
376 args.start = lpStartAddress;
377 args.param = lpParameter;
379 thread_h = CreateThread(lpThreadAttributes,
380 dwStackSize, thread_start,
381 &args,
382 dwCreationFlags & ~CREATE_SUSPENDED,
383 lpThreadId);
385 if (thread_h) {
387 /* fill in ID and handle; tell child this is done */
388 thread_table[i].id = *lpThreadId;
389 thread_table[i].handle = thread_h;
390 SetEvent (parent_ready_h);
392 /* wait for child to fill in stack and copy args */
393 WaitForSingleObject (child_ready_h, INFINITE);
395 /* suspend the child if requested */
396 if (dwCreationFlags & CREATE_SUSPENDED)
397 SuspendThread (thread_h);
399 /* let child call given function now (or when resumed) */
400 SetEvent (parent_ready_h);
402 } else {
403 CloseHandle (parent_ready_h);
408 CloseHandle (child_ready_h);
410 if (thread_h == NULL)
411 thread_table[i].in_use = FALSE;
413 } else { /* no thread slot found */
414 SetLastError (ERROR_TOO_MANY_TCBS);
417 return thread_h;
420 static DWORD WINAPI thread_start(LPVOID arg)
422 DWORD ret = 0;
423 thread_args args = *(thread_args *)arg;
425 /* wait for parent to fill in ID and handle */
426 WaitForSingleObject (args.parent_ready_h, INFINITE);
427 ResetEvent (args.parent_ready_h);
429 /* fill in stack; tell parent this is done */
430 args.entry->stack = GC_get_stack_base();
431 SetEvent (args.child_ready_h);
433 /* wait for parent to tell us to go (in case it needs to suspend us) */
434 WaitForSingleObject (args.parent_ready_h, INFINITE);
435 CloseHandle (args.parent_ready_h);
437 /* Clear the thread entry even if we exit with an exception. */
438 /* This is probably pointless, since an uncaught exception is */
439 /* supposed to result in the process being killed. */
440 __try {
441 ret = args.start (args.param);
442 } __finally {
443 LOCK();
444 args.entry->stack = 0;
445 args.entry->in_use = FALSE;
446 /* cast away volatile qualifier */
447 BZERO((void *) &args.entry->context, sizeof(CONTEXT));
448 UNLOCK();
451 return ret;
454 typedef struct {
455 HINSTANCE hInstance;
456 HINSTANCE hPrevInstance;
457 LPWSTR lpCmdLine;
458 int nShowCmd;
459 } main_thread_args;
461 DWORD WINAPI main_thread_start(LPVOID arg);
463 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
464 LPWSTR lpCmdLine, int nShowCmd)
466 DWORD exit_code = 1;
468 main_thread_args args = {
469 hInstance, hPrevInstance, lpCmdLine, nShowCmd
471 HANDLE thread_h;
472 DWORD thread_id;
474 /* initialize everything */
475 InitializeCriticalSection(&GC_allocate_ml);
476 GC_init();
478 /* start the main thread */
479 thread_h = GC_CreateThread(
480 NULL, 0, main_thread_start, &args, 0, &thread_id);
482 if (thread_h != NULL)
484 WaitForSingleObject (thread_h, INFINITE);
485 GetExitCodeThread (thread_h, &exit_code);
486 CloseHandle (thread_h);
489 GC_deinit();
490 DeleteCriticalSection(&GC_allocate_ml);
492 return (int) exit_code;
495 DWORD WINAPI main_thread_start(LPVOID arg)
497 main_thread_args * args = (main_thread_args *) arg;
499 return (DWORD) GC_WinMain (args->hInstance, args->hPrevInstance,
500 args->lpCmdLine, args->nShowCmd);
503 # else /* !MSWINCE */
505 LONG WINAPI GC_write_fault_handler(struct _EXCEPTION_POINTERS *exc_info);
508 * This isn't generally safe, since DllMain is not premptible.
509 * If another thread holds the lock while this runs we're in trouble.
510 * Pontus Rydin suggests wrapping the thread start routine instead.
512 BOOL WINAPI DllMain(HINSTANCE inst, ULONG reason, LPVOID reserved)
514 switch (reason) {
515 case DLL_PROCESS_ATTACH:
516 InitializeCriticalSection(&GC_allocate_ml);
517 GC_init(); /* Force initialization before thread attach. */
518 /* fall through */
519 case DLL_THREAD_ATTACH:
521 int i;
522 /* It appears to be unsafe to acquire a lock here, since this */
523 /* code is apparently not preeemptible on some systems. */
524 /* (This is based on complaints, not on Microsoft's official */
525 /* documentation, which says this should perform "only simple */
526 /* inititalization tasks".) */
527 /* Hence we make do with nonblocking synchronization. */
529 /* The following should be a noop according to the win32 */
530 /* documentation. There is empirical evidence that it */
531 /* isn't. - HB */
532 # ifdef MPROTECT_VDB
533 if (GC_incremental) SetUnhandledExceptionFilter(GC_write_fault_handler);
534 # endif
536 for (i = 0;
537 /* cast away volatile qualifier */
538 InterlockedExchange((LPLONG) &thread_table[i].in_use, 1) != 0;
539 i++) {
540 /* Compare-and-swap would make this cleaner, but that's not */
541 /* supported before Windows 98 and NT 4.0. In Windows 2000, */
542 /* InterlockedExchange is supposed to be replaced by */
543 /* InterlockedExchangePointer, but that's not really what I */
544 /* want here. */
545 if (i == MAX_THREADS - 1)
546 ABORT("too many threads");
548 thread_table[i].id = GetCurrentThreadId();
549 if (!DuplicateHandle(GetCurrentProcess(),
550 GetCurrentThread(),
551 GetCurrentProcess(),
552 /* cast away volatile qualifier */
553 (HANDLE *) &thread_table[i].handle,
556 DUPLICATE_SAME_ACCESS)) {
557 DWORD last_error = GetLastError();
558 GC_printf1("Last error code: %lx\n", last_error);
559 ABORT("DuplicateHandle failed");
561 thread_table[i].stack = GC_get_stack_base();
562 /* If this thread is being created while we are trying to stop */
563 /* the world, wait here. Hopefully this can't happen on any */
564 /* systems that don't allow us to block here. */
565 while (GC_please_stop) Sleep(20);
567 break;
568 case DLL_THREAD_DETACH:
570 int i;
571 DWORD thread_id = GetCurrentThreadId();
572 LOCK();
573 for (i = 0;
574 i < MAX_THREADS &&
575 (thread_table[i].stack == 0 || thread_table[i].id != thread_id);
576 i++) {}
577 if (i >= MAX_THREADS) {
578 WARN("thread %ld not found on detach", (GC_word)thread_id);
579 } else {
580 thread_table[i].stack = 0;
581 thread_table[i].in_use = FALSE;
582 CloseHandle(thread_table[i].handle);
583 /* cast away volatile qualifier */
584 BZERO((void *) &thread_table[i].context, sizeof(CONTEXT));
586 UNLOCK();
588 break;
589 case DLL_PROCESS_DETACH:
591 int i;
593 LOCK();
594 for (i = 0; i < MAX_THREADS; ++i)
596 if (thread_table[i].in_use)
598 thread_table[i].stack = 0;
599 thread_table[i].in_use = FALSE;
600 CloseHandle(thread_table[i].handle);
601 BZERO((void *) &thread_table[i].context, sizeof(CONTEXT));
604 UNLOCK();
606 GC_deinit();
607 DeleteCriticalSection(&GC_allocate_ml);
609 break;
612 return TRUE;
615 # endif /* !MSWINCE */
617 #endif /* WIN32_THREADS */