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 
119 struct 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 
129 struct 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 
148 struct 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. */
157 struct 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 */
163 struct 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. */
171 struct nine_external_allocation {
172     void *buf;
173 };
174 
175 /* Encapsulates all allocations */
176 struct 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 
201 struct 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 
224 static void
debug_dump_memfd_state(struct nine_memfd_file * memfd_file,bool details)225 debug_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 
259 static void
debug_dump_allocation_state(struct nine_allocation * allocation)260 debug_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 
291 static void
debug_dump_memfd_state(struct nine_memfd_file * memfd_file,bool details)292 debug_dump_memfd_state(struct nine_memfd_file *memfd_file, bool details)
293 {
294     (void)memfd_file;
295     (void)details;
296 }
297 
298 static void
debug_dump_allocation_state(struct nine_allocation * allocation)299 debug_dump_allocation_state(struct nine_allocation *allocation)
300 {
301    (void)allocation;
302 }
303 
304 #endif
305 
306 static void
debug_dump_allocator_state(struct nine_allocator * allocator)307 debug_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 */
319 static struct nine_memfd_file *
nine_get_memfd_file_backing(struct nine_allocation * allocation)320 nine_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 */
331 static struct nine_memfd_file_region *
nine_get_memfd_region_backing(struct nine_allocation * allocation)332 nine_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 
move_region(struct list_head * tail,struct nine_memfd_file_region * region)341 static 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
350 static 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 
move_region_ordered_merge(struct nine_allocator * allocator,struct list_head * tail,struct nine_memfd_file_region * region)368 static 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 
allocate_region(struct nine_allocator * allocator,unsigned offset,unsigned size)406 static 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 */
422 static bool
insert_new_allocation(struct nine_allocator * allocator,struct nine_allocation * new_allocation,unsigned allocation_size)423 insert_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 */
473 static void
nine_flush_pending_releases(struct nine_allocator * allocator)474 nine_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 
498 static void
499 nine_free_internal(struct nine_allocator *allocator, struct nine_allocation *allocation);
500 
501 static void
nine_flush_pending_frees(struct nine_allocator * allocator)502 nine_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 */
533 static void
nine_memfd_unmap_region(struct nine_allocator * allocator,struct nine_memfd_file * memfd_file,struct nine_memfd_file_region * region)534 nine_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 */
553 static void
remove_allocation(struct nine_allocator * allocator,struct nine_memfd_file * memfd_file,struct nine_memfd_file_region * region)554 remove_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 */
577 static void
nine_memfd_try_unmap_file(struct nine_allocator * allocator,int memfd_index,bool weak)578 nine_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. */
599 static void
nine_memfd_files_unmap(struct nine_allocator * allocator,bool unmap_everything_possible)600 nine_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 */
658 static bool
nine_memfd_region_map(struct nine_allocator * allocator,struct nine_memfd_file * memfd_file,struct nine_memfd_file_region * region)659 nine_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. */
687 static bool
nine_memfd_allocator(struct nine_allocator * allocator,struct nine_allocation * new_allocation,unsigned allocation_size)688 nine_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 */
757 struct nine_allocation *
nine_allocate(struct nine_allocator * allocator,unsigned size)758 nine_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 */
813 static void
nine_free_internal(struct nine_allocator * allocator,struct nine_allocation * allocation)814 nine_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 
838 void
nine_free(struct nine_allocator * allocator,struct nine_allocation * allocation)839 nine_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) */
nine_free_worker(struct nine_allocator * allocator,struct nine_allocation * allocation)850 void 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 */
860 void *
nine_get_pointer(struct nine_allocator * allocator,struct nine_allocation * allocation)861 nine_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 */
902 void
nine_pointer_weakrelease(struct nine_allocator * allocator,struct nine_allocation * allocation)903 nine_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 */
922 void
nine_pointer_strongrelease(struct nine_allocator * allocator,struct nine_allocation * allocation)923 nine_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 */
942 void
nine_pointer_delayedstrongrelease(struct nine_allocator * allocator,struct nine_allocation * allocation,unsigned * counter)943 nine_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 */
957 struct nine_allocation *
nine_suballocate(struct nine_allocator * allocator,struct nine_allocation * allocation,int offset)958 nine_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 */
988 struct nine_allocation *
nine_wrap_external_pointer(struct nine_allocator * allocator,void * data)989 nine_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 
1001 struct nine_allocator *
nine_allocator_create(struct NineDevice9 * device,int memfd_virtualsizelimit)1002 nine_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 
1031 void
nine_allocator_destroy(struct nine_allocator * allocator)1032 nine_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 
1063 struct nine_allocation {
1064     unsigned is_external;
1065     void *external;
1066 };
1067 
1068 struct nine_allocator {
1069     struct slab_mempool external_allocation_pool;
1070     pthread_mutex_t mutex_slab;
1071 };
1072 
1073 struct nine_allocation *
nine_allocate(struct nine_allocator * allocator,unsigned size)1074 nine_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 
nine_free(struct nine_allocator * allocator,struct nine_allocation * allocation)1085 void 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 
nine_free_worker(struct nine_allocator * allocator,struct nine_allocation * allocation)1095 void nine_free_worker(struct nine_allocator *allocator, struct nine_allocation *allocation)
1096 {
1097     nine_free(allocator, allocation);
1098 }
1099 
nine_get_pointer(struct nine_allocator * allocator,struct nine_allocation * allocation)1100 void *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 
nine_pointer_weakrelease(struct nine_allocator * allocator,struct nine_allocation * allocation)1108 void nine_pointer_weakrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)
1109 {
1110     (void)allocator;
1111     (void)allocation;
1112 }
1113 
nine_pointer_strongrelease(struct nine_allocator * allocator,struct nine_allocation * allocation)1114 void nine_pointer_strongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)
1115 {
1116     (void)allocator;
1117     (void)allocation;
1118 }
1119 
nine_pointer_delayedstrongrelease(struct nine_allocator * allocator,struct nine_allocation * allocation,unsigned * counter)1120 void 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 
1129 struct nine_allocation *
nine_suballocate(struct nine_allocator * allocator,struct nine_allocation * allocation,int offset)1130 nine_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 
1141 struct nine_allocation *
nine_wrap_external_pointer(struct nine_allocator * allocator,void * data)1142 nine_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 
1153 struct nine_allocator *
nine_allocator_create(struct NineDevice9 * device,int memfd_virtualsizelimit)1154 nine_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 
1169 void
nine_allocator_destroy(struct nine_allocator * allocator)1170 nine_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