1/*
2 * Copyright 2020 Axel Davy <davyaxel0@gmail.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * on the rights to use, copy, modify, merge, publish, distribute, sub
8 * license, and/or sell copies of the Software, and to permit persons to whom
9 * the Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21 * USE OR OTHER DEALINGS IN THE SOFTWARE. */
22
23/*
24 * Memory util function to allocate RAM backing for textures.
25 * DEFAULT textures are stored on GPU
26 * MANAGED textures have a RAM backing and upload the content to a GPU texture for use
27 * SYSTEMMEM textures are stored in RAM and are meant to be uploaded to DEFAULT textures.
28 * Basically SYSTEMMEM + DEFAULT enables to do manually what MANAGED does automatically.
29 *
30 * Once the GPU texture is created, the RAM backing of MANAGED textures can be used in
31 * two occasions:
32 * . Recreating the GPU texture (for example lod change, or GPU memory eviction)
33 * . Reading the texture content (some games do that to fill higher res versions of the texture)
34 *
35 * When a lot of textures are used, the amount of addressing space (virtual memory) taken by MANAGED
36 * and SYSTEMMEM textures can be significant and cause virtual memory exhaustion for 32 bits programs.
37 *
38 * One way to reduce the virtual memory taken is to ignore lod and delete the RAM backing of
39 * MANAGED textures once it is uploaded. If the texture is read, or evicted from GPU memory, the RAM
40 * backing would be recreated (Note that mapping the GPU memory is not acceptable as RAM memory is supposed
41 * to have smaller (fixed) stride constraints).
42 *
43 * Instead the approach taken here is to keep the RAM backing alive, but free its addressing space.
44 * In other words virtual memory usage is reduced, but the RAM usage of the app is the same.
45 * To do so, we use the memfd feature of the linux kernel. It enables to allocate a file
46 * stored in RAM and visible only to the app. We can map/unmap portions of the file as we need.
47 * When a portion is mapped, it takes virtual memory space. When it is not, it doesn't.
48 * The file is stored in RAM, and thus the access speed is the same as normal RAM. Using such
49 * file to allocate data enables to use more than 4GB RAM on 32 bits.
50 *
51 * This approach adds some overhead: when accessing mapped content the first time, pages are allocated
52 * by the system. This has a lot of overhead (several times the time to memset the area).
53 * Releasing these pages (when unmapping) has overhead too, though significantly less.
54 *
55 * This overhead however is much less significant than the overhead of downloading the GPU content.
56 * In addition, we reduce significantly the overhead spent in Gallium nine for new allocations by
57 * using the fact new contents of the file are zero-allocated. By not calling memset in Gallium nine,
58 * the overhead of page allocation happens client side, thus outside the d3d mutex. This should give
59 * a performance boost for multithreaded applications. As malloc also has this overhead (at least for
60 * large enough allocations which use mmap internally), allocating ends up faster than with the standard
61 * allocation path.
62 * By far the overhead induced by page allocation/deallocation is the biggest overhead involved in this
63 * code. It is reduced significantly with huge pages, but it is too complex to configure for the user
64 * to use it (and it has some memory management downsides too). The memset trick enables to move most of
65 * the overhead outside Nine anyway.
66 *
67 * To prevent useless unmappings quickly followed by mapping again, we do not unmap right away allocations
68 * that are not locked for access anymore. Indeed it is likely the allocation will be accessed several times
69 * in a row, for example first to fill it, then to upload it.
70 * We keep everything mapped until we reach a threshold of memory allocated. Then we use hints to prioritize
71 * which regions to unmap first. Thus virtual memory usage is only reduced when the threshold is reached.
72 *
73 * Multiple memfd files are used, each of 100MB. Thus memory usage (but not virtual memory usage) increases
74 * by amounts of 100MB. When not on x86 32 bits, we do use the standard malloc.
75 *
76 * Finally, for ease of use, we do not implement packing of allocation inside page-aligned regions.
77 * One allocation is given one page-aligned region inside a memfd file.
78 * Allocations smaller than a page (4KB on x86) go through malloc.
79 * As texture sizes are usually multiples of powers of two, allocations above the page size are typically
80 * multiples of the page size, thus space is not wasted in practice.
81 *
82 */
83
84#include <errno.h>
85#include <fcntl.h>
86#include <limits.h>
87#include <linux/memfd.h>
88#include <pthread.h>
89#include <stdio.h>
90#include <sys/mman.h>
91#include <sys/types.h>
92#include <sys/stat.h>
93#include <ulimit.h>
94#include <unistd.h>
95
96#include "util/list.h"
97#include "util/u_memory.h"
98#include "util/slab.h"
99
100#include "nine_debug.h"
101#include "nine_memory_helper.h"
102#include "nine_state.h"
103
104
105#define DIVUP(a,b) (((a)+(b)-1)/(b))
106
107/* Required alignment for allocations */
108#define NINE_ALLOCATION_ALIGNMENT 32
109
110#define DBG_CHANNEL (DBG_BASETEXTURE|DBG_SURFACE|DBG_VOLUME|DBG_TEXTURE|DBG_CUBETEXTURE)
111
112/* Use memfd only for 32 bits. Check for memfd_create support */
113#if defined(PIPE_ARCH_X86) && defined(HAVE_MEMFD_CREATE)
114#define NINE_ENABLE_MEMFD
115#endif
116
117#ifdef NINE_ENABLE_MEMFD
118
119struct nine_memfd_file_region {
120    unsigned offset;
121    unsigned size;
122    void *map; /* pointer to the mapped content of the file. Can be NULL */
123    int num_locks; /* Total number of locks blocking the munmap */
124    int num_weak_unlocks; /* Number of users which weakly block the munmap */
125    bool zero_filled;
126    struct list_head list;
127};
128
129struct nine_memfd_file {
130    int fd;
131    int filesize; /* Size of the file */
132    struct list_head free_regions; /* This list is sorted by the offset, and consecutive regions are merged */
133    struct list_head unmapped_allocated_regions; /* This list and the following ones are not sorted */
134    struct list_head locked_mapped_allocated_regions;
135    struct list_head weak_unlocked_mapped_allocated_regions;
136    struct list_head unlocked_mapped_allocated_regions;
137};
138
139/* The allocation is stored inside a memfd */
140#define NINE_MEMFD_ALLOC 1
141/* The allocation is part of another allocation, which is stored inside a memfd */
142#define NINE_MEMFD_SUBALLOC 2
143/* The allocation was allocated with malloc and will have to be freed */
144#define NINE_MALLOC_ALLOC 3
145/* The pointer doesn't need memory management */
146#define NINE_EXTERNAL_ALLOC 4
147
148struct nine_memfd_allocation {
149    struct nine_memfd_file *file; /* File in which the data is allocated */
150    struct nine_memfd_file_region *region; /* Corresponding file memory region. Max 1 allocation per region */
151};
152
153/* 'Suballocations' are used to represent subregions of an allocation.
154 * For example a given layer of a texture. These are not allocations,
155 * but can be accessed separately. To correctly handle accessing them,
156 * we encapsulate them into this structure. */
157struct nine_memfd_suballocation {
158    struct nine_memfd_allocation *parent; /* Parent allocation */
159    int relative_offset; /* Offset relative to the parent */
160};
161
162/* A standard allocation with malloc */
163struct nine_malloc_allocation {
164    void *buf;
165    unsigned allocation_size;
166};
167
168/* A pointer with no need of memory management.
169 * For example a pointer passed by the application,
170 * or a 'suballocation' inside a malloc-ed allocation. */
171struct nine_external_allocation {
172    void *buf;
173};
174
175/* Encapsulates all allocations */
176struct nine_allocation {
177    unsigned allocation_type; /* Type of allocation */
178    union {
179        struct nine_memfd_allocation memfd;
180        struct nine_memfd_suballocation submemfd;
181        struct nine_malloc_allocation malloc;
182        struct nine_external_allocation external;
183    } memory;
184    struct list_head list_free; /* for pending frees */
185    /* The fields below are only used for memfd/submemfd allocations */
186    struct list_head list_release; /* for pending releases */
187    /* Handling of the CSMT thread:
188     * API calls are singled thread (global mutex protection).
189     * However we multithreading internally (CSMT worker thread).
190     * To handle this thread, we map/lock the allocation in the
191     * main thread and increase pending_counter. When the worker thread
192     * is done with the scheduled function, the pending_counter is decreased.
193     * If pending_counter is 0, locks_on_counter can be subtracted from
194     * active_locks (in the main thread). */
195    unsigned locks_on_counter;
196    unsigned *pending_counter;
197    /* Hint from the last unlock indicating the data might be locked again soon */
198    bool weak_unlock;
199};
200
201struct nine_allocator {
202    struct NineDevice9 *device;
203    int page_size; /* Page size */
204    int num_fd_max; /* Max number of memfd files */
205    int min_file_size; /* Minimum memfd file size */
206    /* Tracking of all allocations */
207    long long total_allocations; /* Amount of memory allocated */
208    long long total_locked_memory; /* TODO */ /* Amount of memory blocked by a lock */
209    long long total_virtual_memory; /* Current virtual memory used by our allocations */
210    long long total_virtual_memory_limit; /* Target maximum virtual memory used. Above that, tries to unmap memfd files whenever possible. */
211
212    int num_fd; /* Number of memfd files */ /* TODO release unused memfd files */
213    struct slab_mempool allocation_pool;
214    struct slab_mempool region_pool;
215    struct nine_memfd_file *memfd_pool; /* Table (of size num_fd) of memfd files */
216    struct list_head pending_releases; /* List of allocations with unlocks depending on pending_counter */ /* TODO: Elements seem removed only on flush. Destruction ? */
217
218    pthread_mutex_t mutex_pending_frees;
219    struct list_head pending_frees;
220};
221
222#ifdef DEBUG
223
224static void
225debug_dump_memfd_state(struct nine_memfd_file *memfd_file, bool details)
226{
227    struct nine_memfd_file_region *region;
228
229    DBG("fd: %d, filesize: %d\n", memfd_file->fd, memfd_file->filesize);
230    if (!details)
231        return;
232    LIST_FOR_EACH_ENTRY(region, &memfd_file->free_regions, list) {
233        DBG("FREE block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",
234            region->offset, region->size, region->map,
235        region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);
236    }
237    LIST_FOR_EACH_ENTRY(region, &memfd_file->unmapped_allocated_regions, list) {
238        DBG("UNMAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",
239            region->offset, region->size, region->map,
240        region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);
241    }
242    LIST_FOR_EACH_ENTRY(region, &memfd_file->locked_mapped_allocated_regions, list) {
243        DBG("LOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",
244            region->offset, region->size, region->map,
245        region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);
246    }
247    LIST_FOR_EACH_ENTRY(region, &memfd_file->unlocked_mapped_allocated_regions, list) {
248        DBG("UNLOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",
249            region->offset, region->size, region->map,
250        region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);
251    }
252    LIST_FOR_EACH_ENTRY(region, &memfd_file->weak_unlocked_mapped_allocated_regions, list) {
253        DBG("WEAK UNLOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",
254            region->offset, region->size, region->map,
255        region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);
256    }
257}
258
259static void
260debug_dump_allocation_state(struct nine_allocation *allocation)
261{
262    switch(allocation->allocation_type) {
263        case NINE_MEMFD_ALLOC:
264            DBG("Allocation is stored in this memfd file:\n");
265            debug_dump_memfd_state(allocation->memory.memfd.file, true);
266            DBG("Allocation is offset: %d, size: %d\n",
267                allocation->memory.memfd.region->offset, allocation->memory.memfd.region->size);
268            break;
269        case NINE_MEMFD_SUBALLOC:
270            DBG("Allocation is suballocation at relative offset %d of this allocation:\n",
271                allocation->memory.submemfd.relative_offset);
272            DBG("Parent allocation is stored in this memfd file:\n");
273            debug_dump_memfd_state(allocation->memory.submemfd.parent->file, false);
274            DBG("Parent allocation is offset: %d, size: %d\n",
275                allocation->memory.submemfd.parent->region->offset,
276                allocation->memory.submemfd.parent->region->size);
277            break;
278        case NINE_MALLOC_ALLOC:
279            DBG("Allocation is a standard malloc\n");
280            break;
281        case NINE_EXTERNAL_ALLOC:
282            DBG("Allocation is a suballocation of a standard malloc or an external allocation\n");
283            break;
284        default:
285            assert(false);
286    }
287}
288
289#else
290
291static void
292debug_dump_memfd_state(struct nine_memfd_file *memfd_file, bool details)
293{
294    (void)memfd_file;
295    (void)details;
296}
297
298static void
299debug_dump_allocation_state(struct nine_allocation *allocation)
300{
301   (void)allocation;
302}
303
304#endif
305
306static void
307debug_dump_allocator_state(struct nine_allocator *allocator)
308{
309    DBG("SURFACE ALLOCATOR STATUS:\n");
310    DBG("Total allocated: %lld\n", allocator->total_allocations);
311    DBG("Total virtual memory locked: %lld\n", allocator->total_locked_memory);
312    DBG("Virtual memory used: %lld / %lld\n", allocator->total_virtual_memory, allocator->total_virtual_memory_limit);
313    DBG("Num memfd files: %d / %d\n", allocator->num_fd, allocator->num_fd_max);
314}
315
316
317/* Retrieve file used for the storage of the content of this allocation.
318 * NULL if not using memfd */
319static struct nine_memfd_file *
320nine_get_memfd_file_backing(struct nine_allocation *allocation)
321{
322    if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)
323        return NULL;
324    if (allocation->allocation_type == NINE_MEMFD_ALLOC)
325        return allocation->memory.memfd.file;
326    return allocation->memory.submemfd.parent->file;
327}
328
329/* Retrieve region used for the storage of the content of this allocation.
330 * NULL if not using memfd */
331static struct nine_memfd_file_region *
332nine_get_memfd_region_backing(struct nine_allocation *allocation)
333{
334    if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)
335        return NULL;
336    if (allocation->allocation_type == NINE_MEMFD_ALLOC)
337        return allocation->memory.memfd.region;
338    return allocation->memory.submemfd.parent->region;
339}
340
341static void move_region(struct list_head *tail, struct nine_memfd_file_region *region)
342{
343    /* Remove from previous list (if any) */
344    list_delinit(&region->list);
345    /* Insert in new list (last) */
346    list_addtail(&region->list, tail);
347}
348
349#if 0
350static void move_region_ordered(struct list_head *tail, struct nine_memfd_file_region *region)
351{
352    struct nine_memfd_file_region *cur_region;
353    struct list_head *insertion_point = tail;
354
355    /* Remove from previous list (if any) */
356    list_delinit(&region->list);
357
358    LIST_FOR_EACH_ENTRY(cur_region, tail, list) {
359        if (cur_region->offset > region->offset)
360            break;
361        insertion_point = &cur_region->list;
362    }
363    /* Insert just before cur_region */
364    list_add(&region->list, insertion_point);
365}
366#endif
367
368static void move_region_ordered_merge(struct nine_allocator *allocator, struct list_head *tail, struct nine_memfd_file_region *region)
369{
370    struct nine_memfd_file_region *p, *cur_region = NULL, *prev_region = NULL;
371
372    /* Remove from previous list (if any) */
373    list_delinit(&region->list);
374
375    LIST_FOR_EACH_ENTRY(p, tail, list) {
376        cur_region = p;
377        if (cur_region->offset > region->offset)
378            break;
379        prev_region = cur_region;
380    }
381
382    /* Insert after prev_region and before cur_region. Try to merge */
383    if (prev_region && ((prev_region->offset + prev_region->size) == region->offset)) {
384        if (cur_region && (cur_region->offset == (region->offset + region->size))) {
385            /* Merge all three regions */
386            prev_region->size += region->size + cur_region->size;
387            prev_region->zero_filled = prev_region->zero_filled && region->zero_filled && cur_region->zero_filled;
388            list_del(&cur_region->list);
389            slab_free_st(&allocator->region_pool, region);
390            slab_free_st(&allocator->region_pool, cur_region);
391        } else {
392            prev_region->size += region->size;
393            prev_region->zero_filled = prev_region->zero_filled && region->zero_filled;
394            slab_free_st(&allocator->region_pool, region);
395        }
396    } else if (cur_region && (cur_region->offset == (region->offset + region->size))) {
397        cur_region->offset = region->offset;
398        cur_region->size += region->size;
399        cur_region->zero_filled = region->zero_filled && cur_region->zero_filled;
400        slab_free_st(&allocator->region_pool, region);
401    } else {
402        list_add(&region->list, prev_region ? &prev_region->list : tail);
403    }
404}
405
406static struct nine_memfd_file_region *allocate_region(struct nine_allocator *allocator, unsigned offset, unsigned size) {
407    struct nine_memfd_file_region *region = slab_alloc_st(&allocator->allocation_pool);
408    if (!region)
409        return NULL;
410    region->offset = offset;
411    region->size = size;
412    region->num_locks = 0;
413    region->num_weak_unlocks = 0;
414    region->map = NULL;
415    region->zero_filled = false;
416    list_inithead(&region->list);
417    return region;
418}
419
420/* Go through memfd allocated files, and try to use unused memory for the requested allocation.
421 * Returns whether it suceeded */
422static bool
423insert_new_allocation(struct nine_allocator *allocator, struct nine_allocation *new_allocation, unsigned allocation_size)
424{
425    int memfd_index;
426    struct nine_memfd_file *memfd_file, *best_memfd_file;
427    struct nine_memfd_file_region *region, *best_region, *new_region;
428
429
430    /* Find the smallest - but bigger than the requested size - unused memory
431     * region inside the memfd files. */
432    int min_blocksize = INT_MAX;
433
434    for (memfd_index = 0; memfd_index < allocator->num_fd; memfd_index++) {
435        memfd_file = (void*)allocator->memfd_pool + memfd_index*sizeof(struct nine_memfd_file);
436
437        LIST_FOR_EACH_ENTRY(region, &memfd_file->free_regions, list) {
438            if (region->size <= min_blocksize && region->size >= allocation_size) {
439                min_blocksize = region->size;
440                best_region = region;
441                best_memfd_file = memfd_file;
442            }
443        }
444        if (min_blocksize == allocation_size)
445            break;
446    }
447
448    /* The allocation doesn't fit in any memfd file */
449    if (min_blocksize == INT_MAX)
450        return false;
451
452    /* Target region found */
453    /* Move from free to unmapped allocated */
454    best_region->size = DIVUP(allocation_size, allocator->page_size) * allocator->page_size;
455    assert(min_blocksize >= best_region->size);
456    move_region(&best_memfd_file->unmapped_allocated_regions, best_region);
457    new_allocation->memory.memfd.region = best_region;
458    new_allocation->memory.memfd.file = best_memfd_file;
459
460    /* If the original region is bigger than needed, add new region with remaining space */
461    min_blocksize -= best_region->size;
462    if (min_blocksize > 0) {
463        new_region = allocate_region(allocator, best_region->offset + best_region->size, min_blocksize);
464        new_region->zero_filled = best_region->zero_filled;
465        move_region_ordered_merge(allocator, &best_memfd_file->free_regions, new_region);
466    }
467    allocator->total_allocations += best_region->size;
468    return true;
469}
470
471/* Go through allocations with unlocks waiting on pending_counter being 0.
472 * If 0 is indeed reached, update the allocation status */
473static void
474nine_flush_pending_releases(struct nine_allocator *allocator)
475{
476    struct nine_allocation *allocation, *ptr;
477    LIST_FOR_EACH_ENTRY_SAFE(allocation, ptr, &allocator->pending_releases, list_release) {
478        assert(allocation->locks_on_counter > 0);
479        /* If pending_releases reached 0, remove from the list and update the status */
480        if (*allocation->pending_counter == 0) {
481            struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);
482            struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation);
483            region->num_locks -= allocation->locks_on_counter;
484            allocation->locks_on_counter = 0;
485            list_delinit(&allocation->list_release);
486            if (region->num_locks == 0) {
487                /* Move to the correct list */
488                if (region->num_weak_unlocks)
489                    move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region);
490                else
491                    move_region(&memfd_file->unlocked_mapped_allocated_regions, region);
492                allocator->total_locked_memory -= region->size;
493            }
494        }
495    }
496}
497
498static void
499nine_free_internal(struct nine_allocator *allocator, struct nine_allocation *allocation);
500
501static void
502nine_flush_pending_frees(struct nine_allocator *allocator)
503{
504    struct nine_allocation *allocation, *ptr;
505
506    pthread_mutex_lock(&allocator->mutex_pending_frees);
507    /* The order of release matters as suballocations are supposed to be released first */
508    LIST_FOR_EACH_ENTRY_SAFE(allocation, ptr, &allocator->pending_frees, list_free) {
509        /* Set the allocation in an unlocked state, and then free it */
510        if (allocation->allocation_type == NINE_MEMFD_ALLOC ||
511        allocation->allocation_type == NINE_MEMFD_SUBALLOC) {
512            struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);
513            struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation);
514            if (region->num_locks != 0) {
515                region->num_locks = 0;
516                allocator->total_locked_memory -= region->size;
517                /* Useless, but to keep consistency */
518                move_region(&memfd_file->unlocked_mapped_allocated_regions, region);
519            }
520            region->num_weak_unlocks = 0;
521            allocation->weak_unlock = false;
522            allocation->locks_on_counter = 0;
523            list_delinit(&allocation->list_release);
524        }
525        list_delinit(&allocation->list_free);
526        nine_free_internal(allocator, allocation);
527    }
528    pthread_mutex_unlock(&allocator->mutex_pending_frees);
529}
530
531/* Try to unmap the memfd_index-th file if not already unmapped.
532 * If even_if_weak is False, will not unmap if there are weak unlocks */
533static void
534nine_memfd_unmap_region(struct nine_allocator *allocator,
535                            struct nine_memfd_file *memfd_file,
536                            struct nine_memfd_file_region *region)
537{
538    DBG("Unmapping memfd mapped region at %d: size: %d, map=%p, locks=%d, weak=%d\n",
539        region->offset,  region->size, region->map,
540        region->num_locks, region->num_weak_unlocks);
541    assert(region->map != NULL);
542
543    if (munmap(region->map, region->size) != 0)
544        fprintf(stderr, "Error on unmapping, errno=%d\n", (int)errno);
545
546    region->map = NULL;
547    /* Move from one of the mapped region list to the unmapped one */
548    move_region(&memfd_file->unmapped_allocated_regions, region);
549    allocator->total_virtual_memory -= region->size;
550}
551
552/* Unallocate a region of a memfd file */
553static void
554remove_allocation(struct nine_allocator *allocator, struct nine_memfd_file *memfd_file, struct nine_memfd_file_region *region)
555{
556    assert(region->num_locks == 0);
557    region->num_weak_unlocks = 0;
558    /* Move from mapped region to unmapped region */
559    if (region->map) {
560        if (likely(!region->zero_filled)) {
561            /* As the region is mapped, it is likely the pages are allocated.
562             * Do the memset now for when we allocate again. It is much faster now,
563             * as the pages are allocated. */
564            DBG("memset on data=%p, size %d\n", region->map, region->size);
565            memset(region->map, 0, region->size);
566            region->zero_filled = true;
567        }
568        nine_memfd_unmap_region(allocator, memfd_file, region);
569    }
570    /* Move from unmapped region to free region */
571    allocator->total_allocations -= region->size;
572    move_region_ordered_merge(allocator, &memfd_file->free_regions, region);
573}
574
575/* Try to unmap the regions of the memfd_index-th file if not already unmapped.
576 * If even_if_weak is False, will not unmap if there are weak unlocks */
577static void
578nine_memfd_try_unmap_file(struct nine_allocator *allocator,
579                          int memfd_index,
580                          bool weak)
581{
582    struct nine_memfd_file *memfd_file = (void*)allocator->memfd_pool + memfd_index*sizeof(struct nine_memfd_file);
583    struct nine_memfd_file_region *region, *ptr;
584    DBG("memfd file at %d: fd: %d, filesize: %d\n",
585        memfd_index, memfd_file->fd, memfd_file->filesize);
586    debug_dump_memfd_state(memfd_file, true);
587    LIST_FOR_EACH_ENTRY_SAFE(region, ptr,
588                             weak ?
589                                &memfd_file->weak_unlocked_mapped_allocated_regions :
590                                &memfd_file->unlocked_mapped_allocated_regions,
591                             list) {
592        nine_memfd_unmap_region(allocator, memfd_file, region);
593    }
594}
595
596/* Unmap files until we are below the virtual memory target limit.
597 * If unmap_everything_possible is set, ignore the limit and unmap
598 * all that can be unmapped. */
599static void
600nine_memfd_files_unmap(struct nine_allocator *allocator,
601                       bool unmap_everything_possible)
602{
603    long long memory_limit = unmap_everything_possible ?
604        0 : allocator->total_virtual_memory_limit;
605    int i;
606
607    /* We are below the limit. Do nothing */
608    if (memory_limit >= allocator->total_virtual_memory)
609        return;
610
611    /* Update allocations with pending releases */
612    nine_flush_pending_releases(allocator);
613
614    DBG("Trying to unmap files with no weak unlock (%lld / %lld)\n",
615        allocator->total_virtual_memory, memory_limit);
616
617    /* Try to release everything with no weak releases.
618     * Those have data not needed for a long time (and
619     * possibly ever). */
620    for (i = 0; i < allocator->num_fd; i++) {
621        nine_memfd_try_unmap_file(allocator, i, false);
622        if (memory_limit >= allocator->total_virtual_memory) {
623            return;}
624    }
625
626    DBG("Trying to unmap files even with weak unlocks (%lld / %lld)\n",
627        allocator->total_virtual_memory, memory_limit);
628
629    /* This wasn't enough. Also release files with weak releases */
630    for (i = 0; i < allocator->num_fd; i++) {
631        nine_memfd_try_unmap_file(allocator, i, true);
632        /* Stop if the target is reached */
633        if (memory_limit >= allocator->total_virtual_memory) {
634            return;}
635    }
636
637    if (!unmap_everything_possible)
638        return;
639
640    /* If there are some pending uploads, execute them,
641     * and retry. */
642    if (list_is_empty(&allocator->pending_releases)) {
643        return;}
644    nine_csmt_process(allocator->device);
645    nine_flush_pending_releases(allocator);
646
647    DBG("Retrying after flushing (%lld / %lld)\n",
648        allocator->total_virtual_memory, memory_limit);
649
650    for (i = 0; i < allocator->num_fd; i++) {
651        nine_memfd_try_unmap_file(allocator, i, false);
652        nine_memfd_try_unmap_file(allocator, i, true);
653    }
654    /* We have done all we could */
655}
656
657/* Map a given memfd file */
658static bool
659nine_memfd_region_map(struct nine_allocator *allocator, struct nine_memfd_file *memfd_file, struct nine_memfd_file_region *region)
660{
661    if (region->map != NULL)
662        return true;
663
664    debug_dump_memfd_state(memfd_file, true);
665    nine_memfd_files_unmap(allocator, false);
666
667    void *buf = mmap(NULL, region->size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd_file->fd, region->offset);
668
669    if (buf == MAP_FAILED && errno == ENOMEM) {
670        DBG("Failed to mmap a memfd file - trying to unmap other files\n");
671        nine_memfd_files_unmap(allocator, true);
672        buf = mmap(NULL, region->size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd_file->fd, region->offset);
673    }
674    if (buf == MAP_FAILED) {
675        DBG("Failed to mmap a memfd file, errno=%d\n", (int)errno);
676        return false;
677    }
678    region->map = buf;
679    /* no need to move to an unlocked mapped regions list, the caller will handle the list */
680    allocator->total_virtual_memory += region->size;
681    assert((uintptr_t)buf % NINE_ALLOCATION_ALIGNMENT == 0); /* mmap should be page_size aligned, so it should be fine */
682
683    return true;
684}
685
686/* Allocate with memfd some memory. Returns True if successful. */
687static bool
688nine_memfd_allocator(struct nine_allocator *allocator,
689                     struct nine_allocation *new_allocation,
690                     unsigned allocation_size)
691{
692    struct nine_memfd_file *memfd_file;
693    struct nine_memfd_file_region *region;
694
695    allocation_size = DIVUP(allocation_size, allocator->page_size) * allocator->page_size;
696    new_allocation->allocation_type = NINE_MEMFD_ALLOC;
697    new_allocation->locks_on_counter = 0;
698    new_allocation->pending_counter = NULL;
699    new_allocation->weak_unlock = false;
700    list_inithead(&new_allocation->list_free);
701    list_inithead(&new_allocation->list_release);
702
703    /* Try to find free space in a file already allocated */
704    if (insert_new_allocation(allocator, new_allocation, allocation_size))
705        return true;
706
707    /* No - allocate new memfd file */
708
709    if (allocator->num_fd == allocator->num_fd_max)
710        return false; /* Too many memfd files */
711
712    allocator->num_fd++;
713    memfd_file = (void*)allocator->memfd_pool + (allocator->num_fd-1)*sizeof(struct nine_memfd_file);
714    /* If the allocation size is above the memfd file default size, use a bigger size */
715    memfd_file->filesize = MAX2(allocation_size, allocator->min_file_size);
716
717    memfd_file->fd = memfd_create("gallium_nine_ram", 0);
718    if (memfd_file->fd == -1) {
719        DBG("Failed to created a memfd file, errno=%d\n", (int)errno);
720        allocator->num_fd--;
721        return false;
722    }
723
724    if (ftruncate(memfd_file->fd, memfd_file->filesize) != 0) {
725        DBG("Failed to resize a memfd file, errno=%d\n", (int)errno);
726        close(memfd_file->fd);
727        allocator->num_fd--;
728        return false;
729    }
730
731    list_inithead(&memfd_file->free_regions);
732    list_inithead(&memfd_file->unmapped_allocated_regions);
733    list_inithead(&memfd_file->locked_mapped_allocated_regions);
734    list_inithead(&memfd_file->unlocked_mapped_allocated_regions);
735    list_inithead(&memfd_file->weak_unlocked_mapped_allocated_regions);
736
737    /* Initialize the memfd file with empty region and the allocation */
738    region = allocate_region(allocator, 0, allocation_size);
739    region->zero_filled = true; /* ftruncate does zero-fill the new data */
740    list_add(&region->list, &memfd_file->unmapped_allocated_regions);
741    new_allocation->memory.memfd.file = memfd_file;
742    new_allocation->memory.memfd.region = region;
743    allocator->total_allocations += allocation_size;
744
745    if (allocation_size == memfd_file->filesize)
746        return true;
747
748    /* Add empty region */
749    region = allocate_region(allocator, allocation_size, memfd_file->filesize - allocation_size);
750    region->zero_filled = true; /* ftruncate does zero-fill the new data */
751    list_add(&region->list, &memfd_file->free_regions);
752
753    return true;
754}
755
756/* Allocate memory */
757struct nine_allocation *
758nine_allocate(struct nine_allocator *allocator, unsigned size)
759{
760
761    struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool);
762    debug_dump_allocator_state(allocator);
763    if (!new_allocation)
764        return NULL;
765
766    nine_flush_pending_frees(allocator);
767
768    /* Restrict to >= page_size to prevent having too much fragmentation, as the size of
769     * allocations is rounded to the next page_size multiple. */
770    if (size >= allocator->page_size && allocator->total_virtual_memory_limit >= 0 &&
771        nine_memfd_allocator(allocator, new_allocation, size)) {
772        struct nine_memfd_file_region *region = new_allocation->memory.memfd.region;
773        if (!region->zero_filled) {
774            void *data = nine_get_pointer(allocator, new_allocation);
775            if (!data) {
776                ERR("INTERNAL MMAP FOR NEW ALLOCATION FAILED\n");
777                nine_free(allocator, new_allocation);
778                return NULL;
779            }
780            DBG("memset on data=%p, size %d\n", data, region->size);
781            memset(data, 0, region->size);
782            region->zero_filled = true;
783            /* Even though the user usually fills afterward, we don't weakrelease.
784             * The reason is suballocations don't affect the weakrelease state of their
785             * parents. Thus if only suballocations are accessed, the release would stay
786             * weak forever. */
787            nine_pointer_strongrelease(allocator, new_allocation);
788        }
789        DBG("ALLOCATION SUCCESSFUL\n");
790        debug_dump_allocation_state(new_allocation);
791        return new_allocation;
792    }
793
794    void *data = align_calloc(size, NINE_ALLOCATION_ALIGNMENT);
795    if (!data) {
796        DBG("ALLOCATION FAILED\n");
797        return NULL;
798    }
799
800    new_allocation->allocation_type = NINE_MALLOC_ALLOC;
801    new_allocation->memory.malloc.buf = data;
802    new_allocation->memory.malloc.allocation_size = size;
803    list_inithead(&new_allocation->list_free);
804    allocator->total_allocations += size;
805    allocator->total_locked_memory += size;
806    allocator->total_virtual_memory += size;
807    DBG("ALLOCATION SUCCESSFUL\n");
808    debug_dump_allocation_state(new_allocation);
809    return new_allocation;
810}
811
812/* Release memory */
813static void
814nine_free_internal(struct nine_allocator *allocator, struct nine_allocation *allocation)
815{
816    DBG("RELEASING ALLOCATION\n");
817    debug_dump_allocation_state(allocation);
818    if (allocation->allocation_type == NINE_MALLOC_ALLOC) {
819        allocator->total_allocations -= allocation->memory.malloc.allocation_size;
820        allocator->total_locked_memory -= allocation->memory.malloc.allocation_size;
821        allocator->total_virtual_memory -= allocation->memory.malloc.allocation_size;
822        align_free(allocation->memory.malloc.buf);
823    } else if (allocation->allocation_type == NINE_MEMFD_ALLOC ||
824        allocation->allocation_type == NINE_MEMFD_SUBALLOC) {
825        struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);
826        struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation);
827        if (allocation->weak_unlock)
828            region->num_weak_unlocks--;
829        if (allocation->allocation_type == NINE_MEMFD_ALLOC)
830            remove_allocation(allocator, memfd_file, region);
831    }
832
833    slab_free_st(&allocator->allocation_pool, allocation);
834    debug_dump_allocator_state(allocator);
835}
836
837
838void
839nine_free(struct nine_allocator *allocator, struct nine_allocation *allocation)
840{
841    nine_flush_pending_frees(allocator);
842    nine_flush_pending_releases(allocator);
843    nine_free_internal(allocator, allocation);
844}
845
846/* Called from the worker thread. Similar to nine_free except we are not in the main thread, thus
847 * we are disallowed to change the allocator structures except the fields reserved
848 * for the worker. In addition, the allocation is allowed to not being unlocked (the release
849 * will unlock it) */
850void nine_free_worker(struct nine_allocator *allocator, struct nine_allocation *allocation)
851{
852    /* Add the allocation to the list of pending allocations to free */
853    pthread_mutex_lock(&allocator->mutex_pending_frees);
854    /* The order of free matters as suballocations are supposed to be released first */
855    list_addtail(&allocation->list_free, &allocator->pending_frees);
856    pthread_mutex_unlock(&allocator->mutex_pending_frees);
857}
858
859/* Lock an allocation, and retrieve the pointer */
860void *
861nine_get_pointer(struct nine_allocator *allocator, struct nine_allocation *allocation)
862{
863    struct nine_memfd_file *memfd_file;
864    struct nine_memfd_file_region *region;
865
866    nine_flush_pending_releases(allocator);
867    DBG("allocation_type: %d\n", allocation->allocation_type);
868
869    if (allocation->allocation_type == NINE_MALLOC_ALLOC)
870        return allocation->memory.malloc.buf;
871    if (allocation->allocation_type == NINE_EXTERNAL_ALLOC)
872        return allocation->memory.external.buf;
873
874    memfd_file = nine_get_memfd_file_backing(allocation);
875    region = nine_get_memfd_region_backing(allocation);
876    if (!nine_memfd_region_map(allocator, memfd_file, region)) {
877        DBG("Couldn't map memfd region for get_pointer\n");
878        return NULL;
879    }
880
881    move_region(&memfd_file->locked_mapped_allocated_regions, region); /* Note: redundant if region->num_locks */
882    region->num_locks++;
883
884    if (region->num_locks == 1)
885        allocator->total_locked_memory += region->size;
886    if (allocation->weak_unlock)
887        region->num_weak_unlocks--;
888    allocation->weak_unlock = false;
889    region->zero_filled = false;
890
891
892    if (allocation->allocation_type == NINE_MEMFD_ALLOC)
893        return region->map;
894    if (allocation->allocation_type == NINE_MEMFD_SUBALLOC)
895        return region->map + allocation->memory.submemfd.relative_offset;
896
897    assert(false);
898    return NULL;
899}
900
901/* Unlock an allocation, but with hint that we might lock again soon */
902void
903nine_pointer_weakrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)
904{
905    struct nine_memfd_file_region *region;
906    if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)
907        return;
908
909    region = nine_get_memfd_region_backing(allocation);
910    if (!allocation->weak_unlock)
911        region->num_weak_unlocks++;
912    allocation->weak_unlock = true;
913    region->num_locks--;
914    if (region->num_locks == 0) {
915        struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);
916        allocator->total_locked_memory -= region->size;
917        move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region);
918    }
919}
920
921/* Unlock an allocation */
922void
923nine_pointer_strongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)
924{
925    struct nine_memfd_file_region *region;
926    if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)
927        return;
928
929    region = nine_get_memfd_region_backing(allocation);
930    region->num_locks--;
931    if (region->num_locks == 0) {
932        struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);
933        allocator->total_locked_memory -= region->size;
934        if (region->num_weak_unlocks)
935            move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region);
936        else
937            move_region(&memfd_file->unlocked_mapped_allocated_regions, region);
938    }
939}
940
941/* Delay a release to when a given counter becomes zero */
942void
943nine_pointer_delayedstrongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation, unsigned *counter)
944{
945    if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)
946        return;
947
948    assert(allocation->pending_counter == NULL || allocation->pending_counter == counter);
949    allocation->pending_counter = counter;
950    allocation->locks_on_counter++;
951
952    if (list_is_empty(&allocation->list_release))
953        list_add(&allocation->list_release, &allocator->pending_releases);
954}
955
956/* Create a suballocation of an allocation */
957struct nine_allocation *
958nine_suballocate(struct nine_allocator* allocator, struct nine_allocation *allocation, int offset)
959{
960    struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool);
961    if (!new_allocation)
962        return NULL;
963
964    DBG("Suballocate allocation at offset: %d\n", offset);
965    assert(allocation->allocation_type != NINE_MEMFD_SUBALLOC);
966    list_inithead(&new_allocation->list_free);
967
968    if (allocation->allocation_type != NINE_MEMFD_ALLOC) {
969        new_allocation->allocation_type = NINE_EXTERNAL_ALLOC;
970        if (allocation->allocation_type == NINE_MALLOC_ALLOC)
971            new_allocation->memory.external.buf = allocation->memory.malloc.buf + offset;
972        else
973            new_allocation->memory.external.buf = allocation->memory.external.buf + offset;
974        return new_allocation;
975    }
976    new_allocation->allocation_type = NINE_MEMFD_SUBALLOC;
977    new_allocation->memory.submemfd.parent = &allocation->memory.memfd;
978    new_allocation->memory.submemfd.relative_offset = offset;
979    new_allocation->locks_on_counter = 0;
980    new_allocation->pending_counter = NULL;
981    new_allocation->weak_unlock = false;
982    list_inithead(&new_allocation->list_release);
983    debug_dump_allocation_state(new_allocation);
984    return new_allocation;
985}
986
987/* Wrap an external pointer as an allocation */
988struct nine_allocation *
989nine_wrap_external_pointer(struct nine_allocator* allocator, void* data)
990{
991    struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool);
992    if (!new_allocation)
993        return NULL;
994    DBG("Wrapping external pointer: %p\n", data);
995    new_allocation->allocation_type = NINE_EXTERNAL_ALLOC;
996    new_allocation->memory.external.buf = data;
997    list_inithead(&new_allocation->list_free);
998    return new_allocation;
999}
1000
1001struct nine_allocator *
1002nine_allocator_create(struct NineDevice9 *device, int memfd_virtualsizelimit)
1003{
1004    struct nine_allocator* allocator = MALLOC(sizeof(struct nine_allocator));
1005
1006    if (!allocator)
1007        return NULL;
1008
1009    allocator->device = device;
1010    allocator->page_size = sysconf(_SC_PAGESIZE);
1011    assert(allocator->page_size == 4 << 10);
1012    allocator->num_fd_max = (memfd_virtualsizelimit >= 0) ? MIN2(128, ulimit(__UL_GETOPENMAX)) : 0;
1013    allocator->min_file_size = DIVUP(100 * (1 << 20), allocator->page_size) * allocator->page_size; /* 100MB files */
1014    allocator->total_allocations = 0;
1015    allocator->total_locked_memory = 0;
1016    allocator->total_virtual_memory = 0;
1017    allocator->total_virtual_memory_limit = memfd_virtualsizelimit * (1 << 20);
1018    allocator->num_fd = 0;
1019
1020    DBG("Allocator created (ps: %d; fm: %d)\n", allocator->page_size, allocator->num_fd_max);
1021
1022    slab_create(&allocator->allocation_pool, sizeof(struct nine_allocation), 4096);
1023    slab_create(&allocator->region_pool, sizeof(struct nine_memfd_file_region), 4096);
1024    allocator->memfd_pool = CALLOC(allocator->num_fd_max, sizeof(struct nine_memfd_file));
1025    list_inithead(&allocator->pending_releases);
1026    list_inithead(&allocator->pending_frees);
1027    pthread_mutex_init(&allocator->mutex_pending_frees, NULL);
1028    return allocator;
1029}
1030
1031void
1032nine_allocator_destroy(struct nine_allocator* allocator)
1033{
1034    int i;
1035    DBG("DESTROYING ALLOCATOR\n");
1036    debug_dump_allocator_state(allocator);
1037    nine_flush_pending_releases(allocator);
1038    nine_flush_pending_frees(allocator);
1039    nine_memfd_files_unmap(allocator, true);
1040    pthread_mutex_destroy(&allocator->mutex_pending_frees);
1041
1042    assert(list_is_empty(&allocator->pending_frees));
1043    assert(list_is_empty(&allocator->pending_releases));
1044    for (i = 0; i < allocator->num_fd; i++) {
1045        debug_dump_memfd_state(&allocator->memfd_pool[i], true);
1046        assert(list_is_empty(&allocator->memfd_pool[i].locked_mapped_allocated_regions));
1047        assert(list_is_empty(&allocator->memfd_pool[i].weak_unlocked_mapped_allocated_regions));
1048        assert(list_is_empty(&allocator->memfd_pool[i].unlocked_mapped_allocated_regions));
1049        assert(list_is_singular(&allocator->memfd_pool[i].free_regions));
1050        slab_free_st(&allocator->region_pool,
1051                     list_first_entry(&allocator->memfd_pool[i].free_regions,
1052                                      struct nine_memfd_file_region, list));
1053        close(allocator->memfd_pool[i].fd);
1054    }
1055    slab_destroy(&allocator->allocation_pool);
1056    slab_destroy(&allocator->region_pool);
1057    FREE(allocator->memfd_pool);
1058    FREE(allocator);
1059}
1060
1061#else
1062
1063struct nine_allocation {
1064    unsigned is_external;
1065    void *external;
1066};
1067
1068struct nine_allocator {
1069    struct slab_mempool external_allocation_pool;
1070    pthread_mutex_t mutex_slab;
1071};
1072
1073struct nine_allocation *
1074nine_allocate(struct nine_allocator *allocator, unsigned size)
1075{
1076    struct nine_allocation *allocation;
1077    (void)allocator;
1078    assert(sizeof(struct nine_allocation) <= NINE_ALLOCATION_ALIGNMENT);
1079    allocation = align_calloc(size + NINE_ALLOCATION_ALIGNMENT, NINE_ALLOCATION_ALIGNMENT);
1080    allocation->is_external = false;
1081    return allocation;
1082}
1083
1084
1085void nine_free(struct nine_allocator *allocator, struct nine_allocation *allocation)
1086{
1087    if (allocation->is_external) {
1088        pthread_mutex_lock(&allocator->mutex_slab);
1089        slab_free_st(&allocator->external_allocation_pool, allocation);
1090        pthread_mutex_unlock(&allocator->mutex_slab);
1091    } else
1092        align_free(allocation);
1093}
1094
1095void nine_free_worker(struct nine_allocator *allocator, struct nine_allocation *allocation)
1096{
1097    nine_free(allocator, allocation);
1098}
1099
1100void *nine_get_pointer(struct nine_allocator *allocator, struct nine_allocation *allocation)
1101{
1102    (void)allocator;
1103    if (allocation->is_external)
1104        return allocation->external;
1105    return (uint8_t *)allocation + NINE_ALLOCATION_ALIGNMENT;
1106}
1107
1108void nine_pointer_weakrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)
1109{
1110    (void)allocator;
1111    (void)allocation;
1112}
1113
1114void nine_pointer_strongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)
1115{
1116    (void)allocator;
1117    (void)allocation;
1118}
1119
1120void nine_pointer_delayedstrongrelease(struct nine_allocator *allocator,
1121                                       struct nine_allocation *allocation,
1122                                       unsigned *counter)
1123{
1124    (void)allocator;
1125    (void)allocation;
1126    (void)counter;
1127}
1128
1129struct nine_allocation *
1130nine_suballocate(struct nine_allocator* allocator, struct nine_allocation *allocation, int offset)
1131{
1132    struct nine_allocation *new_allocation;
1133    pthread_mutex_lock(&allocator->mutex_slab);
1134    new_allocation = slab_alloc_st(&allocator->external_allocation_pool);
1135    pthread_mutex_unlock(&allocator->mutex_slab);
1136    new_allocation->is_external = true;
1137    new_allocation->external = (uint8_t *)allocation + NINE_ALLOCATION_ALIGNMENT + offset;
1138    return new_allocation;
1139}
1140
1141struct nine_allocation *
1142nine_wrap_external_pointer(struct nine_allocator* allocator, void* data)
1143{
1144    struct nine_allocation *new_allocation;
1145    pthread_mutex_lock(&allocator->mutex_slab);
1146    new_allocation = slab_alloc_st(&allocator->external_allocation_pool);
1147    pthread_mutex_unlock(&allocator->mutex_slab);
1148    new_allocation->is_external = true;
1149    new_allocation->external = data;
1150    return new_allocation;
1151}
1152
1153struct nine_allocator *
1154nine_allocator_create(struct NineDevice9 *device, int memfd_virtualsizelimit)
1155{
1156    struct nine_allocator* allocator = MALLOC(sizeof(struct nine_allocator));
1157    (void)device;
1158    (void)memfd_virtualsizelimit;
1159
1160    if (!allocator)
1161        return NULL;
1162
1163    slab_create(&allocator->external_allocation_pool, sizeof(struct nine_allocation), 4096);
1164    pthread_mutex_init(&allocator->mutex_slab, NULL);
1165
1166    return allocator;
1167}
1168
1169void
1170nine_allocator_destroy(struct nine_allocator *allocator)
1171{
1172    slab_destroy(&allocator->external_allocation_pool);
1173    pthread_mutex_destroy(&allocator->mutex_slab);
1174}
1175
1176#endif /* NINE_ENABLE_MEMFD */
1177