1 // GNU D Compiler emulated TLS routines.
2 // Copyright (C) 2019-2020 Free Software Foundation, Inc.
4 // GCC is free software; you can redistribute it and/or modify it under
5 // the terms of the GNU General Public License as published by the Free
6 // Software Foundation; either version 3, or (at your option) any later
9 // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
10 // WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 // Under Section 7 of GPL version 3, you are granted additional
15 // permissions described in the GCC Runtime Library Exception, version
16 // 3.1, as published by the Free Software Foundation.
18 // You should have received a copy of the GNU General Public License and
19 // a copy of the GCC Runtime Library Exception along with this program;
20 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
21 // <http://www.gnu.org/licenses/>.
23 // This code is based on the libgcc emutls.c emulated TLS support.
27 import core.atomic, core.stdc.stdlib, core.stdc.string, core.sync.mutex;
28 import rt.util.container.array, rt.util.container.hashtab;
29 import core.internal.traits : classInstanceAlignment;
30 import gcc.builtins, gcc.gthread;
32 version (GNU_EMUTLS): private:
34 alias word = __builtin_machine_uint;
35 alias pointer = __builtin_pointer_uint;
36 alias TlsArray = Array!(void**);
39 * TLS control data emitted by GCC for every TLS variable.
41 struct __emutls_object
54 // Per-thread key to obtain the per-thread TLS variable array
55 __gshared __gthread_key_t emutlsKey;
56 // Largest, currently assigned TLS variable offset
57 __gshared pointer emutlsMaxOffset = 0;
58 // Contains the size of the TLS variables (for GC)
59 __gshared Array!word emutlsSizes;
60 // Contains the TLS variable array for single-threaded apps
61 __gshared TlsArray singleArray;
62 // List of all currently alive TlsArrays (for GC)
63 __gshared HashTab!(TlsArray*, TlsArray*) emutlsArrays;
65 // emutlsMutex Mutex + @nogc handling
66 enum mutexAlign = classInstanceAlignment!Mutex;
67 enum mutexClassInstanceSize = __traits(classInstanceSize, Mutex);
68 __gshared align(mutexAlign) void[mutexClassInstanceSize] _emutlsMutex;
70 @property Mutex emutlsMutex() nothrow @nogc
72 return cast(Mutex) _emutlsMutex.ptr;
76 * Global (de)initialization functions
78 extern (C) void _d_emutls_init() nothrow @nogc
80 memcpy(_emutlsMutex.ptr, typeid(Mutex).initializer.ptr, _emutlsMutex.length);
81 (cast(Mutex) _emutlsMutex.ptr).__ctor();
83 if (__gthread_key_create(&emutlsKey, &emutlsDestroyThread) != 0)
87 __gshared __gthread_once_t initOnce = GTHREAD_ONCE_INIT;
90 * emutls main entrypoint, called by GCC for each TLS variable access.
92 extern (C) void* __emutls_get_address(shared __emutls_object* obj) nothrow @nogc
95 if (__gthread_active_p())
97 // Obtain the offset index into the TLS array (same for all-threads)
98 // for requested var. If it is unset, obtain a new offset index.
99 offset = atomicLoad!(MemoryOrder.acq, pointer)(obj.offset);
100 if (__builtin_expect(offset == 0, 0))
102 __gthread_once(&initOnce, &_d_emutls_init);
103 emutlsMutex.lock_nothrow();
108 offset = ++emutlsMaxOffset;
110 emutlsSizes.ensureLength(offset);
111 // Note: it's important that we copy any data from obj and
112 // do not keep an reference to obj itself: If a library is
113 // unloaded, its tls variables are not removed from the arrays
114 // and the GC will still scan these. If we then try to reference
115 // a pointer to the data segment of an unloaded library, this
117 emutlsSizes[offset - 1] = obj.size;
119 atomicStore!(MemoryOrder.rel, pointer)(obj.offset, offset);
121 emutlsMutex.unlock_nothrow();
124 // For single-threaded systems, don't synchronize
127 if (__builtin_expect(obj.offset == 0, 0))
129 offset = ++emutlsMaxOffset;
131 emutlsSizes.ensureLength(offset);
132 emutlsSizes[offset - 1] = obj.size;
139 if (__gthread_active_p())
140 arr = cast(TlsArray*) __gthread_getspecific(emutlsKey);
144 // This will always be false for singleArray
145 if (__builtin_expect(arr == null, 0))
147 arr = mallocTlsArray(offset);
148 __gthread_setspecific(emutlsKey, arr);
149 emutlsMutex.lock_nothrow();
150 emutlsArrays[arr] = arr;
151 emutlsMutex.unlock_nothrow();
153 // Check if we have to grow the per-thread array
154 else if (__builtin_expect(offset > arr.length, 0))
156 (*arr).ensureLength(offset);
159 // Offset 0 is used as a not-initialized marker above. In the
160 // TLS array, we start at 0.
161 auto index = offset - 1;
163 // Get the per-thread pointer from the TLS array
164 void** ret = (*arr)[index];
165 if (__builtin_expect(ret == null, 0))
167 // Initial access, have to allocate the storage
168 ret = emutlsAlloc(obj);
175 // 1:1 copy from libgcc emutls.c
176 extern (C) void __emutls_register_common(__emutls_object* obj, word size, word align_, ubyte* templ) nothrow @nogc
183 if (obj.align_ < align_)
185 if (templ && size == obj.size)
189 // 1:1 copy from libgcc emutls.c
190 void** emutlsAlloc(shared __emutls_object* obj) nothrow @nogc
194 enum pointerSize = (void*).sizeof;
196 /* We could use here posix_memalign if available and adjust
197 emutls_destroy accordingly. */
198 if ((cast() obj).align_ <= pointerSize)
200 ptr = malloc((cast() obj).size + pointerSize);
203 (cast(void**) ptr)[0] = ptr;
204 ret = ptr + pointerSize;
208 ptr = malloc(obj.size + pointerSize + obj.align_ - 1);
211 ret = cast(void*)((cast(pointer)(ptr + pointerSize + obj.align_ - 1)) & ~cast(
212 pointer)(obj.align_ - 1));
213 (cast(void**) ret)[-1] = ptr;
217 memcpy(ret, cast(ubyte*) obj.templ, cast() obj.size);
219 memset(ret, 0, cast() obj.size);
221 return cast(void**) ret;
225 * When a thread has finished, remove the TLS array from the GC
226 * scan list emutlsArrays, free all allocated TLS variables and
227 * finally free the array.
229 extern (C) void emutlsDestroyThread(void* ptr) nothrow @nogc
231 auto arr = cast(TlsArray*) ptr;
232 emutlsMutex.lock_nothrow();
233 emutlsArrays.remove(arr);
234 emutlsMutex.unlock_nothrow();
236 foreach (entry; *arr)
246 * Allocate a new TLS array, set length according to offset.
248 TlsArray* mallocTlsArray(pointer offset = 0) nothrow @nogc
250 static assert(TlsArray.alignof == (void*).alignof);
251 void[] data = malloc(TlsArray.sizeof)[0 .. TlsArray.sizeof];
252 if (data.ptr == null)
255 static immutable TlsArray init = TlsArray.init;
256 memcpy(data.ptr, &init, data.length);
257 (cast(TlsArray*) data).length = 32;
258 return cast(TlsArray*) data.ptr;
262 * Make sure array is large enough to hold an entry for offset.
263 * Note: the array index will be offset - 1!
265 void ensureLength(Value)(ref Array!(Value) arr, size_t offset) nothrow @nogc
268 if (offset > arr.length)
270 auto newSize = arr.length * 2;
271 if (offset > newSize)
272 newSize = offset + 32;
273 arr.length = newSize;
279 void _d_emutls_scan(scope void delegate(void* pbeg, void* pend) nothrow cb) nothrow
281 void scanArray(scope TlsArray* arr) nothrow
283 foreach (index, entry; *arr)
285 auto ptr = cast(void*) entry;
287 cb(ptr, ptr + emutlsSizes[index]);
291 __gthread_once(&initOnce, &_d_emutls_init);
292 emutlsMutex.lock_nothrow();
293 // this code is effectively nothrow
296 foreach (arr, value; emutlsArrays)
304 emutlsMutex.unlock_nothrow();
305 scanArray(&singleArray);
308 // Call this after druntime has been unloaded
309 void _d_emutls_destroy() nothrow @nogc
311 if (__gthread_key_delete(emutlsKey) != 0)
314 (cast(Mutex) _emutlsMutex.ptr).__dtor();
315 destroy(emutlsArrays);