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.

From-SVN: r270568
This commit is contained in:
Johannes Pfau 2019-04-25 11:11:39 +00:00 committed by Johannes Pfau
parent 7da021f080
commit 9168f22057
7 changed files with 573 additions and 40 deletions

View File

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

View File

@ -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 \

View File

@ -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)

View File

@ -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);
}

View File

@ -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");
}

View File

@ -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)
{
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;

View File

@ -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();
}