Branch data Line data Source code
1 : : // This file is a part of Julia. License is MIT: https://julialang.org/license
2 : :
3 : : #include "gc.h"
4 : : #ifndef _OS_WINDOWS_
5 : : # include <sys/resource.h>
6 : : #endif
7 : :
8 : : #ifdef _P64
9 : : # ifdef _OS_WINDOWS_
10 : : # define MAX_STACK_MAPPINGS 500
11 : : # else
12 : : # define MAX_STACK_MAPPINGS 30000
13 : : # endif
14 : : #else
15 : : # ifdef _OS_WINDOWS_
16 : : # define MAX_STACK_MAPPINGS 250
17 : : # else
18 : : # define MAX_STACK_MAPPINGS 500
19 : : # endif
20 : : #endif
21 : :
22 : : // number of stacks to always keep available per pool
23 : : #define MIN_STACK_MAPPINGS_PER_POOL 5
24 : :
25 : : const size_t jl_guard_size = (4096 * 8);
26 : : static _Atomic(uint32_t) num_stack_mappings = 0;
27 : :
28 : : #ifdef _OS_WINDOWS_
29 : : #define MAP_FAILED NULL
30 : : static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT
31 : : {
32 : : void *stk = VirtualAlloc(NULL, bufsz, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
33 : : if (stk == NULL)
34 : : return MAP_FAILED;
35 : : DWORD dwOldProtect;
36 : : if (!VirtualProtect(stk, jl_guard_size, PAGE_READWRITE | PAGE_GUARD, &dwOldProtect)) {
37 : : VirtualFree(stk, 0, MEM_RELEASE);
38 : : return MAP_FAILED;
39 : : }
40 : : jl_atomic_fetch_add(&num_stack_mappings, 1);
41 : : return stk;
42 : : }
43 : :
44 : :
45 : : static void free_stack(void *stkbuf, size_t bufsz)
46 : : {
47 : : VirtualFree(stkbuf, 0, MEM_RELEASE);
48 : : jl_atomic_fetch_add(&num_stack_mappings, -1);
49 : : }
50 : :
51 : : #else
52 : :
53 : 71 : static void *malloc_stack(size_t bufsz) JL_NOTSAFEPOINT
54 : : {
55 : 71 : void* stk = mmap(0, bufsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
56 [ - + ]: 71 : if (stk == MAP_FAILED)
57 : 0 : return MAP_FAILED;
58 : : #if !defined(JL_HAVE_UCONTEXT) && !defined(JL_HAVE_SIGALTSTACK)
59 : : // setup a guard page to detect stack overflow
60 [ - + ]: 71 : if (mprotect(stk, jl_guard_size, PROT_NONE) == -1) {
61 : 0 : munmap(stk, bufsz);
62 : 0 : return MAP_FAILED;
63 : : }
64 : : #endif
65 : 71 : jl_atomic_fetch_add(&num_stack_mappings, 1);
66 : 71 : return stk;
67 : : }
68 : :
69 : 9 : static void free_stack(void *stkbuf, size_t bufsz)
70 : : {
71 : 9 : munmap(stkbuf, bufsz);
72 : 9 : jl_atomic_fetch_add(&num_stack_mappings, -1);
73 : 9 : }
74 : : #endif
75 : :
76 : :
77 : : const unsigned pool_sizes[] = {
78 : : 128 * 1024,
79 : : 192 * 1024,
80 : : 256 * 1024,
81 : : 384 * 1024,
82 : : 512 * 1024,
83 : : 768 * 1024,
84 : : 1024 * 1024,
85 : : 1537 * 1024,
86 : : 2048 * 1024,
87 : : 3 * 1024 * 1024,
88 : : 4 * 1024 * 1024,
89 : : 6 * 1024 * 1024,
90 : : 8 * 1024 * 1024,
91 : : 12 * 1024 * 1024,
92 : : 16 * 1024 * 1024,
93 : : 24 * 1024 * 1024,
94 : : };
95 : :
96 : : static_assert(sizeof(pool_sizes) == JL_N_STACK_POOLS * sizeof(pool_sizes[0]), "JL_N_STACK_POOLS size mismatch");
97 : :
98 : 308 : static unsigned select_pool(size_t nb) JL_NOTSAFEPOINT
99 : : {
100 : 308 : unsigned pool_id = 0;
101 [ + + ]: 3418 : while (pool_sizes[pool_id] < nb)
102 : 3110 : pool_id++;
103 : 308 : return pool_id;
104 : : }
105 : :
106 : :
107 : 0 : static void _jl_free_stack(jl_ptls_t ptls, void *stkbuf, size_t bufsz)
108 : : {
109 [ # # ]: 0 : if (bufsz <= pool_sizes[JL_N_STACK_POOLS - 1]) {
110 : 0 : unsigned pool_id = select_pool(bufsz);
111 [ # # ]: 0 : if (pool_sizes[pool_id] == bufsz) {
112 : 0 : arraylist_push(&ptls->heap.free_stacks[pool_id], stkbuf);
113 : 0 : return;
114 : : }
115 : : }
116 : 0 : free_stack(stkbuf, bufsz);
117 : : }
118 : :
119 : :
120 : 0 : JL_DLLEXPORT void jl_free_stack(void *stkbuf, size_t bufsz)
121 : : {
122 : 0 : jl_task_t *ct = jl_current_task;
123 : 0 : _jl_free_stack(ct->ptls, stkbuf, bufsz);
124 : 0 : }
125 : :
126 : :
127 : 130 : void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task)
128 : : {
129 : : // avoid adding an original thread stack to the free list
130 [ - + - - ]: 130 : if (task == ptls->root_task && !task->copy_stack)
131 : 0 : return;
132 : 130 : void *stkbuf = task->stkbuf;
133 : 130 : size_t bufsz = task->bufsz;
134 [ + - ]: 130 : if (bufsz <= pool_sizes[JL_N_STACK_POOLS - 1]) {
135 : 130 : unsigned pool_id = select_pool(bufsz);
136 [ + - ]: 130 : if (pool_sizes[pool_id] == bufsz) {
137 : 130 : task->stkbuf = NULL;
138 : 130 : arraylist_push(&ptls->heap.free_stacks[pool_id], stkbuf);
139 : : }
140 : : }
141 : : }
142 : :
143 : :
144 : 178 : JL_DLLEXPORT void *jl_malloc_stack(size_t *bufsz, jl_task_t *owner) JL_NOTSAFEPOINT
145 : : {
146 : 178 : jl_task_t *ct = jl_current_task;
147 : 178 : jl_ptls_t ptls = ct->ptls;
148 : 178 : size_t ssize = *bufsz;
149 : 178 : void *stk = NULL;
150 [ + - ]: 178 : if (ssize <= pool_sizes[JL_N_STACK_POOLS - 1]) {
151 : 178 : unsigned pool_id = select_pool(ssize);
152 : 178 : ssize = pool_sizes[pool_id];
153 : 178 : arraylist_t *pool = &ptls->heap.free_stacks[pool_id];
154 [ + + ]: 178 : if (pool->len > 0) {
155 : 107 : stk = arraylist_pop(pool);
156 : : }
157 : : }
158 : : else {
159 : 0 : ssize = LLT_ALIGN(ssize, jl_page_size);
160 : : }
161 [ + + ]: 178 : if (stk == NULL) {
162 [ - + ]: 71 : if (jl_atomic_load_relaxed(&num_stack_mappings) >= MAX_STACK_MAPPINGS)
163 : : // we accept that this can go over by as much as nthreads since it's not a CAS
164 : 0 : return NULL;
165 : : // TODO: allocate blocks of stacks? but need to mprotect individually anyways
166 : 71 : stk = malloc_stack(ssize);
167 [ - + ]: 71 : if (stk == MAP_FAILED)
168 : 0 : return NULL;
169 : : }
170 : 178 : *bufsz = ssize;
171 [ + + ]: 178 : if (owner) {
172 : 148 : arraylist_t *live_tasks = &ptls->heap.live_tasks;
173 : 148 : arraylist_push(live_tasks, owner);
174 : : }
175 : 178 : return stk;
176 : : }
177 : :
178 : 1179 : void sweep_stack_pools(void)
179 : : {
180 : : // Stack sweeping algorithm:
181 : : // // deallocate stacks if we have too many sitting around unused
182 : : // for (stk in halfof(free_stacks))
183 : : // free_stack(stk, pool_sz);
184 : : // // then sweep the task stacks
185 : : // for (t in live_tasks)
186 : : // if (!gc-marked(t))
187 : : // stkbuf = t->stkbuf
188 : : // bufsz = t->bufsz
189 : : // if (stkbuf)
190 : : // push(free_stacks[sz], stkbuf)
191 [ + + ]: 2358 : for (int i = 0; i < jl_n_threads; i++) {
192 : 1179 : jl_ptls_t ptls2 = jl_all_tls_states[i];
193 : :
194 : : // free half of stacks that remain unused since last sweep
195 [ + + ]: 20043 : for (int p = 0; p < JL_N_STACK_POOLS; p++) {
196 : 18864 : arraylist_t *al = &ptls2->heap.free_stacks[p];
197 : : size_t n_to_free;
198 [ + + ]: 18864 : if (al->len > MIN_STACK_MAPPINGS_PER_POOL) {
199 : 4 : n_to_free = al->len / 2;
200 [ + - ]: 4 : if (n_to_free > (al->len - MIN_STACK_MAPPINGS_PER_POOL))
201 : 4 : n_to_free = al->len - MIN_STACK_MAPPINGS_PER_POOL;
202 : : }
203 : : else {
204 : 18860 : n_to_free = 0;
205 : : }
206 [ + + ]: 18873 : for (int n = 0; n < n_to_free; n++) {
207 : 9 : void *stk = arraylist_pop(al);
208 : 9 : free_stack(stk, pool_sizes[p]);
209 : : }
210 : : }
211 : :
212 : 1179 : arraylist_t *live_tasks = &ptls2->heap.live_tasks;
213 : 1179 : size_t n = 0;
214 : 1179 : size_t ndel = 0;
215 : 1179 : size_t l = live_tasks->len;
216 : 1179 : void **lst = live_tasks->items;
217 [ + + ]: 1179 : if (l == 0)
218 : 798 : continue;
219 : 845 : while (1) {
220 : 1226 : jl_task_t *t = (jl_task_t*)lst[n];
221 [ - + ]: 1226 : assert(jl_is_task(t));
222 [ + + ]: 1226 : if (gc_marked(jl_astaggedvalue(t)->bits.gc)) {
223 [ + + ]: 1106 : if (t->stkbuf == NULL)
224 : 5 : ndel++; // jl_release_task_stack called
225 : : else
226 : 1101 : n++;
227 : : }
228 : : else {
229 : 120 : ndel++;
230 : 120 : void *stkbuf = t->stkbuf;
231 : 120 : size_t bufsz = t->bufsz;
232 [ - + ]: 120 : if (stkbuf) {
233 : 0 : t->stkbuf = NULL;
234 : 0 : _jl_free_stack(ptls2, stkbuf, bufsz);
235 : : }
236 : : #ifdef _COMPILER_TSAN_ENABLED_
237 : : if (t->ctx.tsan_state) {
238 : : __tsan_destroy_fiber(t->ctx.tsan_state);
239 : : t->ctx.tsan_state = NULL;
240 : : }
241 : : #endif
242 : : }
243 [ + + ]: 1226 : if (n >= l - ndel)
244 : 381 : break;
245 : 845 : void *tmp = lst[n];
246 : 845 : lst[n] = lst[n + ndel];
247 : 845 : lst[n + ndel] = tmp;
248 : : }
249 : 381 : live_tasks->len -= ndel;
250 : : }
251 : 1179 : }
252 : :
253 : 0 : JL_DLLEXPORT jl_array_t *jl_live_tasks(void)
254 : : {
255 : 0 : jl_task_t *ct = jl_current_task;
256 : 0 : jl_ptls_t ptls = ct->ptls;
257 : 0 : arraylist_t *live_tasks = &ptls->heap.live_tasks;
258 : : size_t i, j, l;
259 : : jl_array_t *a;
260 : : do {
261 : 0 : l = live_tasks->len;
262 : 0 : a = jl_alloc_vec_any(l + 1); // may gc, changing the number of tasks
263 [ # # ]: 0 : } while (l + 1 < live_tasks->len);
264 : 0 : l = live_tasks->len;
265 : 0 : void **lst = live_tasks->items;
266 : 0 : j = 0;
267 : 0 : ((void**)jl_array_data(a))[j++] = ptls->root_task;
268 [ # # ]: 0 : for (i = 0; i < l; i++) {
269 [ # # ]: 0 : if (((jl_task_t*)lst[i])->stkbuf != NULL)
270 : 0 : ((void**)jl_array_data(a))[j++] = lst[i];
271 : : }
272 : 0 : l = jl_array_len(a);
273 [ # # ]: 0 : if (j < l) {
274 : 0 : JL_GC_PUSH1(&a);
275 : 0 : jl_array_del_end(a, l - j);
276 : 0 : JL_GC_POP();
277 : : }
278 : 0 : return a;
279 : : }
|