1 // GNU D Compiler emulated TLS routines.
2 // Copyright (C) 2019-2025 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 core
.internal
.container
.array
;
29 import core
.internal
.container
.hashtab
;
30 import core
.internal
.traits
: classInstanceAlignment
;
31 import gcc
.builtins
, gcc
.gthread
;
33 version (GNU_EMUTLS
): private:
35 alias word
= __builtin_machine_uint
;
36 alias pointer
= __builtin_pointer_uint
;
37 alias TlsArray
= Array
!(void**);
40 * TLS control data emitted by GCC for every TLS variable.
42 struct __emutls_object
55 // Per-thread key to obtain the per-thread TLS variable array
56 __gshared __gthread_key_t emutlsKey
;
57 // Largest, currently assigned TLS variable offset
58 __gshared pointer emutlsMaxOffset
= 0;
59 // Contains the size of the TLS variables (for GC)
60 __gshared Array
!word emutlsSizes
;
61 // Contains the TLS variable array for single-threaded apps
62 __gshared TlsArray singleArray
;
63 // List of all currently alive TlsArrays (for GC)
64 __gshared HashTab
!(TlsArray
*, TlsArray
*) emutlsArrays
;
66 // emutlsMutex Mutex + @nogc handling
67 enum mutexAlign
= classInstanceAlignment
!Mutex
;
68 enum mutexClassInstanceSize
= __traits(classInstanceSize
, Mutex
);
69 __gshared
align(mutexAlign
) void[mutexClassInstanceSize
] _emutlsMutex
;
71 @property Mutex
emutlsMutex() nothrow @nogc
73 return cast(Mutex
) _emutlsMutex
.ptr
;
77 * Global (de)initialization functions
79 extern (C
) void _d_emutls_init() nothrow @nogc
81 memcpy(_emutlsMutex
.ptr
, typeid(Mutex
).initializer
.ptr
, _emutlsMutex
.length
);
82 (cast(Mutex
) _emutlsMutex
.ptr
).__ctor();
84 if (__gthread_key_create(&emutlsKey
, &emutlsDestroyThread
) != 0)
88 __gshared __gthread_once_t initOnce
= GTHREAD_ONCE_INIT
;
91 * emutls main entrypoint, called by GCC for each TLS variable access.
93 extern (C
) void* __emutls_get_address(shared __emutls_object
* obj
) nothrow @nogc
96 if (__gthread_active_p())
98 // Obtain the offset index into the TLS array (same for all-threads)
99 // for requested var. If it is unset, obtain a new offset index.
100 offset
= atomicLoad
!(MemoryOrder
.acq
, pointer
)(obj
.offset
);
101 if (__builtin_expect(offset
== 0, 0))
103 __gthread_once(&initOnce
, &_d_emutls_init
);
104 emutlsMutex
.lock_nothrow();
109 offset
= ++emutlsMaxOffset
;
111 emutlsSizes
.ensureLength(offset
);
112 // Note: it's important that we copy any data from obj and
113 // do not keep an reference to obj itself: If a library is
114 // unloaded, its tls variables are not removed from the arrays
115 // and the GC will still scan these. If we then try to reference
116 // a pointer to the data segment of an unloaded library, this
118 emutlsSizes
[offset
- 1] = obj
.size
;
120 atomicStore
!(MemoryOrder
.rel
, pointer
)(obj
.offset
, offset
);
122 emutlsMutex
.unlock_nothrow();
125 // For single-threaded systems, don't synchronize
128 if (__builtin_expect(obj
.offset
== 0, 0))
130 offset
= ++emutlsMaxOffset
;
132 emutlsSizes
.ensureLength(offset
);
133 emutlsSizes
[offset
- 1] = obj
.size
;
140 if (__gthread_active_p())
141 arr
= cast(TlsArray
*) __gthread_getspecific(emutlsKey
);
145 // This will always be false for singleArray
146 if (__builtin_expect(arr
== null, 0))
148 arr
= mallocTlsArray(offset
);
149 __gthread_setspecific(emutlsKey
, arr
);
150 emutlsMutex
.lock_nothrow();
151 emutlsArrays
[arr
] = arr
;
152 emutlsMutex
.unlock_nothrow();
154 // Check if we have to grow the per-thread array
155 else if (__builtin_expect(offset
> arr
.length
, 0))
157 (*arr
).ensureLength(offset
);
160 // Offset 0 is used as a not-initialized marker above. In the
161 // TLS array, we start at 0.
162 auto index
= offset
- 1;
164 // Get the per-thread pointer from the TLS array
165 void** ret = (*arr
)[index
];
166 if (__builtin_expect(ret == null, 0))
168 // Initial access, have to allocate the storage
169 ret = emutlsAlloc(obj
);
176 // 1:1 copy from libgcc emutls.c
177 extern (C
) void __emutls_register_common(__emutls_object
* obj
, word size
, word align_
, ubyte* templ
) nothrow @nogc
184 if (obj
.align_
< align_
)
186 if (templ
&& size
== obj
.size
)
190 // 1:1 copy from libgcc emutls.c
191 void** emutlsAlloc(shared __emutls_object
* obj
) nothrow @nogc
195 enum pointerSize
= (void*).sizeof
;
197 /* We could use here posix_memalign if available and adjust
198 emutls_destroy accordingly. */
199 if ((cast() obj
).align_
<= pointerSize
)
201 ptr
= malloc((cast() obj
).size
+ pointerSize
);
204 (cast(void**) ptr
)[0] = ptr
;
205 ret = ptr
+ pointerSize
;
209 ptr
= malloc(obj
.size
+ pointerSize
+ obj
.align_
- 1);
212 ret = cast(void*)((cast(pointer
)(ptr
+ pointerSize
+ obj
.align_
- 1)) & ~cast(
213 pointer
)(obj
.align_
- 1));
214 (cast(void**) ret)[-1] = ptr
;
218 memcpy(ret, cast(ubyte*) obj
.templ
, cast() obj
.size
);
220 memset(ret, 0, cast() obj
.size
);
222 return cast(void**) ret;
226 * When a thread has finished, free all allocated TLS variables and empty the
227 * array. The pointer is not free'd as it is stil referenced by the GC scan
228 * list emutlsArrays, which gets destroyed when druntime is unloaded.
230 extern (C
) void emutlsDestroyThread(void* ptr
) nothrow @nogc
232 auto arr
= cast(TlsArray
*) ptr
;
234 foreach (entry
; *arr
)
244 * Allocate a new TLS array, set length according to offset.
246 TlsArray
* mallocTlsArray(pointer offset
= 0) nothrow @nogc
248 static assert(TlsArray
.alignof
== (void*).alignof
);
249 void[] data
= malloc(TlsArray
.sizeof
)[0 .. TlsArray
.sizeof
];
250 if (data
.ptr
== null)
253 static immutable TlsArray init
= TlsArray
.init
;
254 memcpy(data
.ptr
, &init
, data
.length
);
255 (cast(TlsArray
*) data
).length
= 32;
256 return cast(TlsArray
*) data
.ptr
;
260 * Make sure array is large enough to hold an entry for offset.
261 * Note: the array index will be offset - 1!
263 void ensureLength(Value
)(ref Array
!(Value
) arr
, size_t offset
) nothrow @nogc
266 if (offset
> arr
.length
)
268 auto newSize
= arr
.length
* 2;
269 if (offset
> newSize
)
270 newSize
= offset
+ 32;
271 arr
.length
= newSize
;
277 void _d_emutls_scan(scope void delegate(void* pbeg
, void* pend
) nothrow cb
) nothrow
279 void scanArray(scope TlsArray
* arr
) nothrow
281 foreach (index
, entry
; *arr
)
283 auto ptr
= cast(void*) entry
;
285 cb(ptr
, ptr
+ emutlsSizes
[index
]);
289 __gthread_once(&initOnce
, &_d_emutls_init
);
290 emutlsMutex
.lock_nothrow();
291 // this code is effectively nothrow
294 foreach (arr
, value
; emutlsArrays
)
302 emutlsMutex
.unlock_nothrow();
303 scanArray(&singleArray
);
306 // Call this after druntime has been unloaded
307 void _d_emutls_destroy() nothrow @nogc
309 (cast(Mutex
) _emutlsMutex
.ptr
).__dtor();
310 destroy(emutlsArrays
);