/home/travis/build/MoarVM/MoarVM/src/core/fixedsizealloc.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | |
3 | | #include "memdebug.h" |
4 | | |
5 | | /* The fixed size allocator provides a thread-safe mechanism for getting and |
6 | | * releasing fixed-size chunks of memory. Requests larger blocks from the |
7 | | * operating system, and then allocates out of them. Can certainly be further |
8 | | * improved. The free list works like a stack, so you get the most recently |
9 | | * freed piece of memory of a given size, which should give good cache |
10 | | * behavior. */ |
11 | | |
12 | | /* Turn this on to switch to a mode where we debug by size. */ |
13 | | #define FSA_SIZE_DEBUG 0 |
14 | | #if FSA_SIZE_DEBUG |
15 | | typedef struct { |
16 | | MVMuint64 alloc_size; |
17 | | void *memory; |
18 | | } MVMFixedSizeAllocDebug; |
19 | | #endif |
20 | | |
21 | | /* Creates the allocator data structure with bins. */ |
22 | 130 | MVMFixedSizeAlloc * MVM_fixed_size_create(MVMThreadContext *tc) { |
23 | 130 | int init_stat; |
24 | 130 | #ifdef MVM_VALGRIND_SUPPORT |
25 | | int bin_no; |
26 | | #endif |
27 | 130 | MVMFixedSizeAlloc *al = MVM_malloc(sizeof(MVMFixedSizeAlloc)); |
28 | 130 | al->size_classes = MVM_calloc(MVM_FSA_BINS, sizeof(MVMFixedSizeAllocSizeClass)); |
29 | 130 | if ((init_stat = uv_mutex_init(&(al->complex_alloc_mutex))) < 0) |
30 | 0 | MVM_exception_throw_adhoc(tc, "Failed to initialize mutex: %s", |
31 | 0 | uv_strerror(init_stat)); |
32 | 130 | al->freelist_spin = 0; |
33 | 130 | al->free_at_next_safepoint_overflows = NULL; |
34 | 130 | |
35 | 130 | /* All other places where we use valgrind macros are very likely |
36 | 130 | * thrown out by dead code elimination. Not 100% sure about this, |
37 | 130 | * so we ifdef it out. */ |
38 | 130 | #ifdef MVM_VALGRIND_SUPPORT |
39 | | for (bin_no = 0; bin_no < MVM_FSA_BINS; bin_no++) |
40 | | VALGRIND_CREATE_MEMPOOL(&al->size_classes[bin_no], MVM_FSA_REDZONE_BYTES, 0); |
41 | | #endif |
42 | 130 | |
43 | 130 | return al; |
44 | 130 | } |
45 | | |
46 | 0 | void MVM_fixed_size_destroy(MVMFixedSizeAlloc *al) { |
47 | 0 | int bin_no; |
48 | 0 |
|
49 | 0 | for (bin_no = 0; bin_no < MVM_FSA_BINS; bin_no++) { |
50 | 0 | int page_no; |
51 | 0 | int num_pages = al->size_classes[bin_no].num_pages; |
52 | 0 |
|
53 | 0 | VALGRIND_DESTROY_MEMPOOL(&al->size_classes[bin_no]); |
54 | 0 |
|
55 | 0 | for (page_no = 0; page_no < num_pages; page_no++) { |
56 | 0 | MVM_free(al->size_classes[bin_no].pages[page_no]); |
57 | 0 | } |
58 | 0 | MVM_free(al->size_classes[bin_no].pages); |
59 | 0 | } |
60 | 0 | uv_mutex_destroy(&(al->complex_alloc_mutex)); |
61 | 0 |
|
62 | 0 | MVM_free(al->size_classes); |
63 | 0 | MVM_free(al); |
64 | 0 | } |
65 | | |
66 | | /* Determine the bin. If we hit a bin exactly then it's off-by-one, |
67 | | * since the bins list is base-0. Otherwise we've some extra bits, |
68 | | * which round us up to the next bin, but that's a no-op. */ |
69 | 30.5M | static MVMuint32 bin_for(size_t bytes) { |
70 | 30.5M | MVMuint32 bin = (MVMuint32)(bytes >> MVM_FSA_BIN_BITS); |
71 | 30.5M | if ((bytes & MVM_FSA_BIN_MASK) == 0) |
72 | 28.9M | bin--; |
73 | 30.5M | return bin; |
74 | 30.5M | } |
75 | | |
76 | | /* Sets up a size class bin in the second generation. */ |
77 | 10.6k | static void setup_bin(MVMFixedSizeAlloc *al, MVMuint32 bin) { |
78 | 10.6k | /* Work out page size we want. */ |
79 | 10.6k | MVMuint32 page_size = MVM_FSA_PAGE_ITEMS * ((bin + 1) << MVM_FSA_BIN_BITS) + MVM_FSA_REDZONE_BYTES * 2 * MVM_FSA_PAGE_ITEMS; |
80 | 10.6k | |
81 | 10.6k | /* We'll just allocate a single page to start off with. */ |
82 | 10.6k | al->size_classes[bin].num_pages = 1; |
83 | 10.6k | al->size_classes[bin].pages = MVM_malloc(sizeof(void *) * al->size_classes[bin].num_pages); |
84 | 10.6k | al->size_classes[bin].pages[0] = MVM_malloc(page_size); |
85 | 10.6k | |
86 | 10.6k | /* Set up allocation position and limit. */ |
87 | 10.6k | al->size_classes[bin].alloc_pos = al->size_classes[bin].pages[0]; |
88 | 10.6k | al->size_classes[bin].alloc_limit = al->size_classes[bin].alloc_pos + page_size; |
89 | 10.6k | } |
90 | | |
91 | | /* Adds a new page to a size class bin. */ |
92 | 11.9k | static void add_page(MVMFixedSizeAlloc *al, MVMuint32 bin) { |
93 | 11.9k | /* Work out page size. */ |
94 | 11.9k | MVMuint32 page_size = MVM_FSA_PAGE_ITEMS * ((bin + 1) << MVM_FSA_BIN_BITS) + MVM_FSA_REDZONE_BYTES * 2 * MVM_FSA_PAGE_ITEMS; |
95 | 11.9k | |
96 | 11.9k | /* Add the extra page. */ |
97 | 11.9k | MVMuint32 cur_page = al->size_classes[bin].num_pages; |
98 | 11.9k | al->size_classes[bin].num_pages++; |
99 | 11.9k | al->size_classes[bin].pages = MVM_realloc(al->size_classes[bin].pages, |
100 | 11.9k | sizeof(void *) * al->size_classes[bin].num_pages); |
101 | 11.9k | al->size_classes[bin].pages[cur_page] = MVM_malloc(page_size); |
102 | 11.9k | |
103 | 11.9k | /* Set up allocation position and limit. */ |
104 | 11.9k | al->size_classes[bin].alloc_pos = al->size_classes[bin].pages[cur_page]; |
105 | 11.9k | al->size_classes[bin].alloc_limit = al->size_classes[bin].alloc_pos + page_size; |
106 | 11.9k | |
107 | 11.9k | /* set the cur_page to a proper value */ |
108 | 11.9k | al->size_classes[bin].cur_page = cur_page; |
109 | 11.9k | } |
110 | | |
111 | | /* Allocates a piece of memory of the specified size, using the FSA. */ |
112 | 1.61M | static void * alloc_slow_path(MVMThreadContext *tc, MVMFixedSizeAlloc *al, MVMuint32 bin) { |
113 | 1.61M | void *result; |
114 | 1.61M | |
115 | 1.61M | /* Lock, unless single-threaded. */ |
116 | 1.61M | MVMint32 lock = MVM_instance_have_user_threads(tc); |
117 | 1.61M | if (lock) |
118 | 0 | uv_mutex_lock(&(al->complex_alloc_mutex)); |
119 | 1.61M | |
120 | 1.61M | /* If we've no pages yet, never encountered this bin; set it up. */ |
121 | 1.61M | if (al->size_classes[bin].pages == NULL) |
122 | 10.6k | setup_bin(al, bin); |
123 | 1.61M | |
124 | 1.61M | /* If we're at the page limit, add a new page. */ |
125 | 1.61M | if (al->size_classes[bin].alloc_pos == al->size_classes[bin].alloc_limit) { |
126 | 11.9k | add_page(al, bin); |
127 | 11.9k | } |
128 | 1.61M | |
129 | 1.61M | /* Now we can allocate. */ |
130 | 1.61M | result = (void *)(al->size_classes[bin].alloc_pos + MVM_FSA_REDZONE_BYTES); |
131 | 1.61M | al->size_classes[bin].alloc_pos += ((bin + 1) << MVM_FSA_BIN_BITS) + 2 * MVM_FSA_REDZONE_BYTES; |
132 | 1.61M | |
133 | 1.61M | VALGRIND_MEMPOOL_ALLOC(&al->size_classes[bin], result, (bin + 1) << MVM_FSA_BIN_BITS); |
134 | 1.61M | |
135 | 1.61M | /* Unlock if we locked. */ |
136 | 1.61M | if (lock) |
137 | 0 | uv_mutex_unlock(&(al->complex_alloc_mutex)); |
138 | 1.61M | |
139 | 1.61M | return result; |
140 | 1.61M | } |
141 | 16.0M | void * MVM_fixed_size_alloc(MVMThreadContext *tc, MVMFixedSizeAlloc *al, size_t bytes) { |
142 | 16.0M | #if FSA_SIZE_DEBUG |
143 | | MVMFixedSizeAllocDebug *dbg = MVM_malloc(bytes + sizeof(MVMuint64)); |
144 | | dbg->alloc_size = bytes; |
145 | | return &(dbg->memory); |
146 | | #else |
147 | 16.0M | MVMuint32 bin = bin_for(bytes); |
148 | 16.0M | if (bin < MVM_FSA_BINS) { |
149 | 15.9M | /* Try and take from the free list (fast path). */ |
150 | 15.9M | MVMFixedSizeAllocSizeClass *bin_ptr = &(al->size_classes[bin]); |
151 | 15.9M | MVMFixedSizeAllocFreeListEntry *fle; |
152 | 15.9M | if (MVM_instance_have_user_threads(tc)) { |
153 | 0 | /* Multi-threaded; take a lock. Note that the lock is needed in |
154 | 0 | * addition to the atomic operations: the atomics allow us to add |
155 | 0 | * to the free list in a lock-free way, and the lock allows us to |
156 | 0 | * avoid the ABA issue we'd have with only the atomics. */ |
157 | 0 | while (!MVM_trycas(&(al->freelist_spin), 0, 1)) { |
158 | 0 | MVMint32 i = 0; |
159 | 0 | while (i < 1024) |
160 | 0 | i++; |
161 | 0 | } |
162 | 0 | do { |
163 | 0 | fle = bin_ptr->free_list; |
164 | 0 | if (!fle) |
165 | 0 | break; |
166 | 0 | } while (!MVM_trycas(&(bin_ptr->free_list), fle, fle->next)); |
167 | 0 | MVM_barrier(); |
168 | 0 | al->freelist_spin = 0; |
169 | 0 | } |
170 | 15.9M | else { |
171 | 15.9M | /* Single-threaded; just take it. */ |
172 | 15.9M | fle = bin_ptr->free_list; |
173 | 15.9M | if (fle) |
174 | 14.3M | bin_ptr->free_list = fle->next; |
175 | 15.9M | } |
176 | 15.9M | if (fle) { |
177 | 14.3M | VALGRIND_MEMPOOL_ALLOC(&al->size_classes[bin], ((void *)fle), (bin + 1) << MVM_FSA_BIN_BITS); |
178 | 14.3M | |
179 | 14.3M | return (void *)fle; |
180 | 14.3M | } |
181 | 15.9M | |
182 | 15.9M | /* Failed to take from free list; slow path with the lock. */ |
183 | 1.61M | return alloc_slow_path(tc, al, bin); |
184 | 15.9M | } |
185 | 47.3k | else { |
186 | 47.3k | return MVM_malloc(bytes); |
187 | 47.3k | } |
188 | 16.0M | #endif |
189 | 16.0M | } |
190 | | |
191 | | /* Allocates a piece of memory of the specified size, using the FSA. Promises |
192 | | * it will be zeroed. */ |
193 | 10.4M | void * MVM_fixed_size_alloc_zeroed(MVMThreadContext *tc, MVMFixedSizeAlloc *al, size_t bytes) { |
194 | 10.4M | void *allocd = MVM_fixed_size_alloc(tc, al, bytes); |
195 | 10.4M | memset(allocd, 0, bytes); |
196 | 10.4M | return allocd; |
197 | 10.4M | } |
198 | | |
199 | | /* Frees a piece of memory of the specified size, using the FSA. */ |
200 | 14.5M | static void add_to_bin_freelist(MVMThreadContext *tc, MVMFixedSizeAlloc *al, MVMint32 bin, void *to_free) { |
201 | 14.5M | /* Came from a bin; put into free list. */ |
202 | 14.5M | MVMFixedSizeAllocSizeClass *bin_ptr = &(al->size_classes[bin]); |
203 | 14.5M | MVMFixedSizeAllocFreeListEntry *to_add = (MVMFixedSizeAllocFreeListEntry *)to_free; |
204 | 14.5M | MVMFixedSizeAllocFreeListEntry *orig; |
205 | 14.5M | |
206 | 14.5M | VALGRIND_MEMPOOL_FREE(bin_ptr, to_add); |
207 | 14.5M | VALGRIND_MAKE_MEM_DEFINED(to_add, sizeof(MVMFixedSizeAllocFreeListEntry)); |
208 | 14.5M | |
209 | 14.5M | if (MVM_instance_have_user_threads(tc)) { |
210 | 0 | /* Multi-threaded; race to add it. */ |
211 | 0 | do { |
212 | 0 | orig = bin_ptr->free_list; |
213 | 0 | to_add->next = orig; |
214 | 0 | } while (!MVM_trycas(&(bin_ptr->free_list), orig, to_add)); |
215 | 0 | } |
216 | 14.5M | else { |
217 | 14.5M | /* Single-threaded; just add it. */ |
218 | 14.5M | to_add->next = bin_ptr->free_list; |
219 | 14.5M | bin_ptr->free_list = to_add; |
220 | 14.5M | } |
221 | 14.5M | } |
222 | 14.5M | void MVM_fixed_size_free(MVMThreadContext *tc, MVMFixedSizeAlloc *al, size_t bytes, void *to_free) { |
223 | 14.5M | #if FSA_SIZE_DEBUG |
224 | | MVMFixedSizeAllocDebug *dbg = (MVMFixedSizeAllocDebug *)((char *)to_free - 8); |
225 | | if (dbg->alloc_size != bytes) |
226 | | MVM_panic(1, "Fixed size allocator: wrong size in free"); |
227 | | MVM_free(dbg); |
228 | | #else |
229 | 14.5M | MVMuint32 bin = bin_for(bytes); |
230 | 14.5M | if (bin < MVM_FSA_BINS) { |
231 | 14.5M | /* Add to freelist chained through a bin. */ |
232 | 14.5M | add_to_bin_freelist(tc, al, bin, to_free); |
233 | 14.5M | } |
234 | 45.4k | else { |
235 | 45.4k | /* Was malloc'd due to being oversize, so just free it. */ |
236 | 45.4k | MVM_free(to_free); |
237 | 45.4k | } |
238 | 14.5M | #endif |
239 | 14.5M | } |
240 | | |
241 | | /* Race to add to the "free at next safe point" overflows list. */ |
242 | 0 | static void add_to_overflows_safepoint_free_list(MVMThreadContext *tc, MVMFixedSizeAlloc *al, void *to_free) { |
243 | 0 | MVMFixedSizeAllocSafepointFreeListEntry *orig; |
244 | 0 | MVMFixedSizeAllocSafepointFreeListEntry *to_add = MVM_fixed_size_alloc( |
245 | 0 | tc, al, sizeof(MVMFixedSizeAllocSafepointFreeListEntry)); |
246 | 0 | to_add->to_free = to_free; |
247 | 0 | do { |
248 | 0 | orig = al->free_at_next_safepoint_overflows; |
249 | 0 | to_add->next = orig; |
250 | 0 | } while (!MVM_trycas(&(al->free_at_next_safepoint_overflows), orig, to_add)); |
251 | 0 | } |
252 | | |
253 | | /* Queues a piece of memory of the specified size to be freed at the next |
254 | | * global safe point, using the FSA. A global safe point is defined as one in |
255 | | * which all threads, since we requested the freeing of the memory, have |
256 | | * reached a safe point. */ |
257 | 7.06k | void MVM_fixed_size_free_at_safepoint(MVMThreadContext *tc, MVMFixedSizeAlloc *al, size_t bytes, void *to_free) { |
258 | 7.06k | #if FSA_SIZE_DEBUG |
259 | | MVMFixedSizeAllocDebug *dbg = (MVMFixedSizeAllocDebug *)((char *)to_free - 8); |
260 | | if (dbg->alloc_size != bytes) |
261 | | MVM_panic(1, "Fixed size allocator: wrong size in free"); |
262 | | if (MVM_instance_have_user_threads(tc)) |
263 | | add_to_overflows_safepoint_free_list(tc, al, dbg); |
264 | | else |
265 | | MVM_free(dbg); |
266 | | #else |
267 | 7.06k | MVMuint32 bin = bin_for(bytes); |
268 | 7.06k | if (bin < MVM_FSA_BINS) { |
269 | 6.89k | /* Came from a bin; put into free list. */ |
270 | 6.89k | MVMFixedSizeAllocSizeClass *bin_ptr = &(al->size_classes[bin]); |
271 | 6.89k | if (MVM_instance_have_user_threads(tc)) { |
272 | 0 | /* Multi-threaded; race to add it to the "free at next safe point" |
273 | 0 | * list. */ |
274 | 0 | MVMFixedSizeAllocSafepointFreeListEntry *orig; |
275 | 0 | MVMFixedSizeAllocSafepointFreeListEntry *to_add = MVM_fixed_size_alloc( |
276 | 0 | tc, al, sizeof(MVMFixedSizeAllocSafepointFreeListEntry)); |
277 | 0 | to_add->to_free = to_free; |
278 | 0 | do { |
279 | 0 | orig = bin_ptr->free_at_next_safepoint_list; |
280 | 0 | to_add->next = orig; |
281 | 0 | } while (!MVM_trycas(&(bin_ptr->free_at_next_safepoint_list), orig, to_add)); |
282 | 0 | } |
283 | 6.89k | else { |
284 | 6.89k | /* Single-threaded, so no global safepoint issues to care for; put |
285 | 6.89k | * it on the free list right away. */ |
286 | 6.89k | MVMFixedSizeAllocFreeListEntry *to_add = (MVMFixedSizeAllocFreeListEntry *)to_free; |
287 | 6.89k | |
288 | 6.89k | VALGRIND_MEMPOOL_FREE(bin_ptr, to_add); |
289 | 6.89k | VALGRIND_MAKE_MEM_DEFINED(to_add, sizeof(MVMFixedSizeAllocFreeListEntry)); |
290 | 6.89k | |
291 | 6.89k | to_add->next = bin_ptr->free_list; |
292 | 6.89k | bin_ptr->free_list = to_add; |
293 | 6.89k | } |
294 | 6.89k | } |
295 | 168 | else { |
296 | 168 | /* Was malloc'd due to being oversize. */ |
297 | 168 | if (MVM_instance_have_user_threads(tc)) |
298 | 0 | add_to_overflows_safepoint_free_list(tc, al, to_free); |
299 | 168 | else |
300 | 168 | MVM_free(to_free); |
301 | 168 | } |
302 | 7.06k | #endif |
303 | 7.06k | } |
304 | | |
305 | | /* Called when we're at a safepoint, to free everything queued up to be freed |
306 | | * at the next safepoint. Assumes that it is only called on one thread at a |
307 | | * time, while the world is stopped. */ |
308 | 146 | void MVM_fixed_size_safepoint(MVMThreadContext *tc, MVMFixedSizeAlloc *al) { |
309 | 146 | /* Go through bins and process any safepoint free lists. */ |
310 | 146 | MVMFixedSizeAllocSafepointFreeListEntry *cur, *next; |
311 | 146 | MVMint32 bin; |
312 | 14.1k | for (bin = 0; bin < MVM_FSA_BINS; bin++) { |
313 | 14.0k | cur = al->size_classes[bin].free_at_next_safepoint_list; |
314 | 14.0k | while (cur) { |
315 | 0 | next = cur->next; |
316 | 0 | add_to_bin_freelist(tc, al, bin, cur->to_free); |
317 | 0 | MVM_fixed_size_free(tc, al, sizeof(MVMFixedSizeAllocSafepointFreeListEntry), cur); |
318 | 0 | cur = next; |
319 | 0 | } |
320 | 14.0k | al->size_classes[bin].free_at_next_safepoint_list = NULL; |
321 | 14.0k | } |
322 | 146 | |
323 | 146 | /* Free overflows. */ |
324 | 146 | cur = al->free_at_next_safepoint_overflows; |
325 | 146 | while (cur) { |
326 | 0 | next = cur->next; |
327 | 0 | MVM_free(cur->to_free); |
328 | 0 | MVM_fixed_size_free(tc, al, sizeof(MVMFixedSizeAllocSafepointFreeListEntry), cur); |
329 | 0 | cur = next; |
330 | 0 | } |
331 | 146 | al->free_at_next_safepoint_overflows = NULL; |
332 | 146 | } |