Coverage Report

Created: 2017-04-15 07:07

/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
}