[PATCH, libphobos] Implement GCC emutls in druntime

Johannes Pfau johannespfau@gmail.com
Thu Apr 25 10:32:00 GMT 2019


This adds emulated TLS support to druntime.
The main problem here is that the D garbage collector needs
to be able to scan the TLS memory for pointers, which is not
possible with the current libgcc implementation.

This therefore reimplements the libgcc emutls
support (__emutls_get_address, ...) in druntime, taking
care to expose an interface for the GC to scan memory
(_d_emutls_scan). Due to the library link order, in most
cases D code will corrently call the implementation in
druntime. Where this does not work is if a C program
loads a D library, then the D library will continue to
use the libgcc implementation. This can be fixed in
GCC 10 in various ways, but for now this patch
will allow most use cases for MinGW, OSX and other
emutls targets to work.

In section code, the  _d_emutls_scan function and
_d_emutls_destroy functions need to be called. This
patch implements this for the elf_shared sections code.

Bootstrapped on x86_64 linux with --disable-tls, checked
testsuite with --disable-tls and without. Initial review
on https://github.com/D-Programming-GDC/gcc/pull/13


libphobos/ChangeLog:

2019-04-25  Johannes Pfau  <johannespfau@gmail.com>

        * libdruntime/Makefile.am: Add emutls and gthread files.
        * libdruntime/Makefile.in: Regenerate.
        * libdruntime/gcc/emutls.d: New file. Implement GC-compatible emutls.
        * libdruntime/gcc/gthread.d: New file.
        * libdruntime/gcc/sections/elf_shared.d: Integrate emutls support.
        * testsuite/libphobos.allocations/tls_gc_integration.d: New test for TLS.

---
 libphobos/libdruntime/Makefile.am             |   3 +-
 libphobos/libdruntime/Makefile.in             |  13 +-
 libphobos/libdruntime/gcc/emutls.d            | 316 ++++++++++++++++++
 libphobos/libdruntime/gcc/gthread.d           | 127 +++++++
 .../libdruntime/gcc/sections/elf_shared.d     |  89 +++--
 .../tls_gc_integration.d                      |  50 +++
 6 files changed, 561 insertions(+), 37 deletions(-)
 create mode 100644 libphobos/libdruntime/gcc/emutls.d
 create mode 100644 libphobos/libdruntime/gcc/gthread.d
 create mode 100644 libphobos/testsuite/libphobos.allocations/tls_gc_integration.d

diff --git a/libphobos/libdruntime/Makefile.am b/libphobos/libdruntime/Makefile.am
index b981f233d71..bf9bff095ca 100644
--- a/libphobos/libdruntime/Makefile.am
+++ b/libphobos/libdruntime/Makefile.am
@@ -161,7 +161,8 @@ DRUNTIME_DSOURCES = core/atomic.d core/attribute.d core/bitop.d \
 	core/sync/config.d core/sync/exception.d core/sync/mutex.d \
 	core/sync/rwmutex.d core/sync/semaphore.d core/thread.d core/time.d \
 	core/vararg.d gcc/attribute.d gcc/backtrace.d gcc/builtins.d gcc/deh.d \
-	gcc/sections/android.d gcc/sections/elf_shared.d gcc/sections/osx.d \
+	gcc/emutls.d gcc/gthread.d gcc/sections/android.d \
+	gcc/sections/elf_shared.d gcc/sections/osx.d \
 	gcc/sections/package.d gcc/sections/win32.d gcc/sections/win64.d \
 	gcc/unwind/arm.d gcc/unwind/arm_common.d gcc/unwind/c6x.d \
 	gcc/unwind/generic.d gcc/unwind/package.d gcc/unwind/pe.d object.d \
diff --git a/libphobos/libdruntime/Makefile.in b/libphobos/libdruntime/Makefile.in
index eb290b6a14f..19ee94fc370 100644
--- a/libphobos/libdruntime/Makefile.in
+++ b/libphobos/libdruntime/Makefile.in
@@ -203,10 +203,10 @@ am__objects_1 = core/atomic.lo core/attribute.lo core/bitop.lo \
 	core/sync/exception.lo core/sync/mutex.lo core/sync/rwmutex.lo \
 	core/sync/semaphore.lo core/thread.lo core/time.lo \
 	core/vararg.lo gcc/attribute.lo gcc/backtrace.lo \
-	gcc/builtins.lo gcc/deh.lo gcc/sections/android.lo \
-	gcc/sections/elf_shared.lo gcc/sections/osx.lo \
-	gcc/sections/package.lo gcc/sections/win32.lo \
-	gcc/sections/win64.lo gcc/unwind/arm.lo \
+	gcc/builtins.lo gcc/deh.lo gcc/emutls.lo gcc/gthread.lo \
+	gcc/sections/android.lo gcc/sections/elf_shared.lo \
+	gcc/sections/osx.lo gcc/sections/package.lo \
+	gcc/sections/win32.lo gcc/sections/win64.lo gcc/unwind/arm.lo \
 	gcc/unwind/arm_common.lo gcc/unwind/c6x.lo \
 	gcc/unwind/generic.lo gcc/unwind/package.lo gcc/unwind/pe.lo \
 	object.lo rt/aApply.lo rt/aApplyR.lo rt/aaA.lo rt/adi.lo \
@@ -757,7 +757,8 @@ DRUNTIME_DSOURCES = core/atomic.d core/attribute.d core/bitop.d \
 	core/sync/config.d core/sync/exception.d core/sync/mutex.d \
 	core/sync/rwmutex.d core/sync/semaphore.d core/thread.d core/time.d \
 	core/vararg.d gcc/attribute.d gcc/backtrace.d gcc/builtins.d gcc/deh.d \
-	gcc/sections/android.d gcc/sections/elf_shared.d gcc/sections/osx.d \
+	gcc/emutls.d gcc/gthread.d gcc/sections/android.d \
+	gcc/sections/elf_shared.d gcc/sections/osx.d \
 	gcc/sections/package.d gcc/sections/win32.d gcc/sections/win64.d \
 	gcc/unwind/arm.d gcc/unwind/arm_common.d gcc/unwind/c6x.d \
 	gcc/unwind/generic.d gcc/unwind/package.d gcc/unwind/pe.d object.d \
@@ -1104,6 +1105,8 @@ gcc/attribute.lo: gcc/$(am__dirstamp)
 gcc/backtrace.lo: gcc/$(am__dirstamp)
 gcc/builtins.lo: gcc/$(am__dirstamp)
 gcc/deh.lo: gcc/$(am__dirstamp)
+gcc/emutls.lo: gcc/$(am__dirstamp)
+gcc/gthread.lo: gcc/$(am__dirstamp)
 gcc/sections/$(am__dirstamp):
 	@$(MKDIR_P) gcc/sections
 	@: > gcc/sections/$(am__dirstamp)
diff --git a/libphobos/libdruntime/gcc/emutls.d b/libphobos/libdruntime/gcc/emutls.d
new file mode 100644
index 00000000000..461f20d9e28
--- /dev/null
+++ b/libphobos/libdruntime/gcc/emutls.d
@@ -0,0 +1,316 @@
+// GNU D Compiler emulated TLS routines.
+// Copyright (C) 2019 Free Software Foundation, Inc.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+// This code is based on the libgcc emutls.c emulated TLS support.
+
+module gcc.emutls;
+
+import core.atomic, core.stdc.stdlib, core.stdc.string, core.sync.mutex;
+import rt.util.container.array, rt.util.container.hashtab;
+import core.internal.traits : classInstanceAlignment;
+import gcc.builtins, gcc.gthread;
+
+version (GNU_EMUTLS): private:
+
+alias word = __builtin_machine_uint;
+alias pointer = __builtin_pointer_uint;
+alias TlsArray = Array!(void**);
+
+/*
+ * TLS control data emitted by GCC for every TLS variable.
+ */
+struct __emutls_object
+{
+    word size;
+    word align_;
+    union
+    {
+        pointer offset;
+        void* ptr;
+    }
+
+    ubyte* templ;
+}
+
+// Per-thread key to obtain the per-thread TLS variable array
+__gshared __gthread_key_t emutlsKey;
+// Largest, currently assigned TLS variable offset
+__gshared pointer emutlsMaxOffset = 0;
+// Contains the size of the TLS variables (for GC)
+__gshared Array!word emutlsSizes;
+// Contains the TLS variable array for single-threaded apps
+__gshared TlsArray singleArray;
+// List of all currently alive TlsArrays (for GC)
+__gshared HashTab!(TlsArray*, TlsArray*) emutlsArrays;
+
+// emutlsMutex Mutex + @nogc handling
+enum mutexAlign = classInstanceAlignment!Mutex;
+enum mutexClassInstanceSize = __traits(classInstanceSize, Mutex);
+__gshared align(mutexAlign) void[mutexClassInstanceSize] _emutlsMutex;
+
+@property Mutex emutlsMutex() nothrow @nogc
+{
+    return cast(Mutex) _emutlsMutex.ptr;
+}
+
+/*
+ * Global (de)initialization functions
+ */
+extern (C) void _d_emutls_init() nothrow @nogc
+{
+    memcpy(_emutlsMutex.ptr, typeid(Mutex).initializer.ptr, _emutlsMutex.length);
+    (cast(Mutex) _emutlsMutex.ptr).__ctor();
+
+    if (__gthread_key_create(&emutlsKey, &emutlsDestroyThread) != 0)
+        abort();
+}
+
+__gshared __gthread_once_t initOnce = GTHREAD_ONCE_INIT;
+
+/*
+ * emutls main entrypoint, called by GCC for each TLS variable access.
+ */
+extern (C) void* __emutls_get_address(shared __emutls_object* obj) nothrow @nogc
+{
+    pointer offset;
+    if (__gthread_active_p())
+    {
+        // Obtain the offset index into the TLS array (same for all-threads)
+        // for requested var. If it is unset, obtain a new offset index.
+        offset = atomicLoad!(MemoryOrder.acq, pointer)(obj.offset);
+        if (__builtin_expect(offset == 0, 0))
+        {
+            __gthread_once(&initOnce, &_d_emutls_init);
+            emutlsMutex.lock_nothrow();
+
+            offset = obj.offset;
+            if (offset == 0)
+            {
+                offset = ++emutlsMaxOffset;
+
+                emutlsSizes.ensureLength(offset);
+                // Note: it's important that we copy any data from obj and
+                // do not keep an reference to obj itself: If a library is
+                // unloaded, its tls variables are not removed from the arrays
+                // and the GC will still scan these. If we then try to reference
+                // a pointer to the data segment of an unloaded library, this
+                // will crash.
+                emutlsSizes[offset - 1] = obj.size;
+
+                atomicStore!(MemoryOrder.rel, pointer)(obj.offset, offset);
+            }
+            emutlsMutex.unlock_nothrow();
+        }
+    }
+    // For single-threaded systems, don't synchronize
+    else
+    {
+        if (__builtin_expect(obj.offset == 0, 0))
+        {
+            offset = ++emutlsMaxOffset;
+
+            emutlsSizes.ensureLength(offset);
+            emutlsSizes[offset - 1] = obj.size;
+
+            obj.offset = offset;
+        }
+    }
+
+    TlsArray* arr;
+    if (__gthread_active_p())
+        arr = cast(TlsArray*) __gthread_getspecific(emutlsKey);
+    else
+        arr = &singleArray;
+
+    // This will always be false for singleArray
+    if (__builtin_expect(arr == null, 0))
+    {
+        arr = mallocTlsArray(offset);
+        __gthread_setspecific(emutlsKey, arr);
+        emutlsMutex.lock_nothrow();
+        emutlsArrays[arr] = arr;
+        emutlsMutex.unlock_nothrow();
+    }
+    // Check if we have to grow the per-thread array
+    else if (__builtin_expect(offset > arr.length, 0))
+    {
+        (*arr).ensureLength(offset);
+    }
+
+    // Offset 0 is used as a not-initialized marker above. In the
+    // TLS array, we start at 0.
+    auto index = offset - 1;
+
+    // Get the per-thread pointer from the TLS array
+    void** ret = (*arr)[index];
+    if (__builtin_expect(ret == null, 0))
+    {
+        // Initial access, have to allocate the storage
+        ret = emutlsAlloc(obj);
+        (*arr)[index] = ret;
+    }
+
+    return ret;
+}
+
+// 1:1 copy from libgcc emutls.c
+extern (C) void __emutls_register_common(__emutls_object* obj, word size, word align_, ubyte* templ) nothrow @nogc
+{
+    if (obj.size < size)
+    {
+        obj.size = size;
+        obj.templ = null;
+    }
+    if (obj.align_ < align_)
+        obj.align_ = align_;
+    if (templ && size == obj.size)
+        obj.templ = templ;
+}
+
+// 1:1 copy from libgcc emutls.c
+void** emutlsAlloc(shared __emutls_object* obj) nothrow @nogc
+{
+    void* ptr;
+    void* ret;
+    enum pointerSize = (void*).sizeof;
+
+    /* We could use here posix_memalign if available and adjust
+     emutls_destroy accordingly.  */
+    if ((cast() obj).align_ <= pointerSize)
+    {
+        ptr = malloc((cast() obj).size + pointerSize);
+        if (ptr == null)
+            abort();
+        (cast(void**) ptr)[0] = ptr;
+        ret = ptr + pointerSize;
+    }
+    else
+    {
+        ptr = malloc(obj.size + pointerSize + obj.align_ - 1);
+        if (ptr == null)
+            abort();
+        ret = cast(void*)((cast(pointer)(ptr + pointerSize + obj.align_ - 1)) & ~cast(
+                pointer)(obj.align_ - 1));
+        (cast(void**) ret)[-1] = ptr;
+    }
+
+    if (obj.templ)
+        memcpy(ret, cast(ubyte*) obj.templ, cast() obj.size);
+    else
+        memset(ret, 0, cast() obj.size);
+
+    return cast(void**) ret;
+}
+
+/*
+ * When a thread has finished, remove the TLS array from the GC
+ * scan list emutlsArrays, free all allocated TLS variables and
+ * finally free the array.
+ */
+extern (C) void emutlsDestroyThread(void* ptr) nothrow @nogc
+{
+    auto arr = cast(TlsArray*) ptr;
+    emutlsMutex.lock_nothrow();
+    emutlsArrays.remove(arr);
+    emutlsMutex.unlock_nothrow();
+
+    foreach (entry; *arr)
+    {
+        if (entry)
+            free(entry[-1]);
+    }
+
+    free(arr);
+}
+
+/*
+ * Allocate a new TLS array, set length according to offset.
+ */
+TlsArray* mallocTlsArray(pointer offset = 0) nothrow @nogc
+{
+    static assert(TlsArray.alignof == (void*).alignof);
+    void[] data = malloc(TlsArray.sizeof)[0 .. TlsArray.sizeof];
+    if (data.ptr == null)
+        abort();
+
+    static immutable TlsArray init = TlsArray.init;
+    memcpy(data.ptr, &init, data.length);
+    (cast(TlsArray*) data).length = 32;
+    return cast(TlsArray*) data.ptr;
+}
+
+/*
+ * Make sure array is large enough to hold an entry for offset.
+ * Note: the array index will be offset - 1!
+ */
+void ensureLength(Value)(ref Array!(Value) arr, size_t offset) nothrow @nogc
+{
+    // index is offset-1
+    if (offset > arr.length)
+    {
+        auto newSize = arr.length * 2;
+        if (offset > newSize)
+            newSize = offset + 32;
+        arr.length = newSize;
+    }
+}
+
+// Public interface
+public:
+void _d_emutls_scan(scope void delegate(void* pbeg, void* pend) nothrow cb) nothrow
+{
+    void scanArray(scope TlsArray* arr) nothrow
+    {
+        foreach (index, entry; *arr)
+        {
+            auto ptr = cast(void*) entry;
+            if (ptr)
+                cb(ptr, ptr + emutlsSizes[index]);
+        }
+    }
+
+    __gthread_once(&initOnce, &_d_emutls_init);
+    emutlsMutex.lock_nothrow();
+    // this code is effectively nothrow
+    try
+    {
+        foreach (arr, value; emutlsArrays)
+        {
+            scanArray(arr);
+        }
+    }
+    catch (Exception)
+    {
+    }
+    emutlsMutex.unlock_nothrow();
+    scanArray(&singleArray);
+}
+
+// Call this after druntime has been unloaded
+void _d_emutls_destroy() nothrow @nogc
+{
+    if (__gthread_key_delete(emutlsKey) != 0)
+        abort();
+
+    (cast(Mutex) _emutlsMutex.ptr).__dtor();
+    destroy(emutlsArrays);
+}
diff --git a/libphobos/libdruntime/gcc/gthread.d b/libphobos/libdruntime/gcc/gthread.d
new file mode 100644
index 00000000000..580fdcb9b0d
--- /dev/null
+++ b/libphobos/libdruntime/gcc/gthread.d
@@ -0,0 +1,127 @@
+// GNU D Compiler thread support for emulated TLS routines.
+// Copyright (C) 2019 Free Software Foundation, Inc.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+module gcc.gthread;
+import gcc.config;
+
+extern (C) nothrow @nogc:
+
+alias GthreadDestroyFn = extern (C) void function(void*);
+alias GthreadOnceFn = extern (C) void function();
+
+static if (GNU_Thread_Model == ThreadModel.Posix)
+{
+    import core.sys.posix.pthread;
+
+    alias __gthread_key_create = pthread_key_create;
+    alias __gthread_key_delete = pthread_key_delete;
+    alias __gthread_getspecific = pthread_getspecific;
+    alias __gthread_setspecific = pthread_setspecific;
+    alias __gthread_once = pthread_once;
+    alias __gthread_key_t = pthread_key_t;
+    alias __gthread_once_t = pthread_once_t;
+    enum GTHREAD_ONCE_INIT = PTHREAD_ONCE_INIT;
+
+    // TODO: FreeBSD and Solaris exposes a dummy POSIX threads
+    // interface that will need to be handled here.
+    extern (D) int __gthread_active_p()
+    {
+        return 1;
+    }
+}
+else static if (GNU_Thread_Model == ThreadModel.Single)
+{
+    alias __gthread_key_t = int;
+    alias __gthread_once_t = int;
+    enum GTHREAD_ONCE_INIT = 0;
+
+    extern (D) int __gthread_key_create(__gthread_key_t*, GthreadDestroyFn)
+    {
+        return 0;
+    }
+
+    extern (D) int __gthread_key_delete(__gthread_key_t)
+    {
+        return 0;
+    }
+
+    extern (D) void* __gthread_getspecific(__gthread_key_t)
+    {
+        return null;
+    }
+
+    extern (D) int __gthread_setspecific(__gthread_key_t, void*)
+    {
+        return 0;
+    }
+
+    extern (D) int __gthread_once(__gthread_once_t*, GthreadOnceFn)
+    {
+        return 0;
+    }
+
+    extern (D) int __gthread_active_p()
+    {
+        return 0;
+    }
+}
+else static if (GNU_Thread_Model == ThreadModel.Win32)
+{
+    struct __gthread_once_t
+    {
+        INT done;
+        LONG started;
+    }
+
+    int __gthr_win32_key_create(__gthread_key_t* keyp, GthreadDestroyFn dtor);
+    int __gthr_win32_key_delete(__gthread_key_t key);
+    void* __gthr_win32_getspecific(__gthread_key_t key);
+    int __gthr_win32_setspecific(__gthread_key_t key, const void* ptr);
+    int __gthr_win32_once(__gthread_once_t* once, GthreadOnceFn);
+
+    alias __gthread_key_create = __gthr_win32_key_create;
+    alias __gthread_key_delete = __gthr_win32_key_delete;
+    alias __gthread_getspecific = __gthr_win32_getspecific;
+    alias __gthread_setspecific = __gthr_win32_setspecific;
+    alias __gthread_once = __gthr_win32_once;
+    enum GTHREAD_ONCE_INIT = __gthread_once_t(0, -1);
+    alias __gthread_key_t = c_ulong;
+
+    version (MinGW)
+    {
+        // Mingw runtime >= v0.3 provides a magic variable that is set to nonzero
+        // if -mthreads option was specified, or 0 otherwise.
+        extern __gshared int _CRT_MT;
+    }
+
+    extern (D) int __gthread_active_p()
+    {
+        version (MinGW)
+            return _CRT_MT;
+        else
+            return 1;
+    }
+}
+else
+{
+    static assert(false, "Not implemented");
+}
diff --git a/libphobos/libdruntime/gcc/sections/elf_shared.d b/libphobos/libdruntime/gcc/sections/elf_shared.d
index d92e4cd9c4e..3a2c85cba64 100644
--- a/libphobos/libdruntime/gcc/sections/elf_shared.d
+++ b/libphobos/libdruntime/gcc/sections/elf_shared.d
@@ -222,8 +222,16 @@ version (Shared)
 
     void scanTLSRanges(Array!(ThreadDSO)* tdsos, scope ScanDG dg) nothrow
     {
-        foreach (ref tdso; *tdsos)
-            dg(tdso._tlsRange.ptr, tdso._tlsRange.ptr + tdso._tlsRange.length);
+        version (GNU_EMUTLS)
+        {
+            import gcc.emutls;
+            _d_emutls_scan(dg);
+        }
+        else
+        {
+            foreach (ref tdso; *tdsos)
+                dg(tdso._tlsRange.ptr, tdso._tlsRange.ptr + tdso._tlsRange.length);
+        }
     }
 
     // interface for core.thread to inherit loaded libraries
@@ -310,8 +318,16 @@ else
 
     void scanTLSRanges(Array!(void[])* rngs, scope ScanDG dg) nothrow
     {
-        foreach (rng; *rngs)
-            dg(rng.ptr, rng.ptr + rng.length);
+        version (GNU_EMUTLS)
+        {
+            import gcc.emutls;
+            _d_emutls_scan(dg);
+        }
+        else
+        {
+            foreach (rng; *rngs)
+                dg(rng.ptr, rng.ptr + rng.length);
+        }
     }
 }
 
@@ -519,6 +535,11 @@ extern(C) void _d_dso_registry(CompilerDSOData* data)
                 _handleToDSO.reset();
             }
             finiLocks();
+            version (GNU_EMUTLS)
+            {
+                import gcc.emutls;
+                _d_emutls_destroy();
+            }
         }
     }
 }
@@ -805,40 +826,46 @@ void scanSegments(in ref dl_phdr_info info, DSO* pdso) nothrow @nogc
             break;
 
         case PT_TLS: // TLS segment
-            safeAssert(!pdso._tlsSize, "Multiple TLS segments in image header.");
-            static if (OS_Have_Dlpi_Tls_Modid)
+            version (GNU_EMUTLS)
             {
-                pdso._tlsMod = info.dlpi_tls_modid;
-                pdso._tlsSize = phdr.p_memsz;
             }
-            else version (Solaris)
+            else
             {
-                struct Rt_map
+                safeAssert(!pdso._tlsSize, "Multiple TLS segments in image header.");
+                static if (OS_Have_Dlpi_Tls_Modid)
                 {
-                    Link_map rt_public;
-                    const char* rt_pathname;
-                    c_ulong rt_padstart;
-                    c_ulong rt_padimlen;
-                    c_ulong rt_msize;
-                    uint rt_flags;
-                    uint rt_flags1;
-                    c_ulong rt_tlsmodid;
+                    pdso._tlsMod = info.dlpi_tls_modid;
+                    pdso._tlsSize = phdr.p_memsz;
                 }
+                else version (Solaris)
+                {
+                    struct Rt_map
+                    {
+                        Link_map rt_public;
+                        const char* rt_pathname;
+                        c_ulong rt_padstart;
+                        c_ulong rt_padimlen;
+                        c_ulong rt_msize;
+                        uint rt_flags;
+                        uint rt_flags1;
+                        c_ulong rt_tlsmodid;
+                    }
 
-                Rt_map* map;
-                version (Shared)
-                    dlinfo(handleForName(info.dlpi_name), RTLD_DI_LINKMAP, &map);
+                    Rt_map* map;
+                    version (Shared)
+                        dlinfo(handleForName(info.dlpi_name), RTLD_DI_LINKMAP, &map);
+                    else
+                        dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &map);
+                    // Until Solaris 11.4, tlsmodid for the executable is 0.
+                    // Let it start at 1 as the rest of the code expects.
+                    pdso._tlsMod = map.rt_tlsmodid + 1;
+                    pdso._tlsSize = phdr.p_memsz;
+                }
                 else
-                    dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &map);
-                // Until Solaris 11.4, tlsmodid for the executable is 0.
-                // Let it start at 1 as the rest of the code expects.
-                pdso._tlsMod = map.rt_tlsmodid + 1;
-                pdso._tlsSize = phdr.p_memsz;
-            }
-            else
-            {
-                pdso._tlsMod = 0;
-                pdso._tlsSize = 0;
+                {
+                    pdso._tlsMod = 0;
+                    pdso._tlsSize = 0;
+                }
             }
             break;
 
diff --git a/libphobos/testsuite/libphobos.allocations/tls_gc_integration.d b/libphobos/testsuite/libphobos.allocations/tls_gc_integration.d
new file mode 100644
index 00000000000..44eb40c366d
--- /dev/null
+++ b/libphobos/testsuite/libphobos.allocations/tls_gc_integration.d
@@ -0,0 +1,50 @@
+import core.memory, core.thread, core.bitop;
+
+/*
+ * This test repeatedly performs operations on GC-allocated objects which
+ * are only reachable from TLS storage. Tests are performed in multiple threads
+ * and GC collections are triggered repeatedly, so if the GC does not properly
+ * scan TLS memory, this provokes a crash.
+ */
+class TestTLS
+{
+    uint a;
+    void addNumber()
+    {
+        auto val = volatileLoad(&a);
+        val++;
+        volatileStore(&a, val);
+    }
+}
+
+TestTLS tlsPtr;
+
+static this()
+{
+    tlsPtr = new TestTLS();
+}
+
+void main()
+{
+    void runThread()
+    {
+        for (size_t i = 0; i < 100; i++)
+        {
+            Thread.sleep(10.msecs);
+            tlsPtr.addNumber();
+            GC.collect();
+        }
+    }
+
+    Thread[] threads;
+    for (size_t i = 0; i < 20; i++)
+    {
+        auto t = new Thread(&runThread);
+        threads ~= t;
+        t.start();
+    }
+    runThread();
+
+    foreach (thread; threads)
+        thread.join();
+}
-- 
2.19.2



More information about the Gcc-patches mailing list