From: Ian Romanick Date: Wed, 13 Apr 2005 20:59:15 +0000 (+0000) Subject: Add TLS support to libGL and, by virtue of using glthread.h and GL_CALL, all X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=25fe93f0a11e6f4c8d470441ff91b9cddf7b3023;p=mesa.git Add TLS support to libGL and, by virtue of using glthread.h and GL_CALL, all DRI drivers. A TLS enabled libGL can load a TLS or a non-TLS DRI driver, but a TLS DRI driver requires a TLS enabled libGL. This fixes bug #1822. --- diff --git a/configs/linux-dri b/configs/linux-dri index 5d44ccd2fcd..0953fb223b2 100644 --- a/configs/linux-dri +++ b/configs/linux-dri @@ -16,6 +16,8 @@ MKDEP = /usr/X11R6/bin/makedepend WARN_FLAGS = -Wall OPT_FLAGS = -O -g PIC_FLAGS = -fPIC + +# Add '-DGLX_USE_TLS' to ARCH_FLAGS to enable TLS support. ARCH_FLAGS ?= DEFINES = -D_POSIX_SOURCE -D_POSIX_C_SOURCE=199309L -D_SVID_SOURCE \ diff --git a/src/glx/x11/dri_glx.c b/src/glx/x11/dri_glx.c index b703c4fa9eb..7ac80eb74d7 100644 --- a/src/glx/x11/dri_glx.c +++ b/src/glx/x11/dri_glx.c @@ -222,6 +222,15 @@ static __DRIdriver *OpenDriver(const char *driverName) void *handle = NULL; + /* If TLS support is enabled, try to open the TLS version of the driver + * binary first. If that fails, try the non-TLS version. + */ +#ifdef GLX_USE_TLS + snprintf(realDriverName, 200, "%s/tls/%s_dri.so", libDir, driverName); + InfoMessageF("OpenDriver: trying %s\n", realDriverName); + handle = dlopen(realDriverName, RTLD_NOW | RTLD_GLOBAL); +#endif + if ( handle == NULL ) { snprintf(realDriverName, 200, "%s/%s_dri.so", libDir, driverName); InfoMessageF("OpenDriver: trying %s\n", realDriverName); diff --git a/src/mesa/glapi/gl_x86_asm.py b/src/mesa/glapi/gl_x86_asm.py index 9c299933a96..442069b3629 100644 --- a/src/mesa/glapi/gl_x86_asm.py +++ b/src/mesa/glapi/gl_x86_asm.py @@ -63,7 +63,10 @@ class PrintGenericStubs(gl_XML.FilterGLAPISpecBase): print "* the symbol visibility mode to 'default'." print '*/' print '#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303' - print '#pragma GCC visibility push(default)' + print '# pragma GCC visibility push(default)' + print '# define HIDDEN(x) .hidden x' + print '#else' + print '# define HIDDEN(x)' print '#endif' print '' print '#ifndef __WIN32__' @@ -94,7 +97,17 @@ class PrintGenericStubs(gl_XML.FilterGLAPISpecBase): print '# define THREADS' print '#endif' print '' - print '#if defined(PTHREADS)' + print '#ifdef GLX_USE_TLS' + print '' + print '# define GL_STUB(fn,off,fn_alt)\t\t\t\\' + print 'ALIGNTEXT16;\t\t\t\t\t\t\\' + print 'GLOBL_FN(GL_PREFIX(fn, fn_alt));\t\t\t\\' + print 'GL_PREFIX(fn, fn_alt):\t\t\t\t\t\\' + print '\tCALL(_x86_get_dispatch) ;\t\t\t\\' + print '\tNOP ;\t\t\t\t\t\t\\' + print '\tJMP(GL_OFFSET(off))' + print '' + print '#elif defined(PTHREADS)' print '# define GL_STUB(fn,off,fn_alt)\t\t\t\\' print 'ALIGNTEXT16;\t\t\t\t\t\t\\' print 'GLOBL_FN(GL_PREFIX(fn, fn_alt));\t\t\t\\' @@ -136,7 +149,16 @@ class PrintGenericStubs(gl_XML.FilterGLAPISpecBase): print '' print 'SEG_TEXT' print '' - print '#ifdef PTHREADS' + print '#ifdef GLX_USE_TLS' + print '' + print '\tGLOBL\tGLNAME(_x86_get_dispatch)' + print '\tHIDDEN(GLNAME(_x86_get_dispatch))' + print 'ALIGNTEXT16' + print 'GLNAME(_x86_get_dispatch):' + print '\tmovl\t%gs:_glapi_tls_Dispatch@NTPOFF, %eax' + print '\tret' + print '' + print '#elif defined(PTHREADS)' print 'EXTERN GLNAME(_glapi_Dispatch)' print 'EXTERN GLNAME(_gl_DispatchTSD)' print 'EXTERN GLNAME(pthread_getspecific)' @@ -152,12 +174,38 @@ class PrintGenericStubs(gl_XML.FilterGLAPISpecBase): print 'EXTERN GLNAME(_glapi_get_dispatch)' print '#endif' print '' - print '\t\tALIGNTEXT16 ; GLOBL GLNAME(gl_dispatch_functions_start)' + + print '#if defined( GLX_USE_TLS )' + print '\t\t.section\twtext, "awx", @progbits' + print '#endif /* defined( GLX_USE_TLS ) */' + + print '' + print '\t\tALIGNTEXT16' + print '\t\tGLOBL GLNAME(gl_dispatch_functions_start)' + print '\t\tHIDDEN(GLNAME(gl_dispatch_functions_start))' print 'GLNAME(gl_dispatch_functions_start):' print '' return def printRealFooter(self): + print '' + print '\t\tGLOBL\tGLNAME(gl_dispatch_functions_end)' + print '\t\tHIDDEN(GLNAME(gl_dispatch_functions_end))' + print '\t\tALIGNTEXT16' + print 'GLNAME(gl_dispatch_functions_end):' + print '' + print '#if defined(GLX_USE_TLS) && defined(__linux__)' + print ' .section ".note.ABI-tag", "a"' + print ' .p2align 2' + print ' .long 1f - 0f /* name length */' + print ' .long 3f - 2f /* data length */' + print ' .long 1 /* note length */' + print '0: .asciz "GNU" /* vendor name */' + print '1: .p2align 2' + print '2: .long 0 /* note data: the ABI tag */' + print ' .long 2,4,20 /* Minimum kernel version w/TLS */' + print '3: .p2align 2 /* pad out section */' + print '#endif /* GLX_USE_TLS */' print '' print '#endif /* __WIN32__ */' return @@ -167,11 +215,11 @@ class PrintGenericStubs(gl_XML.FilterGLAPISpecBase): alt = "%s@%u" % (f.name, stack) if f.fn_alias == None: - print '\tGL_STUB(%s, _gloffset_%s, %s)' % (f.name, f.real_name, alt) + print '\tGL_STUB(%s, _gloffset_%s, %s)' % (f.name, f.real_name, alt) else: - alias_alt = "%s@%u" % (f.real_name, stack) - print '\tGL_STUB_ALIAS(%s, _gloffset_%s, %s, %s, %s)' % \ - (f.name, f.real_name, alt, f.real_name, alias_alt) + alias_alt = "%s@%u" % (f.real_name, stack) + print '\tGL_STUB_ALIAS(%s, _gloffset_%s, %s, %s, %s)' % \ + (f.name, f.real_name, alt, f.real_name, alias_alt) return def show_usage(): diff --git a/src/mesa/glapi/glapi.c b/src/mesa/glapi/glapi.c index bbc0a9608d0..8ed833042b2 100644 --- a/src/mesa/glapi/glapi.c +++ b/src/mesa/glapi/glapi.c @@ -61,6 +61,10 @@ static GLboolean WarnFlag = GL_FALSE; static _glapi_warning_func warning_func; +static void init_glapi_relocs(void); + +static _glapi_proc generate_entrypoint(GLuint functionOffset); +static void fill_in_entrypoint_offset(_glapi_proc entrypoint, GLuint offset); /* * Enable/disable printing of warning messages. @@ -133,6 +137,28 @@ static GLint NoOpUnused(void) #if defined(THREADS) +#if defined(GLX_USE_TLS) + +__thread struct _glapi_table * _glapi_tls_Dispatch + __attribute__((tls_model("initial-exec"))) + = (struct _glapi_table *) __glapi_noop_table; + +static __thread struct _glapi_table * _glapi_tls_RealDispatch + __attribute__((tls_model("initial-exec"))) + = (struct _glapi_table *) __glapi_noop_table; + +__thread void * _glapi_tls_Context + __attribute__((tls_model("initial-exec"))); + +/** + * Legacy per-thread dispatch pointer. This is only needed to support + * non-TLS DRI drivers. + */ + +_glthread_TSD _gl_DispatchTSD; + +#else + /** * \name Multi-threaded control support variables * @@ -166,6 +192,7 @@ static _glthread_TSD RealDispatchTSD; /**< only when using override */ static _glthread_TSD ContextTSD; /**< Per-thread context pointer */ /*@}*/ +#endif /* defined(GLX_USE_TLS) */ #define DISPATCH_TABLE_NAME __glapi_threadsafe_table #define UNUSED_TABLE_NAME __unused_threadsafe_functions @@ -184,6 +211,29 @@ static GLint glUnused(void) /***** END THREAD-SAFE DISPATCH *****/ +#if defined(GLX_USE_TLS) + +/** + * \name Old dispatch pointers + * + * Very old DRI based drivers assume that \c _glapi_Dispatch will never be + * \c NULL. Becuase of that, special "thread-safe" dispatch functions are + * needed here. Slightly more recent drivers detect the multi-threaded case + * by \c _glapi_DispatchTSD being \c NULL. + * + * \deprecated + * + * \warning + * \c _glapi_RealDispatch does not exist in TLS builds. I don't think it was + * ever used outside libGL.so, so this should be safe. + */ +/*@{*/ +PUBLIC const struct _glapi_table *_glapi_Dispatch = (struct _glapi_table *) __glapi_threadsafe_table; +PUBLIC const struct _glapi_table *_glapi_DispatchTSD = NULL; +PUBLIC const void *_glapi_Context = NULL; +/*@}*/ + +#else PUBLIC struct _glapi_table *_glapi_Dispatch = (struct _glapi_table *) __glapi_noop_table; #if defined( THREADS ) @@ -191,10 +241,11 @@ PUBLIC struct _glapi_table *_glapi_DispatchTSD = (struct _glapi_table *) __glapi #endif PUBLIC struct _glapi_table *_glapi_RealDispatch = (struct _glapi_table *) __glapi_noop_table; - /* Used when thread safety disabled */ PUBLIC void *_glapi_Context = NULL; +#endif /* defined(GLX_USE_TLS) */ + static GLboolean DispatchOverride = GL_FALSE; @@ -224,7 +275,7 @@ str_dup(const char *str) PUBLIC void _glapi_check_multithread(void) { -#if defined(THREADS) +#if defined(THREADS) && !defined(GLX_USE_TLS) if (!ThreadSafe) { static unsigned long knownID; static GLboolean firstCall = GL_TRUE; @@ -255,7 +306,9 @@ PUBLIC void _glapi_set_context(void *context) { (void) __unused_noop_functions; /* silence a warning */ -#if defined(THREADS) +#if defined(GLX_USE_TLS) + _glapi_tls_Context = context; +#elif defined(THREADS) (void) __unused_threadsafe_functions; /* silence a warning */ _glthread_SetTSD(&ContextTSD, context); _glapi_Context = (ThreadSafe) ? NULL : context; @@ -274,7 +327,9 @@ _glapi_set_context(void *context) PUBLIC void * _glapi_get_context(void) { -#if defined(THREADS) +#if defined(GLX_USE_TLS) + return _glapi_tls_Context; +#elif defined(THREADS) if (ThreadSafe) { return _glthread_GetTSD(&ContextTSD); } @@ -294,6 +349,13 @@ _glapi_get_context(void) PUBLIC void _glapi_set_dispatch(struct _glapi_table *dispatch) { +#if defined(PTHREADS) || defined(GLX_USE_TLS) + static pthread_once_t once_control = PTHREAD_ONCE_INIT; + + + pthread_once( & once_control, init_glapi_relocs ); +#endif + if (!dispatch) { /* use the no-op functions */ dispatch = (struct _glapi_table *) __glapi_noop_table; @@ -304,7 +366,15 @@ _glapi_set_dispatch(struct _glapi_table *dispatch) } #endif -#if defined(THREADS) +#if defined(GLX_USE_TLS) + if (DispatchOverride) { + _glapi_tls_RealDispatch = dispatch; + } + else { + _glthread_SetTSD(&_gl_DispatchTSD, (void *) dispatch); + _glapi_tls_Dispatch = dispatch; + } +#elif defined(THREADS) if (DispatchOverride) { _glthread_SetTSD(&RealDispatchTSD, (void *) dispatch); if (ThreadSafe) @@ -342,7 +412,13 @@ _glapi_set_dispatch(struct _glapi_table *dispatch) PUBLIC struct _glapi_table * _glapi_get_dispatch(void) { -#if defined(THREADS) +#if defined(GLX_USE_TLS) + struct _glapi_table * api = (DispatchOverride) + ? _glapi_tls_RealDispatch : _glapi_tls_Dispatch; + + assert( api != NULL ); + return api; +#elif defined(THREADS) if (ThreadSafe) { if (DispatchOverride) { return (struct _glapi_table *) _glthread_GetTSD(&RealDispatchTSD); @@ -399,7 +475,10 @@ _glapi_begin_dispatch_override(struct _glapi_table *override) _glapi_set_dispatch(real); -#if defined(THREADS) +#if defined(GLX_USE_TLS) + _glthread_SetTSD(&_gl_DispatchTSD, (void *) override); + _glapi_tls_Dispatch = override; +#elif defined(THREADS) _glthread_SetTSD(&_gl_DispatchTSD, (void *) override); if ( ThreadSafe ) { _glapi_Dispatch = (struct _glapi_table *) __glapi_threadsafe_table; @@ -424,10 +503,14 @@ _glapi_end_dispatch_override(int layer) DispatchOverride = GL_FALSE; _glapi_set_dispatch(real); /* the rest of this isn't needed, just play it safe */ -#if defined(THREADS) +#if defined(GLX_USE_TLS) + _glapi_tls_RealDispatch = NULL; +#else +# if defined(THREADS) _glthread_SetTSD(&RealDispatchTSD, NULL); -#endif +# endif _glapi_RealDispatch = NULL; +#endif } @@ -439,7 +522,9 @@ _glapi_get_override_dispatch(int layer) } else { if (DispatchOverride) { -#if defined(THREADS) +#if defined(GLX_USE_TLS) + return (struct _glapi_table *) _glapi_tls_Dispatch; +#elif defined(THREADS) return (struct _glapi_table *) _glthread_GetTSD(&_gl_DispatchTSD); #else return _glapi_Dispatch; @@ -498,9 +583,15 @@ get_static_proc_offset(const char *funcName) #ifdef USE_X86_ASM + +#if defined( GLX_USE_TLS ) +extern GLubyte gl_dispatch_functions_start[]; +extern GLubyte gl_dispatch_functions_end[]; +#else extern const GLubyte gl_dispatch_functions_start[]; +#endif -# if defined(THREADS) +# if defined(THREADS) && !defined(GLX_USE_TLS) # define X86_DISPATCH_FUNCTION_SIZE 32 # else # define X86_DISPATCH_FUNCTION_SIZE 16 @@ -603,45 +694,20 @@ static _glapi_proc generate_entrypoint(GLuint functionOffset) { #if defined(USE_X86_ASM) - /* - * This x86 code contributed by Josh Vanderhoof. - * - * 0: a1 10 32 54 76 movl __glapi_Dispatch,%eax - * 00 01 02 03 04 - * 5: 85 c0 testl %eax,%eax - * 05 06 - * 7: 74 06 je f - * 07 08 - * 9: ff a0 10 32 54 76 jmp *0x76543210(%eax) - * 09 0a 0b 0c 0d 0e - * f: e8 fc ff ff ff call __glapi_get_dispatch - * 0f 10 11 12 13 - * 14: ff a0 10 32 54 76 jmp *0x76543210(%eax) - * 14 15 16 17 18 19 + /* 32 is chosen as something of a magic offset. For x86, the dispatch + * at offset 32 is the first one where the offset in the + * "jmp OFFSET*4(%eax)" can't be encoded in a single byte. */ - static const unsigned char insn_template[] = { - 0xa1, 0x00, 0x00, 0x00, 0x00, - 0x85, 0xc0, - 0x74, 0x06, - 0xff, 0xa0, 0x00, 0x00, 0x00, 0x00, - 0xe8, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xa0, 0x00, 0x00, 0x00, 0x00 - }; - unsigned char *code = (unsigned char *) malloc(sizeof(insn_template)); - unsigned int next_insn; - if (code) { - memcpy(code, insn_template, sizeof(insn_template)); + const GLubyte * const template_func = gl_dispatch_functions_start + + (X86_DISPATCH_FUNCTION_SIZE * 32); + GLubyte * const code = (GLubyte *) malloc( X86_DISPATCH_FUNCTION_SIZE ); -#if defined( THREADS ) - *(unsigned int *)(code + 0x01) = (unsigned int)&_glapi_DispatchTSD; -#else - *(unsigned int *)(code + 0x01) = (unsigned int)&_glapi_Dispatch; -#endif - *(unsigned int *)(code + 0x0b) = (unsigned int)functionOffset * 4; - next_insn = (unsigned int)(code + 0x14); - *(unsigned int *)(code + 0x10) = (unsigned int)_glapi_get_dispatch - next_insn; - *(unsigned int *)(code + 0x16) = (unsigned int)functionOffset * 4; + + if ( code != NULL ) { + (void) memcpy( code, template_func, X86_DISPATCH_FUNCTION_SIZE ); + fill_in_entrypoint_offset( (_glapi_proc) code, functionOffset ); } + return (_glapi_proc) code; #elif defined(USE_SPARC_ASM) @@ -707,10 +773,19 @@ static void fill_in_entrypoint_offset(_glapi_proc entrypoint, GLuint offset) { #if defined(USE_X86_ASM) - - unsigned char *code = (unsigned char *) entrypoint; - *(unsigned int *)(code + 0x0b) = offset * 4; - *(unsigned int *)(code + 0x16) = offset * 4; + GLubyte * const code = (GLubyte *) entrypoint; + + +#if X86_DISPATCH_FUNCTION_SIZE == 32 + *((unsigned int *)(code + 11)) = 4 * offset; + *((unsigned int *)(code + 22)) = 4 * offset; +#elif X86_DISPATCH_FUNCTION_SIZE == 16 && defined( GLX_USE_TLS ) + *((unsigned int *)(code + 8)) = 4 * offset; +#elif X86_DISPATCH_FUNCTION_SIZE == 16 + *((unsigned int *)(code + 7)) = 4 * offset; +#else +# error Invalid X86_DISPATCH_FUNCTION_SIZE! +#endif #elif defined(USE_SPARC_ASM) @@ -1028,3 +1103,25 @@ _glapi_check_table(const struct _glapi_table *table) (void) table; #endif } + + +/** + * Perform platform-specific GL API entry-point fixups. + * + * + */ +static void +init_glapi_relocs( void ) +{ +#if defined( USE_X86_ASM ) && defined( GLX_USE_TLS ) + extern void * _x86_get_dispatch(void); + const GLubyte * const get_disp = (const GLubyte *) _x86_get_dispatch; + GLubyte * curr_func = (GLubyte *) gl_dispatch_functions_start; + + + while ( curr_func != (GLubyte *) gl_dispatch_functions_end ) { + (void) memcpy( curr_func, get_disp, 6 ); + curr_func += X86_DISPATCH_FUNCTION_SIZE; + } +#endif /* defined( USE_X86_ASM ) && defined( GLX_USE_TLS ) */ +} diff --git a/src/mesa/glapi/glapi.h b/src/mesa/glapi/glapi.h index 7cdccc12758..e3ef066e699 100644 --- a/src/mesa/glapi/glapi.h +++ b/src/mesa/glapi/glapi.h @@ -54,10 +54,17 @@ typedef void (*_glapi_warning_func)(void *ctx, const char *str, ...); typedef void (*_glapi_proc)(void); /* generic function pointer */ -extern void *_glapi_Context; +#if defined (GLX_USE_TLS) + +const extern void *_glapi_Context; +const extern struct _glapi_table *_glapi_Dispatch; +#else + +extern void *_glapi_Context; extern struct _glapi_table *_glapi_Dispatch; +#endif /* defined (GLX_USE_TLS) */ extern void _glapi_noop_enable_warnings(GLboolean enable); diff --git a/src/mesa/glapi/glthread.h b/src/mesa/glapi/glthread.h index 1d2a4a7b003..615d169986d 100644 --- a/src/mesa/glapi/glthread.h +++ b/src/mesa/glapi/glthread.h @@ -306,7 +306,14 @@ _glthread_GetTSD(_glthread_TSD *); extern void _glthread_SetTSD(_glthread_TSD *, void *); -#ifndef GL_CALL +#if defined(GLX_USE_TLS) + +extern __thread struct _glapi_table * _glapi_tls_Dispatch + __attribute__((tls_model("initial-exec"))); + +# define GL_CALL(name) (*(_glapi_tls_Dispatch-> name)) + +#elif !defined(GL_CALL) # if defined(THREADS) extern struct _glapi_table * _glapi_DispatchTSD; # define GL_CALL(name) \ diff --git a/src/mesa/x86/glapi_x86.S b/src/mesa/x86/glapi_x86.S index aee8565a0e1..c0a971bd53b 100644 --- a/src/mesa/x86/glapi_x86.S +++ b/src/mesa/x86/glapi_x86.S @@ -33,7 +33,10 @@ * the symbol visibility mode to 'default'. */ #if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303 -#pragma GCC visibility push(default) +# pragma GCC visibility push(default) +# define HIDDEN(x) .hidden x +#else +# define HIDDEN(x) #endif #ifndef __WIN32__ @@ -64,7 +67,17 @@ # define THREADS #endif -#if defined(PTHREADS) +#ifdef GLX_USE_TLS + +# define GL_STUB(fn,off,fn_alt) \ +ALIGNTEXT16; \ +GLOBL_FN(GL_PREFIX(fn, fn_alt)); \ +GL_PREFIX(fn, fn_alt): \ + CALL(_x86_get_dispatch) ; \ + NOP ; \ + JMP(GL_OFFSET(off)) + +#elif defined(PTHREADS) # define GL_STUB(fn,off,fn_alt) \ ALIGNTEXT16; \ GLOBL_FN(GL_PREFIX(fn, fn_alt)); \ @@ -106,7 +119,16 @@ GL_PREFIX(fn, fn_alt): \ SEG_TEXT -#ifdef PTHREADS +#ifdef GLX_USE_TLS + + GLOBL GLNAME(_x86_get_dispatch) + HIDDEN(GLNAME(_x86_get_dispatch)) +ALIGNTEXT16 +GLNAME(_x86_get_dispatch): + movl %gs:_glapi_tls_Dispatch@NTPOFF, %eax + ret + +#elif defined(PTHREADS) EXTERN GLNAME(_glapi_Dispatch) EXTERN GLNAME(_gl_DispatchTSD) EXTERN GLNAME(pthread_getspecific) @@ -122,7 +144,13 @@ GLNAME(_x86_get_dispatch): EXTERN GLNAME(_glapi_get_dispatch) #endif - ALIGNTEXT16 ; GLOBL GLNAME(gl_dispatch_functions_start) +#if defined( GLX_USE_TLS ) + .section wtext, "awx", @progbits +#endif /* defined( GLX_USE_TLS ) */ + + ALIGNTEXT16 + GLOBL GLNAME(gl_dispatch_functions_start) + HIDDEN(GLNAME(gl_dispatch_functions_start)) GLNAME(gl_dispatch_functions_start): GL_STUB(NewList, _gloffset_NewList, NewList@8) @@ -1120,4 +1148,22 @@ GLNAME(gl_dispatch_functions_start): GL_STUB_ALIAS(PointParameterfSGIS, _gloffset_PointParameterfEXT, PointParameterfSGIS@8, PointParameterfEXT, PointParameterfEXT@8) GL_STUB_ALIAS(PointParameterfvSGIS, _gloffset_PointParameterfvEXT, PointParameterfvSGIS@8, PointParameterfvEXT, PointParameterfvEXT@8) + GLOBL GLNAME(gl_dispatch_functions_end) + HIDDEN(GLNAME(gl_dispatch_functions_end)) + ALIGNTEXT16 +GLNAME(gl_dispatch_functions_end): + +#if defined(GLX_USE_TLS) && defined(__linux__) + .section ".note.ABI-tag", "a" + .p2align 2 + .long 1f - 0f /* name length */ + .long 3f - 2f /* data length */ + .long 1 /* note length */ +0: .asciz "GNU" /* vendor name */ +1: .p2align 2 +2: .long 0 /* note data: the ABI tag */ + .long 2,4,20 /* Minimum kernel version w/TLS */ +3: .p2align 2 /* pad out section */ +#endif /* GLX_USE_TLS */ + #endif /* __WIN32__ */