1 #if defined(GC_WIN32_THREADS)
3 #include "private/gc_priv.h"
10 #define MAX_THREADS 64
16 void *stack
; /* The cold end of the stack. */
17 /* 0 ==> entry not valid. */
18 /* !in_use ==> stack == 0 */
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. */
36 DWORD thread_id
= GetCurrentThreadId();
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
) {
44 /* SuspendThread will fail if thread is running kernel code */
45 while (SuspendThread(thread_table
[i
].handle
) == (DWORD
)-1)
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. */
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
));
62 if (SuspendThread(thread_table
[i
].handle
) == (DWORD
)-1)
63 ABORT("SuspendThread failed");
65 thread_table
[i
].suspended
= TRUE
;
71 DWORD thread_id
= GetCurrentThreadId();
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
;
84 # pragma warning(disable:4715)
86 ptr_t
GC_current_stackbottom()
88 DWORD thread_id
= GetCurrentThreadId();
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");
96 # pragma warning(default:4715)
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))
106 static ptr_t
GC_get_lo_stack_addr(ptr_t s
)
109 MEMORY_BASIC_INFORMATION info
;
110 VirtualQuery(s
, &info
, sizeof(info
));
112 bottom
= info
.BaseAddress
;
113 VirtualQuery(bottom
- 1, &info
, sizeof(info
));
114 } while ((info
.Protect
& PAGE_READWRITE
)
115 && !(info
.Protect
& PAGE_GUARD
));
120 void GC_push_all_stacks()
122 DWORD thread_id
= GetCurrentThreadId();
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
);
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");
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
);
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
);
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
);
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
);
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
);
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
);
301 --> architecture
not supported
312 void GC_get_next_stack(char *start
, char **lo
, char **hi
)
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
) {
326 if (current_min
== ADDR_LIMIT
) {
330 *lo
= GC_get_lo_stack_addr(current_min
);
331 if (*lo
< start
) *lo
= start
;
334 #if !defined(MSWINCE) && !(defined(__MINGW32__) && !defined(_DLL))
336 HANDLE WINAPI
GC_CreateThread(
337 LPSECURITY_ATTRIBUTES lpThreadAttributes
,
338 DWORD dwStackSize
, LPTHREAD_START_ROUTINE lpStartAddress
,
339 LPVOID lpParameter
, DWORD dwCreationFlags
, LPDWORD lpThreadId
)
341 return CreateThread(lpThreadAttributes
, dwStackSize
, lpStartAddress
,
342 lpParameter
, dwCreationFlags
, lpThreadId
);
345 #else /* !defined(MSWINCE) && !(defined(__MINGW32__) && !defined(_DLL)) */
348 HANDLE child_ready_h
, parent_ready_h
;
349 volatile struct thread_entry
* entry
;
350 LPTHREAD_START_ROUTINE start
;
354 DWORD WINAPI
thread_start(LPVOID arg
);
356 HANDLE WINAPI
GC_CreateThread(
357 LPSECURITY_ATTRIBUTES lpThreadAttributes
,
358 DWORD dwStackSize
, LPTHREAD_START_ROUTINE lpStartAddress
,
359 LPVOID lpParameter
, DWORD dwCreationFlags
, LPDWORD lpThreadId
)
361 HANDLE thread_h
= NULL
;
362 HANDLE child_ready_h
, parent_ready_h
;
367 /* allocate thread slot */
369 for (i
= 0; i
!= MAX_THREADS
&& thread_table
[i
].in_use
; i
++)
371 if (i
!= MAX_THREADS
) {
372 thread_table
[i
].in_use
= TRUE
;
376 if (i
!= MAX_THREADS
) {
378 /* create unnamed unsignalled events */
379 if (child_ready_h
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
)) {
380 if (parent_ready_h
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
)) {
382 /* set up thread arguments */
383 args
.child_ready_h
= child_ready_h
;
384 args
.parent_ready_h
= parent_ready_h
;
385 args
.entry
= &thread_table
[i
];
386 args
.start
= lpStartAddress
;
387 args
.param
= lpParameter
;
389 thread_h
= CreateThread(lpThreadAttributes
,
390 dwStackSize
, thread_start
,
392 dwCreationFlags
& ~CREATE_SUSPENDED
,
397 /* fill in ID and handle; tell child this is done */
398 thread_table
[i
].id
= *lpThreadId
;
399 thread_table
[i
].handle
= thread_h
;
400 SetEvent (parent_ready_h
);
402 /* wait for child to fill in stack and copy args */
403 WaitForSingleObject (child_ready_h
, INFINITE
);
405 /* suspend the child if requested */
406 if (dwCreationFlags
& CREATE_SUSPENDED
)
407 SuspendThread (thread_h
);
409 /* let child call given function now (or when resumed) */
410 SetEvent (parent_ready_h
);
413 CloseHandle (parent_ready_h
);
418 CloseHandle (child_ready_h
);
420 if (thread_h
== NULL
)
421 thread_table
[i
].in_use
= FALSE
;
423 } else { /* no thread slot found */
424 SetLastError (ERROR_TOO_MANY_TCBS
);
430 static DWORD WINAPI
thread_start(LPVOID arg
)
433 thread_args args
= *(thread_args
*)arg
;
435 /* wait for parent to fill in ID and handle */
436 WaitForSingleObject (args
.parent_ready_h
, INFINITE
);
437 ResetEvent (args
.parent_ready_h
);
439 /* fill in stack; tell parent this is done */
440 args
.entry
->stack
= GC_get_stack_base();
441 SetEvent (args
.child_ready_h
);
443 /* wait for parent to tell us to go (in case it needs to suspend us) */
444 WaitForSingleObject (args
.parent_ready_h
, INFINITE
);
445 CloseHandle (args
.parent_ready_h
);
447 /* Clear the thread entry even if we exit with an exception. */
448 /* This is probably pointless, since an uncaught exception is */
449 /* supposed to result in the process being killed. */
452 #endif /* __GNUC__ */
453 ret
= args
.start (args
.param
);
456 #endif /* __GNUC__ */
458 args
.entry
->stack
= 0;
459 args
.entry
->in_use
= FALSE
;
460 /* cast away volatile qualifier */
461 BZERO((void *) &args
.entry
->context
, sizeof(CONTEXT
));
465 #endif /* __GNUC__ */
469 #endif /* !defined(MSWINCE) && !(defined(__MINGW32__) && !defined(_DLL)) */
475 HINSTANCE hPrevInstance
;
480 DWORD WINAPI
main_thread_start(LPVOID arg
);
482 int WINAPI
WinMain(HINSTANCE hInstance
, HINSTANCE hPrevInstance
,
483 LPWSTR lpCmdLine
, int nShowCmd
)
487 main_thread_args args
= {
488 hInstance
, hPrevInstance
, lpCmdLine
, nShowCmd
493 /* initialize everything */
494 InitializeCriticalSection(&GC_allocate_ml
);
497 /* start the main thread */
498 thread_h
= GC_CreateThread(
499 NULL
, 0, main_thread_start
, &args
, 0, &thread_id
);
501 if (thread_h
!= NULL
)
503 WaitForSingleObject (thread_h
, INFINITE
);
504 GetExitCodeThread (thread_h
, &exit_code
);
505 CloseHandle (thread_h
);
509 DeleteCriticalSection(&GC_allocate_ml
);
511 return (int) exit_code
;
514 DWORD WINAPI
main_thread_start(LPVOID arg
)
516 main_thread_args
* args
= (main_thread_args
*) arg
;
518 return (DWORD
) GC_WinMain (args
->hInstance
, args
->hPrevInstance
,
519 args
->lpCmdLine
, args
->nShowCmd
);
522 # else /* !MSWINCE */
524 LONG WINAPI
GC_write_fault_handler(struct _EXCEPTION_POINTERS
*exc_info
);
527 * This isn't generally safe, since DllMain is not premptible.
528 * If another thread holds the lock while this runs we're in trouble.
529 * Pontus Rydin suggests wrapping the thread start routine instead.
531 BOOL WINAPI
DllMain(HINSTANCE inst
, ULONG reason
, LPVOID reserved
)
534 case DLL_PROCESS_ATTACH
:
535 InitializeCriticalSection(&GC_allocate_ml
);
536 GC_init(); /* Force initialization before thread attach. */
538 case DLL_THREAD_ATTACH
:
541 /* It appears to be unsafe to acquire a lock here, since this */
542 /* code is apparently not preeemptible on some systems. */
543 /* (This is based on complaints, not on Microsoft's official */
544 /* documentation, which says this should perform "only simple */
545 /* inititalization tasks".) */
546 /* Hence we make do with nonblocking synchronization. */
548 /* The following should be a noop according to the win32 */
549 /* documentation. There is empirical evidence that it */
552 if (GC_incremental
) SetUnhandledExceptionFilter(GC_write_fault_handler
);
556 /* cast away volatile qualifier */
557 InterlockedExchange((LPLONG
) &thread_table
[i
].in_use
, 1) != 0;
559 /* Compare-and-swap would make this cleaner, but that's not */
560 /* supported before Windows 98 and NT 4.0. In Windows 2000, */
561 /* InterlockedExchange is supposed to be replaced by */
562 /* InterlockedExchangePointer, but that's not really what I */
564 if (i
== MAX_THREADS
- 1)
565 ABORT("too many threads");
567 thread_table
[i
].id
= GetCurrentThreadId();
568 if (!DuplicateHandle(GetCurrentProcess(),
571 /* cast away volatile qualifier */
572 (HANDLE
*) &thread_table
[i
].handle
,
575 DUPLICATE_SAME_ACCESS
)) {
576 DWORD last_error
= GetLastError();
577 GC_printf1("Last error code: %lx\n", last_error
);
578 ABORT("DuplicateHandle failed");
580 thread_table
[i
].stack
= GC_get_stack_base();
581 /* If this thread is being created while we are trying to stop */
582 /* the world, wait here. Hopefully this can't happen on any */
583 /* systems that don't allow us to block here. */
584 while (GC_please_stop
) Sleep(20);
587 case DLL_THREAD_DETACH
:
590 DWORD thread_id
= GetCurrentThreadId();
594 (thread_table
[i
].stack
== 0 || thread_table
[i
].id
!= thread_id
);
596 if (i
>= MAX_THREADS
) {
597 WARN("thread %ld not found on detach", (GC_word
)thread_id
);
599 thread_table
[i
].stack
= 0;
600 thread_table
[i
].in_use
= FALSE
;
601 CloseHandle(thread_table
[i
].handle
);
602 /* cast away volatile qualifier */
603 BZERO((void *) &thread_table
[i
].context
, sizeof(CONTEXT
));
608 case DLL_PROCESS_DETACH
:
613 for (i
= 0; i
< MAX_THREADS
; ++i
)
615 if (thread_table
[i
].in_use
)
617 thread_table
[i
].stack
= 0;
618 thread_table
[i
].in_use
= FALSE
;
619 CloseHandle(thread_table
[i
].handle
);
620 BZERO((void *) &thread_table
[i
].context
, sizeof(CONTEXT
));
626 DeleteCriticalSection(&GC_allocate_ml
);
634 # endif /* !MSWINCE */
636 #endif /* GC_WIN32_THREADS */