Path: blob/21.2-virgl/src/gallium/frontends/nine/nine_memory_helper.c
4561 views
/*1* Copyright 2020 Axel Davy <[email protected]>2*3* Permission is hereby granted, free of charge, to any person obtaining a4* copy of this software and associated documentation files (the "Software"),5* to deal in the Software without restriction, including without limitation6* on the rights to use, copy, modify, merge, publish, distribute, sub7* license, and/or sell copies of the Software, and to permit persons to whom8* the Software is furnished to do so, subject to the following conditions:9*10* The above copyright notice and this permission notice (including the next11* paragraph) shall be included in all copies or substantial portions of the12* Software.13*14* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR15* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,16* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL17* THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,18* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR19* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE20* USE OR OTHER DEALINGS IN THE SOFTWARE. */2122/*23* Memory util function to allocate RAM backing for textures.24* DEFAULT textures are stored on GPU25* MANAGED textures have a RAM backing and upload the content to a GPU texture for use26* SYSTEMMEM textures are stored in RAM and are meant to be uploaded to DEFAULT textures.27* Basically SYSTEMMEM + DEFAULT enables to do manually what MANAGED does automatically.28*29* Once the GPU texture is created, the RAM backing of MANAGED textures can be used in30* two occasions:31* . Recreating the GPU texture (for example lod change, or GPU memory eviction)32* . Reading the texture content (some games do that to fill higher res versions of the texture)33*34* When a lot of textures are used, the amount of addressing space (virtual memory) taken by MANAGED35* and SYSTEMMEM textures can be significant and cause virtual memory exhaustion for 32 bits programs.36*37* One way to reduce the virtual memory taken is to ignore lod and delete the RAM backing of38* MANAGED textures once it is uploaded. If the texture is read, or evicted from GPU memory, the RAM39* backing would be recreated (Note that mapping the GPU memory is not acceptable as RAM memory is supposed40* to have smaller (fixed) stride constraints).41*42* Instead the approach taken here is to keep the RAM backing alive, but free its addressing space.43* In other words virtual memory usage is reduced, but the RAM usage of the app is the same.44* To do so, we use the memfd feature of the linux kernel. It enables to allocate a file45* stored in RAM and visible only to the app. We can map/unmap portions of the file as we need.46* When a portion is mapped, it takes virtual memory space. When it is not, it doesn't.47* The file is stored in RAM, and thus the access speed is the same as normal RAM. Using such48* file to allocate data enables to use more than 4GB RAM on 32 bits.49*50* This approach adds some overhead: when accessing mapped content the first time, pages are allocated51* by the system. This has a lot of overhead (several times the time to memset the area).52* Releasing these pages (when unmapping) has overhead too, though significantly less.53*54* This overhead however is much less significant than the overhead of downloading the GPU content.55* In addition, we reduce significantly the overhead spent in Gallium nine for new allocations by56* using the fact new contents of the file are zero-allocated. By not calling memset in Gallium nine,57* the overhead of page allocation happens client side, thus outside the d3d mutex. This should give58* a performance boost for multithreaded applications. As malloc also has this overhead (at least for59* large enough allocations which use mmap internally), allocating ends up faster than with the standard60* allocation path.61* By far the overhead induced by page allocation/deallocation is the biggest overhead involved in this62* code. It is reduced significantly with huge pages, but it is too complex to configure for the user63* to use it (and it has some memory management downsides too). The memset trick enables to move most of64* the overhead outside Nine anyway.65*66* To prevent useless unmappings quickly followed by mapping again, we do not unmap right away allocations67* that are not locked for access anymore. Indeed it is likely the allocation will be accessed several times68* in a row, for example first to fill it, then to upload it.69* We keep everything mapped until we reach a threshold of memory allocated. Then we use hints to prioritize70* which regions to unmap first. Thus virtual memory usage is only reduced when the threshold is reached.71*72* Multiple memfd files are used, each of 100MB. Thus memory usage (but not virtual memory usage) increases73* by amounts of 100MB. When not on x86 32 bits, we do use the standard malloc.74*75* Finally, for ease of use, we do not implement packing of allocation inside page-aligned regions.76* One allocation is given one page-aligned region inside a memfd file.77* Allocations smaller than a page (4KB on x86) go through malloc.78* As texture sizes are usually multiples of powers of two, allocations above the page size are typically79* multiples of the page size, thus space is not wasted in practice.80*81*/8283#include <errno.h>84#include <fcntl.h>85#include <limits.h>86#include <linux/memfd.h>87#include <pthread.h>88#include <stdio.h>89#include <sys/mman.h>90#include <sys/types.h>91#include <sys/stat.h>92#include <ulimit.h>93#include <unistd.h>9495#include "util/list.h"96#include "util/u_memory.h"97#include "util/slab.h"9899#include "nine_debug.h"100#include "nine_memory_helper.h"101#include "nine_state.h"102103104#define DIVUP(a,b) (((a)+(b)-1)/(b))105106/* Required alignment for allocations */107#define NINE_ALLOCATION_ALIGNMENT 32108109#define DBG_CHANNEL (DBG_BASETEXTURE|DBG_SURFACE|DBG_VOLUME|DBG_TEXTURE|DBG_CUBETEXTURE)110111/* Use memfd only for 32 bits. Check for memfd_create support */112#if defined(PIPE_ARCH_X86) && defined(HAVE_MEMFD_CREATE)113#define NINE_ENABLE_MEMFD114#endif115116#ifdef NINE_ENABLE_MEMFD117118struct nine_memfd_file_region {119unsigned offset;120unsigned size;121void *map; /* pointer to the mapped content of the file. Can be NULL */122int num_locks; /* Total number of locks blocking the munmap */123int num_weak_unlocks; /* Number of users which weakly block the munmap */124bool zero_filled;125struct list_head list;126};127128struct nine_memfd_file {129int fd;130int filesize; /* Size of the file */131struct list_head free_regions; /* This list is sorted by the offset, and consecutive regions are merged */132struct list_head unmapped_allocated_regions; /* This list and the following ones are not sorted */133struct list_head locked_mapped_allocated_regions;134struct list_head weak_unlocked_mapped_allocated_regions;135struct list_head unlocked_mapped_allocated_regions;136};137138/* The allocation is stored inside a memfd */139#define NINE_MEMFD_ALLOC 1140/* The allocation is part of another allocation, which is stored inside a memfd */141#define NINE_MEMFD_SUBALLOC 2142/* The allocation was allocated with malloc and will have to be freed */143#define NINE_MALLOC_ALLOC 3144/* The pointer doesn't need memory management */145#define NINE_EXTERNAL_ALLOC 4146147struct nine_memfd_allocation {148struct nine_memfd_file *file; /* File in which the data is allocated */149struct nine_memfd_file_region *region; /* Corresponding file memory region. Max 1 allocation per region */150};151152/* 'Suballocations' are used to represent subregions of an allocation.153* For example a given layer of a texture. These are not allocations,154* but can be accessed separately. To correctly handle accessing them,155* we encapsulate them into this structure. */156struct nine_memfd_suballocation {157struct nine_memfd_allocation *parent; /* Parent allocation */158int relative_offset; /* Offset relative to the parent */159};160161/* A standard allocation with malloc */162struct nine_malloc_allocation {163void *buf;164unsigned allocation_size;165};166167/* A pointer with no need of memory management.168* For example a pointer passed by the application,169* or a 'suballocation' inside a malloc-ed allocation. */170struct nine_external_allocation {171void *buf;172};173174/* Encapsulates all allocations */175struct nine_allocation {176unsigned allocation_type; /* Type of allocation */177union {178struct nine_memfd_allocation memfd;179struct nine_memfd_suballocation submemfd;180struct nine_malloc_allocation malloc;181struct nine_external_allocation external;182} memory;183struct list_head list_free; /* for pending frees */184/* The fields below are only used for memfd/submemfd allocations */185struct list_head list_release; /* for pending releases */186/* Handling of the CSMT thread:187* API calls are singled thread (global mutex protection).188* However we multithreading internally (CSMT worker thread).189* To handle this thread, we map/lock the allocation in the190* main thread and increase pending_counter. When the worker thread191* is done with the scheduled function, the pending_counter is decreased.192* If pending_counter is 0, locks_on_counter can be subtracted from193* active_locks (in the main thread). */194unsigned locks_on_counter;195unsigned *pending_counter;196/* Hint from the last unlock indicating the data might be locked again soon */197bool weak_unlock;198};199200struct nine_allocator {201struct NineDevice9 *device;202int page_size; /* Page size */203int num_fd_max; /* Max number of memfd files */204int min_file_size; /* Minimum memfd file size */205/* Tracking of all allocations */206long long total_allocations; /* Amount of memory allocated */207long long total_locked_memory; /* TODO */ /* Amount of memory blocked by a lock */208long long total_virtual_memory; /* Current virtual memory used by our allocations */209long long total_virtual_memory_limit; /* Target maximum virtual memory used. Above that, tries to unmap memfd files whenever possible. */210211int num_fd; /* Number of memfd files */ /* TODO release unused memfd files */212struct slab_mempool allocation_pool;213struct slab_mempool region_pool;214struct nine_memfd_file *memfd_pool; /* Table (of size num_fd) of memfd files */215struct list_head pending_releases; /* List of allocations with unlocks depending on pending_counter */ /* TODO: Elements seem removed only on flush. Destruction ? */216217pthread_mutex_t mutex_pending_frees;218struct list_head pending_frees;219};220221#ifdef DEBUG222223static void224debug_dump_memfd_state(struct nine_memfd_file *memfd_file, bool details)225{226struct nine_memfd_file_region *region;227228DBG("fd: %d, filesize: %d\n", memfd_file->fd, memfd_file->filesize);229if (!details)230return;231LIST_FOR_EACH_ENTRY(region, &memfd_file->free_regions, list) {232DBG("FREE block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",233region->offset, region->size, region->map,234region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);235}236LIST_FOR_EACH_ENTRY(region, &memfd_file->unmapped_allocated_regions, list) {237DBG("UNMAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",238region->offset, region->size, region->map,239region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);240}241LIST_FOR_EACH_ENTRY(region, &memfd_file->locked_mapped_allocated_regions, list) {242DBG("LOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",243region->offset, region->size, region->map,244region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);245}246LIST_FOR_EACH_ENTRY(region, &memfd_file->unlocked_mapped_allocated_regions, list) {247DBG("UNLOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",248region->offset, region->size, region->map,249region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);250}251LIST_FOR_EACH_ENTRY(region, &memfd_file->weak_unlocked_mapped_allocated_regions, list) {252DBG("WEAK UNLOCKED MAPPED ALLOCATED block: offset %d, size %d, map=%p, locks=%d, weak=%d, z=%d\n",253region->offset, region->size, region->map,254region->num_locks, region->num_weak_unlocks, (int)region->zero_filled);255}256}257258static void259debug_dump_allocation_state(struct nine_allocation *allocation)260{261switch(allocation->allocation_type) {262case NINE_MEMFD_ALLOC:263DBG("Allocation is stored in this memfd file:\n");264debug_dump_memfd_state(allocation->memory.memfd.file, true);265DBG("Allocation is offset: %d, size: %d\n",266allocation->memory.memfd.region->offset, allocation->memory.memfd.region->size);267break;268case NINE_MEMFD_SUBALLOC:269DBG("Allocation is suballocation at relative offset %d of this allocation:\n",270allocation->memory.submemfd.relative_offset);271DBG("Parent allocation is stored in this memfd file:\n");272debug_dump_memfd_state(allocation->memory.submemfd.parent->file, false);273DBG("Parent allocation is offset: %d, size: %d\n",274allocation->memory.submemfd.parent->region->offset,275allocation->memory.submemfd.parent->region->size);276break;277case NINE_MALLOC_ALLOC:278DBG("Allocation is a standard malloc\n");279break;280case NINE_EXTERNAL_ALLOC:281DBG("Allocation is a suballocation of a standard malloc or an external allocation\n");282break;283default:284assert(false);285}286}287288#else289290static void291debug_dump_memfd_state(struct nine_memfd_file *memfd_file, bool details)292{293(void)memfd_file;294(void)details;295}296297static void298debug_dump_allocation_state(struct nine_allocation *allocation)299{300(void)allocation;301}302303#endif304305static void306debug_dump_allocator_state(struct nine_allocator *allocator)307{308DBG("SURFACE ALLOCATOR STATUS:\n");309DBG("Total allocated: %lld\n", allocator->total_allocations);310DBG("Total virtual memory locked: %lld\n", allocator->total_locked_memory);311DBG("Virtual memory used: %lld / %lld\n", allocator->total_virtual_memory, allocator->total_virtual_memory_limit);312DBG("Num memfd files: %d / %d\n", allocator->num_fd, allocator->num_fd_max);313}314315316/* Retrieve file used for the storage of the content of this allocation.317* NULL if not using memfd */318static struct nine_memfd_file *319nine_get_memfd_file_backing(struct nine_allocation *allocation)320{321if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)322return NULL;323if (allocation->allocation_type == NINE_MEMFD_ALLOC)324return allocation->memory.memfd.file;325return allocation->memory.submemfd.parent->file;326}327328/* Retrieve region used for the storage of the content of this allocation.329* NULL if not using memfd */330static struct nine_memfd_file_region *331nine_get_memfd_region_backing(struct nine_allocation *allocation)332{333if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)334return NULL;335if (allocation->allocation_type == NINE_MEMFD_ALLOC)336return allocation->memory.memfd.region;337return allocation->memory.submemfd.parent->region;338}339340static void move_region(struct list_head *tail, struct nine_memfd_file_region *region)341{342/* Remove from previous list (if any) */343list_delinit(®ion->list);344/* Insert in new list (last) */345list_addtail(®ion->list, tail);346}347348#if 0349static void move_region_ordered(struct list_head *tail, struct nine_memfd_file_region *region)350{351struct nine_memfd_file_region *cur_region;352struct list_head *insertion_point = tail;353354/* Remove from previous list (if any) */355list_delinit(®ion->list);356357LIST_FOR_EACH_ENTRY(cur_region, tail, list) {358if (cur_region->offset > region->offset)359break;360insertion_point = &cur_region->list;361}362/* Insert just before cur_region */363list_add(®ion->list, insertion_point);364}365#endif366367static void move_region_ordered_merge(struct nine_allocator *allocator, struct list_head *tail, struct nine_memfd_file_region *region)368{369struct nine_memfd_file_region *p, *cur_region = NULL, *prev_region = NULL;370371/* Remove from previous list (if any) */372list_delinit(®ion->list);373374LIST_FOR_EACH_ENTRY(p, tail, list) {375cur_region = p;376if (cur_region->offset > region->offset)377break;378prev_region = cur_region;379}380381/* Insert after prev_region and before cur_region. Try to merge */382if (prev_region && ((prev_region->offset + prev_region->size) == region->offset)) {383if (cur_region && (cur_region->offset == (region->offset + region->size))) {384/* Merge all three regions */385prev_region->size += region->size + cur_region->size;386prev_region->zero_filled = prev_region->zero_filled && region->zero_filled && cur_region->zero_filled;387list_del(&cur_region->list);388slab_free_st(&allocator->region_pool, region);389slab_free_st(&allocator->region_pool, cur_region);390} else {391prev_region->size += region->size;392prev_region->zero_filled = prev_region->zero_filled && region->zero_filled;393slab_free_st(&allocator->region_pool, region);394}395} else if (cur_region && (cur_region->offset == (region->offset + region->size))) {396cur_region->offset = region->offset;397cur_region->size += region->size;398cur_region->zero_filled = region->zero_filled && cur_region->zero_filled;399slab_free_st(&allocator->region_pool, region);400} else {401list_add(®ion->list, prev_region ? &prev_region->list : tail);402}403}404405static struct nine_memfd_file_region *allocate_region(struct nine_allocator *allocator, unsigned offset, unsigned size) {406struct nine_memfd_file_region *region = slab_alloc_st(&allocator->allocation_pool);407if (!region)408return NULL;409region->offset = offset;410region->size = size;411region->num_locks = 0;412region->num_weak_unlocks = 0;413region->map = NULL;414region->zero_filled = false;415list_inithead(®ion->list);416return region;417}418419/* Go through memfd allocated files, and try to use unused memory for the requested allocation.420* Returns whether it suceeded */421static bool422insert_new_allocation(struct nine_allocator *allocator, struct nine_allocation *new_allocation, unsigned allocation_size)423{424int memfd_index;425struct nine_memfd_file *memfd_file, *best_memfd_file;426struct nine_memfd_file_region *region, *best_region, *new_region;427428429/* Find the smallest - but bigger than the requested size - unused memory430* region inside the memfd files. */431int min_blocksize = INT_MAX;432433for (memfd_index = 0; memfd_index < allocator->num_fd; memfd_index++) {434memfd_file = (void*)allocator->memfd_pool + memfd_index*sizeof(struct nine_memfd_file);435436LIST_FOR_EACH_ENTRY(region, &memfd_file->free_regions, list) {437if (region->size <= min_blocksize && region->size >= allocation_size) {438min_blocksize = region->size;439best_region = region;440best_memfd_file = memfd_file;441}442}443if (min_blocksize == allocation_size)444break;445}446447/* The allocation doesn't fit in any memfd file */448if (min_blocksize == INT_MAX)449return false;450451/* Target region found */452/* Move from free to unmapped allocated */453best_region->size = DIVUP(allocation_size, allocator->page_size) * allocator->page_size;454assert(min_blocksize >= best_region->size);455move_region(&best_memfd_file->unmapped_allocated_regions, best_region);456new_allocation->memory.memfd.region = best_region;457new_allocation->memory.memfd.file = best_memfd_file;458459/* If the original region is bigger than needed, add new region with remaining space */460min_blocksize -= best_region->size;461if (min_blocksize > 0) {462new_region = allocate_region(allocator, best_region->offset + best_region->size, min_blocksize);463new_region->zero_filled = best_region->zero_filled;464move_region_ordered_merge(allocator, &best_memfd_file->free_regions, new_region);465}466allocator->total_allocations += best_region->size;467return true;468}469470/* Go through allocations with unlocks waiting on pending_counter being 0.471* If 0 is indeed reached, update the allocation status */472static void473nine_flush_pending_releases(struct nine_allocator *allocator)474{475struct nine_allocation *allocation, *ptr;476LIST_FOR_EACH_ENTRY_SAFE(allocation, ptr, &allocator->pending_releases, list_release) {477assert(allocation->locks_on_counter > 0);478/* If pending_releases reached 0, remove from the list and update the status */479if (*allocation->pending_counter == 0) {480struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);481struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation);482region->num_locks -= allocation->locks_on_counter;483allocation->locks_on_counter = 0;484list_delinit(&allocation->list_release);485if (region->num_locks == 0) {486/* Move to the correct list */487if (region->num_weak_unlocks)488move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region);489else490move_region(&memfd_file->unlocked_mapped_allocated_regions, region);491allocator->total_locked_memory -= region->size;492}493}494}495}496497static void498nine_free_internal(struct nine_allocator *allocator, struct nine_allocation *allocation);499500static void501nine_flush_pending_frees(struct nine_allocator *allocator)502{503struct nine_allocation *allocation, *ptr;504505pthread_mutex_lock(&allocator->mutex_pending_frees);506/* The order of release matters as suballocations are supposed to be released first */507LIST_FOR_EACH_ENTRY_SAFE(allocation, ptr, &allocator->pending_frees, list_free) {508/* Set the allocation in an unlocked state, and then free it */509if (allocation->allocation_type == NINE_MEMFD_ALLOC ||510allocation->allocation_type == NINE_MEMFD_SUBALLOC) {511struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);512struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation);513if (region->num_locks != 0) {514region->num_locks = 0;515allocator->total_locked_memory -= region->size;516/* Useless, but to keep consistency */517move_region(&memfd_file->unlocked_mapped_allocated_regions, region);518}519region->num_weak_unlocks = 0;520allocation->weak_unlock = false;521allocation->locks_on_counter = 0;522list_delinit(&allocation->list_release);523}524list_delinit(&allocation->list_free);525nine_free_internal(allocator, allocation);526}527pthread_mutex_unlock(&allocator->mutex_pending_frees);528}529530/* Try to unmap the memfd_index-th file if not already unmapped.531* If even_if_weak is False, will not unmap if there are weak unlocks */532static void533nine_memfd_unmap_region(struct nine_allocator *allocator,534struct nine_memfd_file *memfd_file,535struct nine_memfd_file_region *region)536{537DBG("Unmapping memfd mapped region at %d: size: %d, map=%p, locks=%d, weak=%d\n",538region->offset, region->size, region->map,539region->num_locks, region->num_weak_unlocks);540assert(region->map != NULL);541542if (munmap(region->map, region->size) != 0)543fprintf(stderr, "Error on unmapping, errno=%d\n", (int)errno);544545region->map = NULL;546/* Move from one of the mapped region list to the unmapped one */547move_region(&memfd_file->unmapped_allocated_regions, region);548allocator->total_virtual_memory -= region->size;549}550551/* Unallocate a region of a memfd file */552static void553remove_allocation(struct nine_allocator *allocator, struct nine_memfd_file *memfd_file, struct nine_memfd_file_region *region)554{555assert(region->num_locks == 0);556region->num_weak_unlocks = 0;557/* Move from mapped region to unmapped region */558if (region->map) {559if (likely(!region->zero_filled)) {560/* As the region is mapped, it is likely the pages are allocated.561* Do the memset now for when we allocate again. It is much faster now,562* as the pages are allocated. */563DBG("memset on data=%p, size %d\n", region->map, region->size);564memset(region->map, 0, region->size);565region->zero_filled = true;566}567nine_memfd_unmap_region(allocator, memfd_file, region);568}569/* Move from unmapped region to free region */570allocator->total_allocations -= region->size;571move_region_ordered_merge(allocator, &memfd_file->free_regions, region);572}573574/* Try to unmap the regions of the memfd_index-th file if not already unmapped.575* If even_if_weak is False, will not unmap if there are weak unlocks */576static void577nine_memfd_try_unmap_file(struct nine_allocator *allocator,578int memfd_index,579bool weak)580{581struct nine_memfd_file *memfd_file = (void*)allocator->memfd_pool + memfd_index*sizeof(struct nine_memfd_file);582struct nine_memfd_file_region *region, *ptr;583DBG("memfd file at %d: fd: %d, filesize: %d\n",584memfd_index, memfd_file->fd, memfd_file->filesize);585debug_dump_memfd_state(memfd_file, true);586LIST_FOR_EACH_ENTRY_SAFE(region, ptr,587weak ?588&memfd_file->weak_unlocked_mapped_allocated_regions :589&memfd_file->unlocked_mapped_allocated_regions,590list) {591nine_memfd_unmap_region(allocator, memfd_file, region);592}593}594595/* Unmap files until we are below the virtual memory target limit.596* If unmap_everything_possible is set, ignore the limit and unmap597* all that can be unmapped. */598static void599nine_memfd_files_unmap(struct nine_allocator *allocator,600bool unmap_everything_possible)601{602long long memory_limit = unmap_everything_possible ?6030 : allocator->total_virtual_memory_limit;604int i;605606/* We are below the limit. Do nothing */607if (memory_limit >= allocator->total_virtual_memory)608return;609610/* Update allocations with pending releases */611nine_flush_pending_releases(allocator);612613DBG("Trying to unmap files with no weak unlock (%lld / %lld)\n",614allocator->total_virtual_memory, memory_limit);615616/* Try to release everything with no weak releases.617* Those have data not needed for a long time (and618* possibly ever). */619for (i = 0; i < allocator->num_fd; i++) {620nine_memfd_try_unmap_file(allocator, i, false);621if (memory_limit >= allocator->total_virtual_memory) {622return;}623}624625DBG("Trying to unmap files even with weak unlocks (%lld / %lld)\n",626allocator->total_virtual_memory, memory_limit);627628/* This wasn't enough. Also release files with weak releases */629for (i = 0; i < allocator->num_fd; i++) {630nine_memfd_try_unmap_file(allocator, i, true);631/* Stop if the target is reached */632if (memory_limit >= allocator->total_virtual_memory) {633return;}634}635636if (!unmap_everything_possible)637return;638639/* If there are some pending uploads, execute them,640* and retry. */641if (list_is_empty(&allocator->pending_releases)) {642return;}643nine_csmt_process(allocator->device);644nine_flush_pending_releases(allocator);645646DBG("Retrying after flushing (%lld / %lld)\n",647allocator->total_virtual_memory, memory_limit);648649for (i = 0; i < allocator->num_fd; i++) {650nine_memfd_try_unmap_file(allocator, i, false);651nine_memfd_try_unmap_file(allocator, i, true);652}653/* We have done all we could */654}655656/* Map a given memfd file */657static bool658nine_memfd_region_map(struct nine_allocator *allocator, struct nine_memfd_file *memfd_file, struct nine_memfd_file_region *region)659{660if (region->map != NULL)661return true;662663debug_dump_memfd_state(memfd_file, true);664nine_memfd_files_unmap(allocator, false);665666void *buf = mmap(NULL, region->size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd_file->fd, region->offset);667668if (buf == MAP_FAILED && errno == ENOMEM) {669DBG("Failed to mmap a memfd file - trying to unmap other files\n");670nine_memfd_files_unmap(allocator, true);671buf = mmap(NULL, region->size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd_file->fd, region->offset);672}673if (buf == MAP_FAILED) {674DBG("Failed to mmap a memfd file, errno=%d\n", (int)errno);675return false;676}677region->map = buf;678/* no need to move to an unlocked mapped regions list, the caller will handle the list */679allocator->total_virtual_memory += region->size;680assert((uintptr_t)buf % NINE_ALLOCATION_ALIGNMENT == 0); /* mmap should be page_size aligned, so it should be fine */681682return true;683}684685/* Allocate with memfd some memory. Returns True if successful. */686static bool687nine_memfd_allocator(struct nine_allocator *allocator,688struct nine_allocation *new_allocation,689unsigned allocation_size)690{691struct nine_memfd_file *memfd_file;692struct nine_memfd_file_region *region;693694allocation_size = DIVUP(allocation_size, allocator->page_size) * allocator->page_size;695new_allocation->allocation_type = NINE_MEMFD_ALLOC;696new_allocation->locks_on_counter = 0;697new_allocation->pending_counter = NULL;698new_allocation->weak_unlock = false;699list_inithead(&new_allocation->list_free);700list_inithead(&new_allocation->list_release);701702/* Try to find free space in a file already allocated */703if (insert_new_allocation(allocator, new_allocation, allocation_size))704return true;705706/* No - allocate new memfd file */707708if (allocator->num_fd == allocator->num_fd_max)709return false; /* Too many memfd files */710711allocator->num_fd++;712memfd_file = (void*)allocator->memfd_pool + (allocator->num_fd-1)*sizeof(struct nine_memfd_file);713/* If the allocation size is above the memfd file default size, use a bigger size */714memfd_file->filesize = MAX2(allocation_size, allocator->min_file_size);715716memfd_file->fd = memfd_create("gallium_nine_ram", 0);717if (memfd_file->fd == -1) {718DBG("Failed to created a memfd file, errno=%d\n", (int)errno);719allocator->num_fd--;720return false;721}722723if (ftruncate(memfd_file->fd, memfd_file->filesize) != 0) {724DBG("Failed to resize a memfd file, errno=%d\n", (int)errno);725close(memfd_file->fd);726allocator->num_fd--;727return false;728}729730list_inithead(&memfd_file->free_regions);731list_inithead(&memfd_file->unmapped_allocated_regions);732list_inithead(&memfd_file->locked_mapped_allocated_regions);733list_inithead(&memfd_file->unlocked_mapped_allocated_regions);734list_inithead(&memfd_file->weak_unlocked_mapped_allocated_regions);735736/* Initialize the memfd file with empty region and the allocation */737region = allocate_region(allocator, 0, allocation_size);738region->zero_filled = true; /* ftruncate does zero-fill the new data */739list_add(®ion->list, &memfd_file->unmapped_allocated_regions);740new_allocation->memory.memfd.file = memfd_file;741new_allocation->memory.memfd.region = region;742allocator->total_allocations += allocation_size;743744if (allocation_size == memfd_file->filesize)745return true;746747/* Add empty region */748region = allocate_region(allocator, allocation_size, memfd_file->filesize - allocation_size);749region->zero_filled = true; /* ftruncate does zero-fill the new data */750list_add(®ion->list, &memfd_file->free_regions);751752return true;753}754755/* Allocate memory */756struct nine_allocation *757nine_allocate(struct nine_allocator *allocator, unsigned size)758{759760struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool);761debug_dump_allocator_state(allocator);762if (!new_allocation)763return NULL;764765nine_flush_pending_frees(allocator);766767/* Restrict to >= page_size to prevent having too much fragmentation, as the size of768* allocations is rounded to the next page_size multiple. */769if (size >= allocator->page_size && allocator->total_virtual_memory_limit >= 0 &&770nine_memfd_allocator(allocator, new_allocation, size)) {771struct nine_memfd_file_region *region = new_allocation->memory.memfd.region;772if (!region->zero_filled) {773void *data = nine_get_pointer(allocator, new_allocation);774if (!data) {775ERR("INTERNAL MMAP FOR NEW ALLOCATION FAILED\n");776nine_free(allocator, new_allocation);777return NULL;778}779DBG("memset on data=%p, size %d\n", data, region->size);780memset(data, 0, region->size);781region->zero_filled = true;782/* Even though the user usually fills afterward, we don't weakrelease.783* The reason is suballocations don't affect the weakrelease state of their784* parents. Thus if only suballocations are accessed, the release would stay785* weak forever. */786nine_pointer_strongrelease(allocator, new_allocation);787}788DBG("ALLOCATION SUCCESSFUL\n");789debug_dump_allocation_state(new_allocation);790return new_allocation;791}792793void *data = align_calloc(size, NINE_ALLOCATION_ALIGNMENT);794if (!data) {795DBG("ALLOCATION FAILED\n");796return NULL;797}798799new_allocation->allocation_type = NINE_MALLOC_ALLOC;800new_allocation->memory.malloc.buf = data;801new_allocation->memory.malloc.allocation_size = size;802list_inithead(&new_allocation->list_free);803allocator->total_allocations += size;804allocator->total_locked_memory += size;805allocator->total_virtual_memory += size;806DBG("ALLOCATION SUCCESSFUL\n");807debug_dump_allocation_state(new_allocation);808return new_allocation;809}810811/* Release memory */812static void813nine_free_internal(struct nine_allocator *allocator, struct nine_allocation *allocation)814{815DBG("RELEASING ALLOCATION\n");816debug_dump_allocation_state(allocation);817if (allocation->allocation_type == NINE_MALLOC_ALLOC) {818allocator->total_allocations -= allocation->memory.malloc.allocation_size;819allocator->total_locked_memory -= allocation->memory.malloc.allocation_size;820allocator->total_virtual_memory -= allocation->memory.malloc.allocation_size;821align_free(allocation->memory.malloc.buf);822} else if (allocation->allocation_type == NINE_MEMFD_ALLOC ||823allocation->allocation_type == NINE_MEMFD_SUBALLOC) {824struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);825struct nine_memfd_file_region *region = nine_get_memfd_region_backing(allocation);826if (allocation->weak_unlock)827region->num_weak_unlocks--;828if (allocation->allocation_type == NINE_MEMFD_ALLOC)829remove_allocation(allocator, memfd_file, region);830}831832slab_free_st(&allocator->allocation_pool, allocation);833debug_dump_allocator_state(allocator);834}835836837void838nine_free(struct nine_allocator *allocator, struct nine_allocation *allocation)839{840nine_flush_pending_frees(allocator);841nine_flush_pending_releases(allocator);842nine_free_internal(allocator, allocation);843}844845/* Called from the worker thread. Similar to nine_free except we are not in the main thread, thus846* we are disallowed to change the allocator structures except the fields reserved847* for the worker. In addition, the allocation is allowed to not being unlocked (the release848* will unlock it) */849void nine_free_worker(struct nine_allocator *allocator, struct nine_allocation *allocation)850{851/* Add the allocation to the list of pending allocations to free */852pthread_mutex_lock(&allocator->mutex_pending_frees);853/* The order of free matters as suballocations are supposed to be released first */854list_addtail(&allocation->list_free, &allocator->pending_frees);855pthread_mutex_unlock(&allocator->mutex_pending_frees);856}857858/* Lock an allocation, and retrieve the pointer */859void *860nine_get_pointer(struct nine_allocator *allocator, struct nine_allocation *allocation)861{862struct nine_memfd_file *memfd_file;863struct nine_memfd_file_region *region;864865nine_flush_pending_releases(allocator);866DBG("allocation_type: %d\n", allocation->allocation_type);867868if (allocation->allocation_type == NINE_MALLOC_ALLOC)869return allocation->memory.malloc.buf;870if (allocation->allocation_type == NINE_EXTERNAL_ALLOC)871return allocation->memory.external.buf;872873memfd_file = nine_get_memfd_file_backing(allocation);874region = nine_get_memfd_region_backing(allocation);875if (!nine_memfd_region_map(allocator, memfd_file, region)) {876DBG("Couldn't map memfd region for get_pointer\n");877return NULL;878}879880move_region(&memfd_file->locked_mapped_allocated_regions, region); /* Note: redundant if region->num_locks */881region->num_locks++;882883if (region->num_locks == 1)884allocator->total_locked_memory += region->size;885if (allocation->weak_unlock)886region->num_weak_unlocks--;887allocation->weak_unlock = false;888region->zero_filled = false;889890891if (allocation->allocation_type == NINE_MEMFD_ALLOC)892return region->map;893if (allocation->allocation_type == NINE_MEMFD_SUBALLOC)894return region->map + allocation->memory.submemfd.relative_offset;895896assert(false);897return NULL;898}899900/* Unlock an allocation, but with hint that we might lock again soon */901void902nine_pointer_weakrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)903{904struct nine_memfd_file_region *region;905if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)906return;907908region = nine_get_memfd_region_backing(allocation);909if (!allocation->weak_unlock)910region->num_weak_unlocks++;911allocation->weak_unlock = true;912region->num_locks--;913if (region->num_locks == 0) {914struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);915allocator->total_locked_memory -= region->size;916move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region);917}918}919920/* Unlock an allocation */921void922nine_pointer_strongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)923{924struct nine_memfd_file_region *region;925if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)926return;927928region = nine_get_memfd_region_backing(allocation);929region->num_locks--;930if (region->num_locks == 0) {931struct nine_memfd_file *memfd_file = nine_get_memfd_file_backing(allocation);932allocator->total_locked_memory -= region->size;933if (region->num_weak_unlocks)934move_region(&memfd_file->weak_unlocked_mapped_allocated_regions, region);935else936move_region(&memfd_file->unlocked_mapped_allocated_regions, region);937}938}939940/* Delay a release to when a given counter becomes zero */941void942nine_pointer_delayedstrongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation, unsigned *counter)943{944if (allocation->allocation_type > NINE_MEMFD_SUBALLOC)945return;946947assert(allocation->pending_counter == NULL || allocation->pending_counter == counter);948allocation->pending_counter = counter;949allocation->locks_on_counter++;950951if (list_is_empty(&allocation->list_release))952list_add(&allocation->list_release, &allocator->pending_releases);953}954955/* Create a suballocation of an allocation */956struct nine_allocation *957nine_suballocate(struct nine_allocator* allocator, struct nine_allocation *allocation, int offset)958{959struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool);960if (!new_allocation)961return NULL;962963DBG("Suballocate allocation at offset: %d\n", offset);964assert(allocation->allocation_type != NINE_MEMFD_SUBALLOC);965list_inithead(&new_allocation->list_free);966967if (allocation->allocation_type != NINE_MEMFD_ALLOC) {968new_allocation->allocation_type = NINE_EXTERNAL_ALLOC;969if (allocation->allocation_type == NINE_MALLOC_ALLOC)970new_allocation->memory.external.buf = allocation->memory.malloc.buf + offset;971else972new_allocation->memory.external.buf = allocation->memory.external.buf + offset;973return new_allocation;974}975new_allocation->allocation_type = NINE_MEMFD_SUBALLOC;976new_allocation->memory.submemfd.parent = &allocation->memory.memfd;977new_allocation->memory.submemfd.relative_offset = offset;978new_allocation->locks_on_counter = 0;979new_allocation->pending_counter = NULL;980new_allocation->weak_unlock = false;981list_inithead(&new_allocation->list_release);982debug_dump_allocation_state(new_allocation);983return new_allocation;984}985986/* Wrap an external pointer as an allocation */987struct nine_allocation *988nine_wrap_external_pointer(struct nine_allocator* allocator, void* data)989{990struct nine_allocation *new_allocation = slab_alloc_st(&allocator->allocation_pool);991if (!new_allocation)992return NULL;993DBG("Wrapping external pointer: %p\n", data);994new_allocation->allocation_type = NINE_EXTERNAL_ALLOC;995new_allocation->memory.external.buf = data;996list_inithead(&new_allocation->list_free);997return new_allocation;998}9991000struct nine_allocator *1001nine_allocator_create(struct NineDevice9 *device, int memfd_virtualsizelimit)1002{1003struct nine_allocator* allocator = MALLOC(sizeof(struct nine_allocator));10041005if (!allocator)1006return NULL;10071008allocator->device = device;1009allocator->page_size = sysconf(_SC_PAGESIZE);1010assert(allocator->page_size == 4 << 10);1011allocator->num_fd_max = (memfd_virtualsizelimit >= 0) ? MIN2(128, ulimit(__UL_GETOPENMAX)) : 0;1012allocator->min_file_size = DIVUP(100 * (1 << 20), allocator->page_size) * allocator->page_size; /* 100MB files */1013allocator->total_allocations = 0;1014allocator->total_locked_memory = 0;1015allocator->total_virtual_memory = 0;1016allocator->total_virtual_memory_limit = memfd_virtualsizelimit * (1 << 20);1017allocator->num_fd = 0;10181019DBG("Allocator created (ps: %d; fm: %d)\n", allocator->page_size, allocator->num_fd_max);10201021slab_create(&allocator->allocation_pool, sizeof(struct nine_allocation), 4096);1022slab_create(&allocator->region_pool, sizeof(struct nine_memfd_file_region), 4096);1023allocator->memfd_pool = CALLOC(allocator->num_fd_max, sizeof(struct nine_memfd_file));1024list_inithead(&allocator->pending_releases);1025list_inithead(&allocator->pending_frees);1026pthread_mutex_init(&allocator->mutex_pending_frees, NULL);1027return allocator;1028}10291030void1031nine_allocator_destroy(struct nine_allocator* allocator)1032{1033int i;1034DBG("DESTROYING ALLOCATOR\n");1035debug_dump_allocator_state(allocator);1036nine_flush_pending_releases(allocator);1037nine_flush_pending_frees(allocator);1038nine_memfd_files_unmap(allocator, true);1039pthread_mutex_destroy(&allocator->mutex_pending_frees);10401041assert(list_is_empty(&allocator->pending_frees));1042assert(list_is_empty(&allocator->pending_releases));1043for (i = 0; i < allocator->num_fd; i++) {1044debug_dump_memfd_state(&allocator->memfd_pool[i], true);1045assert(list_is_empty(&allocator->memfd_pool[i].locked_mapped_allocated_regions));1046assert(list_is_empty(&allocator->memfd_pool[i].weak_unlocked_mapped_allocated_regions));1047assert(list_is_empty(&allocator->memfd_pool[i].unlocked_mapped_allocated_regions));1048assert(list_is_singular(&allocator->memfd_pool[i].free_regions));1049slab_free_st(&allocator->region_pool,1050list_first_entry(&allocator->memfd_pool[i].free_regions,1051struct nine_memfd_file_region, list));1052close(allocator->memfd_pool[i].fd);1053}1054slab_destroy(&allocator->allocation_pool);1055slab_destroy(&allocator->region_pool);1056FREE(allocator->memfd_pool);1057FREE(allocator);1058}10591060#else10611062struct nine_allocation {1063unsigned is_external;1064void *external;1065};10661067struct nine_allocator {1068struct slab_mempool external_allocation_pool;1069pthread_mutex_t mutex_slab;1070};10711072struct nine_allocation *1073nine_allocate(struct nine_allocator *allocator, unsigned size)1074{1075struct nine_allocation *allocation;1076(void)allocator;1077assert(sizeof(struct nine_allocation) <= NINE_ALLOCATION_ALIGNMENT);1078allocation = align_calloc(size + NINE_ALLOCATION_ALIGNMENT, NINE_ALLOCATION_ALIGNMENT);1079allocation->is_external = false;1080return allocation;1081}108210831084void nine_free(struct nine_allocator *allocator, struct nine_allocation *allocation)1085{1086if (allocation->is_external) {1087pthread_mutex_lock(&allocator->mutex_slab);1088slab_free_st(&allocator->external_allocation_pool, allocation);1089pthread_mutex_unlock(&allocator->mutex_slab);1090} else1091align_free(allocation);1092}10931094void nine_free_worker(struct nine_allocator *allocator, struct nine_allocation *allocation)1095{1096nine_free(allocator, allocation);1097}10981099void *nine_get_pointer(struct nine_allocator *allocator, struct nine_allocation *allocation)1100{1101(void)allocator;1102if (allocation->is_external)1103return allocation->external;1104return (uint8_t *)allocation + NINE_ALLOCATION_ALIGNMENT;1105}11061107void nine_pointer_weakrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)1108{1109(void)allocator;1110(void)allocation;1111}11121113void nine_pointer_strongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)1114{1115(void)allocator;1116(void)allocation;1117}11181119void nine_pointer_delayedstrongrelease(struct nine_allocator *allocator,1120struct nine_allocation *allocation,1121unsigned *counter)1122{1123(void)allocator;1124(void)allocation;1125(void)counter;1126}11271128struct nine_allocation *1129nine_suballocate(struct nine_allocator* allocator, struct nine_allocation *allocation, int offset)1130{1131struct nine_allocation *new_allocation;1132pthread_mutex_lock(&allocator->mutex_slab);1133new_allocation = slab_alloc_st(&allocator->external_allocation_pool);1134pthread_mutex_unlock(&allocator->mutex_slab);1135new_allocation->is_external = true;1136new_allocation->external = (uint8_t *)allocation + NINE_ALLOCATION_ALIGNMENT + offset;1137return new_allocation;1138}11391140struct nine_allocation *1141nine_wrap_external_pointer(struct nine_allocator* allocator, void* data)1142{1143struct nine_allocation *new_allocation;1144pthread_mutex_lock(&allocator->mutex_slab);1145new_allocation = slab_alloc_st(&allocator->external_allocation_pool);1146pthread_mutex_unlock(&allocator->mutex_slab);1147new_allocation->is_external = true;1148new_allocation->external = data;1149return new_allocation;1150}11511152struct nine_allocator *1153nine_allocator_create(struct NineDevice9 *device, int memfd_virtualsizelimit)1154{1155struct nine_allocator* allocator = MALLOC(sizeof(struct nine_allocator));1156(void)device;1157(void)memfd_virtualsizelimit;11581159if (!allocator)1160return NULL;11611162slab_create(&allocator->external_allocation_pool, sizeof(struct nine_allocation), 4096);1163pthread_mutex_init(&allocator->mutex_slab, NULL);11641165return allocator;1166}11671168void1169nine_allocator_destroy(struct nine_allocator *allocator)1170{1171slab_destroy(&allocator->external_allocation_pool);1172pthread_mutex_destroy(&allocator->mutex_slab);1173}11741175#endif /* NINE_ENABLE_MEMFD */117611771178