1/* Threads compatibility routines for libgcc2 and libobjc.  */
2/* Compile this one with gcc.  */
3
4/* Copyright (C) 1999-2013 Free Software Foundation, Inc.
5   Contributed by Mumit Khan <khan@xraylith.wisc.edu>.
6
7This file is part of GCC.
8
9GCC is free software; you can redistribute it and/or modify it under
10the terms of the GNU General Public License as published by the Free
11Software Foundation; either version 3, or (at your option) any later
12version.
13
14GCC is distributed in the hope that it will be useful, but WITHOUT ANY
15WARRANTY; without even the implied warranty of MERCHANTABILITY or
16FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
17for more details.
18
19Under Section 7 of GPL version 3, you are granted additional
20permissions described in the GCC Runtime Library Exception, version
213.1, as published by the Free Software Foundation.
22
23You should have received a copy of the GNU General Public License and
24a copy of the GCC Runtime Library Exception along with this program;
25see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
26<http://www.gnu.org/licenses/>.  */
27
28#ifndef GCC_GTHR_WIN32_H
29#define GCC_GTHR_WIN32_H
30
31/* Make sure CONST_CAST2 (origin in system.h) is declared.  */
32#ifndef CONST_CAST2
33#define CONST_CAST2(TOTYPE,FROMTYPE,X) ((__extension__(union {FROMTYPE _q; TOTYPE _nq;})(X))._nq)
34#endif
35
36/* Windows32 threads specific definitions. The windows32 threading model
37   does not map well into pthread-inspired gcc's threading model, and so
38   there are caveats one needs to be aware of.
39
40   1. The destructor supplied to __gthread_key_create is ignored for
41      generic x86-win32 ports. This will certainly cause memory leaks
42      due to unreclaimed eh contexts (sizeof (eh_context) is at least
43      24 bytes for x86 currently).
44
45      This memory leak may be significant for long-running applications
46      that make heavy use of C++ EH.
47
48      However, Mingw runtime (version 0.3 or newer) provides a mechanism
49      to emulate pthreads key dtors; the runtime provides a special DLL,
50      linked in if -mthreads option is specified, that runs the dtors in
51      the reverse order of registration when each thread exits. If
52      -mthreads option is not given, a stub is linked in instead of the
53      DLL, which results in memory leak. Other x86-win32 ports can use
54      the same technique of course to avoid the leak.
55
56   2. The error codes returned are non-POSIX like, and cast into ints.
57      This may cause incorrect error return due to truncation values on
58      hw where sizeof (DWORD) > sizeof (int).
59
60   3. We are currently using a special mutex instead of the Critical
61      Sections, since Win9x does not support TryEnterCriticalSection
62      (while NT does).
63
64   The basic framework should work well enough. In the long term, GCC
65   needs to use Structured Exception Handling on Windows32.  */
66
67#define __GTHREADS 1
68
69#include <errno.h>
70#ifdef __MINGW32__
71#include <_mingw.h>
72#endif
73
74#ifndef ___GLIBCXX_UNUSED_PARAM
75#define ___GLIBCXX_UNUSED_PARAM(x) x
76#endif
77
78#ifdef _LIBOBJC
79
80/* This is necessary to prevent windef.h (included from windows.h) from
81   defining its own BOOL as a typedef.  */
82#ifndef __OBJC__
83#define __OBJC__
84#endif
85#include <windows.h>
86/* Now undef the windows BOOL.  */
87#undef BOOL
88
89/* Key structure for maintaining thread specific storage */
90static DWORD	__gthread_objc_data_tls = (DWORD) -1;
91
92/* Backend initialization functions */
93
94/* Initialize the threads subsystem.  */
95int
96__gthread_objc_init_thread_system (void)
97{
98  /* Initialize the thread storage key.  */
99  if ((__gthread_objc_data_tls = TlsAlloc ()) != (DWORD) -1)
100    return 0;
101  else
102    return -1;
103}
104
105/* Close the threads subsystem.  */
106int
107__gthread_objc_close_thread_system (void)
108{
109  if (__gthread_objc_data_tls != (DWORD) -1)
110    TlsFree (__gthread_objc_data_tls);
111  return 0;
112}
113
114/* Backend thread functions */
115
116/* Create a new thread of execution.  */
117objc_thread_t
118__gthread_objc_thread_detach (void (*func)(void *arg), void *arg)
119{
120  DWORD	thread_id = 0;
121  HANDLE win32_handle;
122
123  if (!(win32_handle = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) func,
124				     arg, 0, &thread_id)))
125    thread_id = 0;
126
127  return (objc_thread_t) (INT_PTR) thread_id;
128}
129
130/* Set the current thread's priority.  */
131int
132__gthread_objc_thread_set_priority (int priority)
133{
134  int sys_priority = 0;
135
136  switch (priority)
137    {
138    case OBJC_THREAD_INTERACTIVE_PRIORITY:
139      sys_priority = THREAD_PRIORITY_NORMAL;
140      break;
141    default:
142    case OBJC_THREAD_BACKGROUND_PRIORITY:
143      sys_priority = THREAD_PRIORITY_BELOW_NORMAL;
144      break;
145    case OBJC_THREAD_LOW_PRIORITY:
146      sys_priority = THREAD_PRIORITY_LOWEST;
147      break;
148    }
149
150  /* Change priority */
151  if (SetThreadPriority (GetCurrentThread (), sys_priority))
152    return 0;
153  else
154    return -1;
155}
156
157/* Return the current thread's priority.  */
158int
159__gthread_objc_thread_get_priority (void)
160{
161  int sys_priority;
162
163  sys_priority = GetThreadPriority (GetCurrentThread ());
164
165  switch (sys_priority)
166    {
167    case THREAD_PRIORITY_HIGHEST:
168    case THREAD_PRIORITY_TIME_CRITICAL:
169    case THREAD_PRIORITY_ABOVE_NORMAL:
170    case THREAD_PRIORITY_NORMAL:
171      return OBJC_THREAD_INTERACTIVE_PRIORITY;
172
173    default:
174    case THREAD_PRIORITY_BELOW_NORMAL:
175      return OBJC_THREAD_BACKGROUND_PRIORITY;
176
177    case THREAD_PRIORITY_IDLE:
178    case THREAD_PRIORITY_LOWEST:
179      return OBJC_THREAD_LOW_PRIORITY;
180    }
181
182  /* Couldn't get priority.  */
183  return -1;
184}
185
186/* Yield our process time to another thread.  */
187void
188__gthread_objc_thread_yield (void)
189{
190  Sleep (0);
191}
192
193/* Terminate the current thread.  */
194int
195__gthread_objc_thread_exit (void)
196{
197  /* exit the thread */
198  ExitThread (__objc_thread_exit_status);
199
200  /* Failed if we reached here */
201  return -1;
202}
203
204/* Returns an integer value which uniquely describes a thread.  */
205objc_thread_t
206__gthread_objc_thread_id (void)
207{
208  return (objc_thread_t) (INT_PTR) GetCurrentThreadId ();
209}
210
211/* Sets the thread's local storage pointer.  */
212int
213__gthread_objc_thread_set_data (void *value)
214{
215  if (TlsSetValue (__gthread_objc_data_tls, value))
216    return 0;
217  else
218    return -1;
219}
220
221/* Returns the thread's local storage pointer.  */
222void *
223__gthread_objc_thread_get_data (void)
224{
225  DWORD lasterror;
226  void *ptr;
227
228  lasterror = GetLastError ();
229
230  ptr = TlsGetValue (__gthread_objc_data_tls);          /* Return thread data.  */
231
232  SetLastError (lasterror);
233
234  return ptr;
235}
236
237/* Backend mutex functions */
238
239/* Allocate a mutex.  */
240int
241__gthread_objc_mutex_allocate (objc_mutex_t mutex)
242{
243  if ((mutex->backend = (void *) CreateMutex (NULL, 0, NULL)) == NULL)
244    return -1;
245  else
246    return 0;
247}
248
249/* Deallocate a mutex.  */
250int
251__gthread_objc_mutex_deallocate (objc_mutex_t mutex)
252{
253  CloseHandle ((HANDLE) (mutex->backend));
254  return 0;
255}
256
257/* Grab a lock on a mutex.  */
258int
259__gthread_objc_mutex_lock (objc_mutex_t mutex)
260{
261  int status;
262
263  status = WaitForSingleObject ((HANDLE) (mutex->backend), INFINITE);
264  if (status != WAIT_OBJECT_0 && status != WAIT_ABANDONED)
265    return -1;
266  else
267    return 0;
268}
269
270/* Try to grab a lock on a mutex.  */
271int
272__gthread_objc_mutex_trylock (objc_mutex_t mutex)
273{
274  int status;
275
276  status = WaitForSingleObject ((HANDLE) (mutex->backend), 0);
277  if (status != WAIT_OBJECT_0 && status != WAIT_ABANDONED)
278    return -1;
279  else
280    return 0;
281}
282
283/* Unlock the mutex */
284int
285__gthread_objc_mutex_unlock (objc_mutex_t mutex)
286{
287  if (ReleaseMutex ((HANDLE) (mutex->backend)) == 0)
288    return -1;
289  else
290    return 0;
291}
292
293/* Backend condition mutex functions */
294
295/* Allocate a condition.  */
296int
297__gthread_objc_condition_allocate (objc_condition_t ___GLIBCXX_UNUSED_PARAM(condition))
298{
299  /* Unimplemented.  */
300  return -1;
301}
302
303/* Deallocate a condition.  */
304int
305__gthread_objc_condition_deallocate (objc_condition_t ___GLIBCXX_UNUSED_PARAM(condition))
306{
307  /* Unimplemented.  */
308  return -1;
309}
310
311/* Wait on the condition */
312int
313__gthread_objc_condition_wait (objc_condition_t ___GLIBCXX_UNUSED_PARAM(condition),
314			       objc_mutex_t ___GLIBCXX_UNUSED_PARAM(mutex))
315{
316  /* Unimplemented.  */
317  return -1;
318}
319
320/* Wake up all threads waiting on this condition.  */
321int
322__gthread_objc_condition_broadcast (objc_condition_t ___GLIBCXX_UNUSED_PARAM(condition))
323{
324  /* Unimplemented.  */
325  return -1;
326}
327
328/* Wake up one thread waiting on this condition.  */
329int
330__gthread_objc_condition_signal (objc_condition_t ___GLIBCXX_UNUSED_PARAM(condition))
331{
332  /* Unimplemented.  */
333  return -1;
334}
335
336#else /* _LIBOBJC */
337
338#ifdef __cplusplus
339extern "C" {
340#endif
341
342typedef unsigned long __gthread_key_t;
343
344typedef struct {
345  int done;
346  long started;
347} __gthread_once_t;
348
349typedef struct {
350  long counter;
351  void *sema;
352} __gthread_mutex_t;
353
354typedef struct {
355  long counter;
356  long depth;
357  unsigned long owner;
358  void *sema;
359} __gthread_recursive_mutex_t;
360
361#define __GTHREAD_ONCE_INIT {0, -1}
362#define __GTHREAD_MUTEX_INIT_FUNCTION __gthread_mutex_init_function
363#define __GTHREAD_MUTEX_INIT_DEFAULT {-1, 0}
364#define __GTHREAD_RECURSIVE_MUTEX_INIT_FUNCTION \
365  __gthread_recursive_mutex_init_function
366#define __GTHREAD_RECURSIVE_MUTEX_INIT_DEFAULT {-1, 0, 0, 0}
367
368#if defined (_WIN32) && !defined(__CYGWIN__)
369#define MINGW32_SUPPORTS_MT_EH 1
370/* Mingw runtime >= v0.3 provides a magic variable that is set to nonzero
371   if -mthreads option was specified, or 0 otherwise. This is to get around
372   the lack of weak symbols in PE-COFF.  */
373extern int _CRT_MT;
374extern int __mingwthr_key_dtor (unsigned long, void (*) (void *));
375#endif /* _WIN32 && !__CYGWIN__ */
376
377/* The Windows95 kernel does not export InterlockedCompareExchange.
378   This provides a substitute.   When building apps that reference
379   gthread_mutex_try_lock, the  __GTHREAD_I486_INLINE_LOCK_PRIMITIVES
380   macro  must be defined if Windows95 is a target.  Currently
381   gthread_mutex_try_lock is not referenced by libgcc or libstdc++.  */
382#ifdef __GTHREAD_I486_INLINE_LOCK_PRIMITIVES
383static inline long
384__gthr_i486_lock_cmp_xchg(long *__dest, long __xchg, long __comperand)
385{
386  long result;
387  __asm__ __volatile__ ("\n\
388	lock\n\
389	cmpxchg{l} {%4, %1|%1, %4}\n"
390	: "=a" (result), "=m" (*__dest)
391	: "0" (__comperand), "m" (*__dest), "r" (__xchg)
392	: "cc");
393  return result;
394}
395#define __GTHR_W32_InterlockedCompareExchange __gthr_i486_lock_cmp_xchg
396#else  /* __GTHREAD_I486_INLINE_LOCK_PRIMITIVES */
397#define __GTHR_W32_InterlockedCompareExchange InterlockedCompareExchange
398#endif /* __GTHREAD_I486_INLINE_LOCK_PRIMITIVES */
399
400static inline int
401__gthread_active_p (void)
402{
403#ifdef MINGW32_SUPPORTS_MT_EH
404  return _CRT_MT;
405#else
406  return 1;
407#endif
408}
409
410#if __GTHREAD_HIDE_WIN32API
411
412/* The implementations are in config/i386/gthr-win32.c in libgcc.a.
413   Only stubs are exposed to avoid polluting the C++ namespace with
414   windows api definitions.  */
415
416extern int __gthr_win32_once (__gthread_once_t *, void (*) (void));
417extern int __gthr_win32_key_create (__gthread_key_t *, void (*) (void*));
418extern int __gthr_win32_key_delete (__gthread_key_t);
419extern void * __gthr_win32_getspecific (__gthread_key_t);
420extern int __gthr_win32_setspecific (__gthread_key_t, const void *);
421extern void __gthr_win32_mutex_init_function (__gthread_mutex_t *);
422extern int __gthr_win32_mutex_lock (__gthread_mutex_t *);
423extern int __gthr_win32_mutex_trylock (__gthread_mutex_t *);
424extern int __gthr_win32_mutex_unlock (__gthread_mutex_t *);
425extern void
426  __gthr_win32_recursive_mutex_init_function (__gthread_recursive_mutex_t *);
427extern int __gthr_win32_recursive_mutex_lock (__gthread_recursive_mutex_t *);
428extern int
429  __gthr_win32_recursive_mutex_trylock (__gthread_recursive_mutex_t *);
430extern int __gthr_win32_recursive_mutex_unlock (__gthread_recursive_mutex_t *);
431extern void __gthr_win32_mutex_destroy (__gthread_mutex_t *);
432extern int
433  __gthr_win32_recursive_mutex_destroy (__gthread_recursive_mutex_t *);
434
435static inline int
436__gthread_once (__gthread_once_t *__once, void (*__func) (void))
437{
438  if (__gthread_active_p ())
439    return __gthr_win32_once (__once, __func);
440  else
441    return -1;
442}
443
444static inline int
445__gthread_key_create (__gthread_key_t *__key, void (*__dtor) (void *))
446{
447  return __gthr_win32_key_create (__key, __dtor);
448}
449
450static inline int
451__gthread_key_delete (__gthread_key_t __key)
452{
453  return __gthr_win32_key_delete (__key);
454}
455
456static inline void *
457__gthread_getspecific (__gthread_key_t __key)
458{
459  return __gthr_win32_getspecific (__key);
460}
461
462static inline int
463__gthread_setspecific (__gthread_key_t __key, const void *__ptr)
464{
465  return __gthr_win32_setspecific (__key, __ptr);
466}
467
468static inline void
469__gthread_mutex_init_function (__gthread_mutex_t *__mutex)
470{
471  __gthr_win32_mutex_init_function (__mutex);
472}
473
474static inline void
475__gthread_mutex_destroy (__gthread_mutex_t *__mutex)
476{
477  __gthr_win32_mutex_destroy (__mutex);
478}
479
480static inline int
481__gthread_mutex_lock (__gthread_mutex_t *__mutex)
482{
483  if (__gthread_active_p ())
484    return __gthr_win32_mutex_lock (__mutex);
485  else
486    return 0;
487}
488
489static inline int
490__gthread_mutex_trylock (__gthread_mutex_t *__mutex)
491{
492  if (__gthread_active_p ())
493    return __gthr_win32_mutex_trylock (__mutex);
494  else
495    return 0;
496}
497
498static inline int
499__gthread_mutex_unlock (__gthread_mutex_t *__mutex)
500{
501  if (__gthread_active_p ())
502    return __gthr_win32_mutex_unlock (__mutex);
503  else
504    return 0;
505}
506
507static inline void
508__gthread_recursive_mutex_init_function (__gthread_recursive_mutex_t *__mutex)
509{
510   __gthr_win32_recursive_mutex_init_function (__mutex);
511}
512
513static inline int
514__gthread_recursive_mutex_lock (__gthread_recursive_mutex_t *__mutex)
515{
516  if (__gthread_active_p ())
517    return __gthr_win32_recursive_mutex_lock (__mutex);
518  else
519    return 0;
520}
521
522static inline int
523__gthread_recursive_mutex_trylock (__gthread_recursive_mutex_t *__mutex)
524{
525  if (__gthread_active_p ())
526    return __gthr_win32_recursive_mutex_trylock (__mutex);
527  else
528    return 0;
529}
530
531static inline int
532__gthread_recursive_mutex_unlock (__gthread_recursive_mutex_t *__mutex)
533{
534  if (__gthread_active_p ())
535    return __gthr_win32_recursive_mutex_unlock (__mutex);
536  else
537    return 0;
538}
539
540static inline int
541__gthread_recursive_mutex_destroy (__gthread_recursive_mutex_t *__mutex)
542{
543  return __gthr_win32_recursive_mutex_destroy (__mutex);
544}
545
546#else /* ! __GTHREAD_HIDE_WIN32API */
547
548#include <windows.h>
549#include <errno.h>
550
551static inline int
552__gthread_once (__gthread_once_t *__once, void (*__func) (void))
553{
554  if (! __gthread_active_p ())
555    return -1;
556  else if (__once == NULL || __func == NULL)
557    return EINVAL;
558
559  if (! __once->done)
560    {
561      if (InterlockedIncrement (&(__once->started)) == 0)
562	{
563	  (*__func) ();
564	  __once->done = TRUE;
565	}
566      else
567	{
568	  /* Another thread is currently executing the code, so wait for it
569	     to finish; yield the CPU in the meantime.  If performance
570	     does become an issue, the solution is to use an Event that
571	     we wait on here (and set above), but that implies a place to
572	     create the event before this routine is called.  */
573	  while (! __once->done)
574	    Sleep (0);
575	}
576    }
577
578  return 0;
579}
580
581/* Windows32 thread local keys don't support destructors; this leads to
582   leaks, especially in threaded applications making extensive use of
583   C++ EH. Mingw uses a thread-support DLL to work-around this problem.  */
584static inline int
585__gthread_key_create (__gthread_key_t *__key,
586		      void (*__dtor) (void *) __attribute__((unused)))
587{
588  int __status = 0;
589  DWORD __tls_index = TlsAlloc ();
590  if (__tls_index != 0xFFFFFFFF)
591    {
592      *__key = __tls_index;
593#ifdef MINGW32_SUPPORTS_MT_EH
594      /* Mingw runtime will run the dtors in reverse order for each thread
595         when the thread exits.  */
596      __status = __mingwthr_key_dtor (*__key, __dtor);
597#endif
598    }
599  else
600    __status = (int) GetLastError ();
601  return __status;
602}
603
604static inline int
605__gthread_key_delete (__gthread_key_t __key)
606{
607  return (TlsFree (__key) != 0) ? 0 : (int) GetLastError ();
608}
609
610static inline void *
611__gthread_getspecific (__gthread_key_t __key)
612{
613  DWORD __lasterror;
614  void *__ptr;
615
616  __lasterror = GetLastError ();
617
618  __ptr = TlsGetValue (__key);
619
620  SetLastError (__lasterror);
621
622  return __ptr;
623}
624
625static inline int
626__gthread_setspecific (__gthread_key_t __key, const void *__ptr)
627{
628  if (TlsSetValue (__key, CONST_CAST2(void *, const void *, __ptr)) != 0)
629    return 0;
630  else
631    return GetLastError ();
632}
633
634static inline void
635__gthread_mutex_init_function (__gthread_mutex_t *__mutex)
636{
637  __mutex->counter = -1;
638  __mutex->sema = CreateSemaphore (NULL, 0, 65535, NULL);
639}
640
641static inline void
642__gthread_mutex_destroy (__gthread_mutex_t *__mutex)
643{
644  CloseHandle ((HANDLE) __mutex->sema);
645}
646
647static inline int
648__gthread_mutex_lock (__gthread_mutex_t *__mutex)
649{
650  int __status = 0;
651
652  if (__gthread_active_p ())
653    {
654      if (InterlockedIncrement (&__mutex->counter) == 0 ||
655	  WaitForSingleObject (__mutex->sema, INFINITE) == WAIT_OBJECT_0)
656	__status = 0;
657      else
658	{
659	  /* WaitForSingleObject returns WAIT_FAILED, and we can only do
660	     some best-effort cleanup here.  */
661	  InterlockedDecrement (&__mutex->counter);
662	  __status = 1;
663	}
664    }
665  return __status;
666}
667
668static inline int
669__gthread_mutex_trylock (__gthread_mutex_t *__mutex)
670{
671  int __status = 0;
672
673  if (__gthread_active_p ())
674    {
675      if (__GTHR_W32_InterlockedCompareExchange (&__mutex->counter, 0, -1) < 0)
676	__status = 0;
677      else
678	__status = 1;
679    }
680  return __status;
681}
682
683static inline int
684__gthread_mutex_unlock (__gthread_mutex_t *__mutex)
685{
686  if (__gthread_active_p ())
687    {
688      if (InterlockedDecrement (&__mutex->counter) >= 0)
689	return ReleaseSemaphore (__mutex->sema, 1, NULL) ? 0 : 1;
690    }
691  return 0;
692}
693
694static inline void
695__gthread_recursive_mutex_init_function (__gthread_recursive_mutex_t *__mutex)
696{
697  __mutex->counter = -1;
698  __mutex->depth = 0;
699  __mutex->owner = 0;
700  __mutex->sema = CreateSemaphore (NULL, 0, 65535, NULL);
701}
702
703static inline int
704__gthread_recursive_mutex_lock (__gthread_recursive_mutex_t *__mutex)
705{
706  if (__gthread_active_p ())
707    {
708      DWORD __me = GetCurrentThreadId();
709      if (InterlockedIncrement (&__mutex->counter) == 0)
710	{
711	  __mutex->depth = 1;
712	  __mutex->owner = __me;
713	}
714      else if (__mutex->owner == __me)
715	{
716	  InterlockedDecrement (&__mutex->counter);
717	  ++(__mutex->depth);
718	}
719      else if (WaitForSingleObject (__mutex->sema, INFINITE) == WAIT_OBJECT_0)
720	{
721	  __mutex->depth = 1;
722	  __mutex->owner = __me;
723	}
724      else
725	{
726	  /* WaitForSingleObject returns WAIT_FAILED, and we can only do
727	     some best-effort cleanup here.  */
728	  InterlockedDecrement (&__mutex->counter);
729	  return 1;
730	}
731    }
732  return 0;
733}
734
735static inline int
736__gthread_recursive_mutex_trylock (__gthread_recursive_mutex_t *__mutex)
737{
738  if (__gthread_active_p ())
739    {
740      DWORD __me = GetCurrentThreadId();
741      if (__GTHR_W32_InterlockedCompareExchange (&__mutex->counter, 0, -1) < 0)
742	{
743	  __mutex->depth = 1;
744	  __mutex->owner = __me;
745	}
746      else if (__mutex->owner == __me)
747	++(__mutex->depth);
748      else
749	return 1;
750    }
751  return 0;
752}
753
754static inline int
755__gthread_recursive_mutex_unlock (__gthread_recursive_mutex_t *__mutex)
756{
757  if (__gthread_active_p ())
758    {
759      --(__mutex->depth);
760      if (__mutex->depth == 0)
761	{
762	  __mutex->owner = 0;
763
764	  if (InterlockedDecrement (&__mutex->counter) >= 0)
765	    return ReleaseSemaphore (__mutex->sema, 1, NULL) ? 0 : 1;
766	}
767    }
768  return 0;
769}
770
771static inline int
772__gthread_recursive_mutex_destroy (__gthread_recursive_mutex_t *__mutex)
773{
774  CloseHandle ((HANDLE) __mutex->sema);
775  return 0;
776}
777
778#endif /*  __GTHREAD_HIDE_WIN32API */
779
780#ifdef __cplusplus
781}
782#endif
783
784#endif /* _LIBOBJC */
785
786#endif /* ! GCC_GTHR_WIN32_H */
787