D: Implement GCC emutls in druntime

* 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.

git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@270568 138bc75d-0d04-0410-961f-82ee72b054a4
diff --git a/libphobos/ChangeLog b/libphobos/ChangeLog
index 9d442f4..131d7f9 100644
--- a/libphobos/ChangeLog
+++ b/libphobos/ChangeLog
@@ -1,3 +1,12 @@
+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.
+
 2019-04-25  Iain Buclaw  <ibuclaw@gdcproject.org>
 
 	* testsuite/Makefile.am: Set PWD_COMMAND.
diff --git a/libphobos/libdruntime/Makefile.am b/libphobos/libdruntime/Makefile.am
index b981f23..bf9bff09 100644
--- a/libphobos/libdruntime/Makefile.am
+++ b/libphobos/libdruntime/Makefile.am
@@ -161,7 +161,8 @@
 	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 eb290b6..19ee94f 100644
--- a/libphobos/libdruntime/Makefile.in
+++ b/libphobos/libdruntime/Makefile.in
@@ -203,10 +203,10 @@
 	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 @@
 	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/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 0000000..461f20d
--- /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 0000000..580fdcb
--- /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 d92e4cd..3a2c85c 100644
--- a/libphobos/libdruntime/gcc/sections/elf_shared.d
+++ b/libphobos/libdruntime/gcc/sections/elf_shared.d
@@ -222,8 +222,16 @@
 
     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 @@
 
     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 @@
                 _handleToDSO.reset();
             }
             finiLocks();
+            version (GNU_EMUTLS)
+            {
+                import gcc.emutls;
+                _d_emutls_destroy();
+            }
         }
     }
 }
@@ -805,40 +826,46 @@
             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)
-            {
-                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);
-                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;
+                safeAssert(!pdso._tlsSize, "Multiple TLS segments in image header.");
+                static if (OS_Have_Dlpi_Tls_Modid)
+                {
+                    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);
+                    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;
+                }
             }
             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 0000000..44eb40c
--- /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();
+}