Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/mimalloc/src/threadlocal.c
14369 views
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2019-2026, Microsoft Research, Daan Leijen
3
This is free software; you can redistribute it and/or modify it under the
4
terms of the MIT license. A copy of the license can be found in the file
5
"LICENSE" at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
8
/* ----------------------------------------------------------------------------
9
Implement dynamic thread local variables (for heap's).
10
Unlike most OS native implementations there is no limit on the number
11
that can be allocated.
12
-----------------------------------------------------------------------------*/
13
14
#include "mimalloc.h"
15
#include "mimalloc/internal.h"
16
#include "mimalloc/prim.h"
17
18
/* -----------------------------------------------------------
19
Each thread can have (a dynamically expanding) array of
20
thread-local values. Each slot has a value and a version.
21
The version is used to safely reuse slots.
22
----------------------------------------------------------- */
23
typedef struct mi_tls_slot_s {
24
size_t version;
25
void* value;
26
} mi_tls_slot_t;
27
28
typedef struct mi_thread_locals_s {
29
size_t count;
30
mi_tls_slot_t slots[1];
31
} mi_thread_locals_t;
32
33
static mi_thread_locals_t mi_thread_locals_empty = { 0, {{0,NULL}} };
34
35
mi_decl_thread mi_thread_locals_t* mi_thread_locals = &mi_thread_locals_empty; // always point to a valid `mi_thread_locals_t`
36
37
38
/* -----------------------------------------------------------
39
Each key consists of the slot index in the lower bits,
40
and its version it the top bits. When we get a value
41
the version must match or we return NULL. When we set
42
a value, we also set the version of the key.
43
----------------------------------------------------------- */
44
45
#define MI_TLS_IDX_BITS (MI_SIZE_BITS/2)
46
#define MI_TLS_IDX_MASK ((MI_ZU(1)<<MI_TLS_IDX_BITS)-1)
47
48
static size_t mi_key_index( size_t key ) {
49
return (key & MI_TLS_IDX_MASK);
50
}
51
52
static size_t mi_key_version( size_t key ) {
53
return (key >> MI_TLS_IDX_BITS);
54
}
55
56
static mi_thread_local_t mi_key_create( size_t index, size_t version ) {
57
mi_assert_internal(version != 0);
58
mi_assert_internal(index <= MI_TLS_IDX_MASK);
59
const mi_thread_local_t key = ((version << MI_TLS_IDX_BITS) | index);
60
mi_assert_internal(key != 0);
61
return key;
62
}
63
64
65
// dynamically reallocate the thread local slots when needed
66
static mi_thread_locals_t* mi_thread_locals_expand(size_t least_idx) {
67
mi_thread_locals_t* tls_old = mi_thread_locals;
68
const size_t count_old = tls_old->count;
69
size_t count;
70
if (count_old==0) {
71
tls_old = NULL; // so we allocate fresh from mi_thread_locals_empty
72
count = 16; // start with 16 slots
73
}
74
else if (count_old >= MI_TLS_IDX_MASK - 1024) {
75
return NULL; // too large
76
}
77
else if (count_old >= 1024) {
78
count = count_old + 1024; // at some point increase linearly
79
}
80
else {
81
count = 2*count_old; // and double initially
82
}
83
if (count <= least_idx) {
84
count = least_idx + 1;
85
}
86
mi_thread_locals_t* tls = (mi_thread_locals_t*)mi_rezalloc(tls_old, sizeof(mi_thread_locals_t) + count*sizeof(mi_tls_slot_t));
87
if mi_unlikely(tls==NULL) return NULL;
88
tls->count = count;
89
mi_thread_locals = tls;
90
return tls;
91
}
92
93
static mi_decl_noinline bool mi_thread_local_set_expand( mi_thread_local_t key, void* val ) {
94
if (val==NULL) return true;
95
const size_t idx = mi_key_index(key);
96
mi_thread_locals_t* tls = mi_thread_locals_expand(idx);
97
if (tls==NULL) return false;
98
mi_assert_internal(tls == mi_thread_locals);
99
mi_assert_internal(idx < tls->count);
100
tls->slots[idx].value = val;
101
tls->slots[idx].version = mi_key_version(key);
102
return true;
103
}
104
105
// set a tls slot; returns `true` if successful.
106
// Can return `false` if we could not reallocate the slots array.
107
bool _mi_thread_local_set( mi_thread_local_t key, void* val ) {
108
mi_thread_locals_t* tls = mi_thread_locals;
109
mi_assert_internal(tls!=NULL);
110
mi_assert_internal(key!=0);
111
const size_t idx = mi_key_index(key);
112
if mi_likely(idx < tls->count) {
113
tls->slots[idx].value = val;
114
tls->slots[idx].version = mi_key_version(key);
115
return true;
116
}
117
else {
118
return mi_thread_local_set_expand( key, val ); // tailcall
119
}
120
}
121
122
// get a tls slot value
123
void* _mi_thread_local_get( mi_thread_local_t key ) {
124
const mi_thread_locals_t* const tls = mi_thread_locals;
125
mi_assert_internal(tls!=NULL);
126
mi_assert_internal(key!=0);
127
const size_t idx = mi_key_index(key);
128
if mi_likely(idx < tls->count && mi_key_version(key) == tls->slots[idx].version) {
129
return tls->slots[idx].value;
130
}
131
else {
132
return NULL;
133
}
134
}
135
136
void _mi_thread_locals_thread_done(void) {
137
mi_thread_locals_t* const tls = mi_thread_locals;
138
if (tls!=NULL && tls->count > 0) {
139
mi_free(tls);
140
mi_thread_locals = &mi_thread_locals_empty;
141
}
142
}
143
144
/* -----------------------------------------------------------
145
Create and free fresh TLS key's
146
----------------------------------------------------------- */
147
#include "bitmap.h"
148
149
static mi_lock_t mi_thread_locals_lock; // we need a lock in order to re-allocate the slot bits
150
static mi_bitmap_t* mi_thread_locals_free; // reuse an arena bitmap to track which slots were assigned (1=free, 0=in-use)
151
static size_t mi_thread_locals_version; // version to be able to reuse slots safely
152
153
void _mi_thread_locals_init(void) {
154
mi_lock_init(&mi_thread_locals_lock);
155
}
156
157
void _mi_thread_locals_done(void) {
158
mi_lock(&mi_thread_locals_lock) {
159
mi_bitmap_t* const slots = mi_thread_locals_free;
160
mi_free(slots);
161
}
162
mi_lock_done(&mi_thread_locals_lock);
163
}
164
165
// strange signature but allows us to reuse the arena code for claiming free pages
166
static bool mi_thread_local_claim_fun(size_t _slice_index, mi_arena_t* _arena, bool* keep_set) {
167
MI_UNUSED(_slice_index); MI_UNUSED(_arena);
168
*keep_set = false;
169
return true;
170
}
171
172
// When we claim a free slot, we increase the global version counter
173
// (so if we reuse a slot it will be returning NULL initially when a thread tries to get it)
174
static mi_thread_local_t mi_thread_local_claim(void) {
175
size_t idx = 0;
176
if (mi_thread_locals_free != NULL && mi_bitmap_try_find_and_claim(mi_thread_locals_free,0,&idx,&mi_thread_local_claim_fun,NULL)) {
177
mi_thread_locals_version++;
178
if (mi_thread_locals_version == SIZE_MAX/2) { mi_thread_locals_version = 1; }
179
return mi_key_create( idx, mi_thread_locals_version);
180
}
181
else {
182
return 0;
183
}
184
}
185
186
static bool mi_thread_local_create_expand(void) {
187
mi_bitmap_t* slots = mi_thread_locals_free;
188
// 1024 bits at a time
189
const size_t oldcount = (slots==NULL ? 0 : mi_bitmap_max_bits(slots));
190
const size_t newcount = 1024 + oldcount;
191
if (newcount > MI_TLS_IDX_MASK) { return false; }
192
const size_t newsize = mi_bitmap_size( newcount, NULL );
193
slots = (mi_bitmap_t*)mi_realloc_aligned(slots, newsize, MI_BCHUNK_SIZE);
194
if (slots == NULL) { return false; }
195
mi_bitmap_init(slots, newcount, true /* or otherwise we would zero all old entries */);
196
mi_bitmap_unsafe_setN(slots, oldcount, newcount - oldcount);
197
mi_thread_locals_free = slots;
198
return true;
199
}
200
201
202
// create a fresh key
203
mi_thread_local_t _mi_thread_local_create(void) {
204
mi_thread_local_t key = 0;
205
mi_lock(&mi_thread_locals_lock) {
206
key = mi_thread_local_claim();
207
if (key==0) {
208
if (mi_thread_local_create_expand()) {
209
key = mi_thread_local_claim();
210
}
211
}
212
}
213
return key;
214
}
215
216
// free a key
217
void _mi_thread_local_free(mi_thread_local_t key) {
218
if (key==0) return;
219
const size_t idx = mi_key_index(key);
220
mi_lock(&mi_thread_locals_lock) {
221
mi_bitmap_t* const slots = mi_thread_locals_free;
222
if (slots!=NULL && idx < mi_bitmap_max_bits(slots)) {
223
mi_bitmap_set(slots,idx);
224
}
225
}
226
}
227
228
229