LCOV - code coverage report
Current view: top level - src/backend/utils/mmgr - bump.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 104 129 80.6 %
Date: 2025-08-17 01:17:32 Functions: 12 18 66.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * bump.c
       4             :  *    Bump allocator definitions.
       5             :  *
       6             :  * Bump is a MemoryContext implementation designed for memory usages which
       7             :  * require allocating a large number of chunks, none of which ever need to be
       8             :  * pfree'd or realloc'd.  Chunks allocated by this context have no chunk header
       9             :  * and operations which ordinarily require looking at the chunk header cannot
      10             :  * be performed.  For example, pfree, realloc, GetMemoryChunkSpace and
      11             :  * GetMemoryChunkContext are all not possible with bump allocated chunks.  The
      12             :  * only way to release memory allocated by this context type is to reset or
      13             :  * delete the context.
      14             :  *
      15             :  * Portions Copyright (c) 2024-2025, PostgreSQL Global Development Group
      16             :  *
      17             :  * IDENTIFICATION
      18             :  *    src/backend/utils/mmgr/bump.c
      19             :  *
      20             :  *
      21             :  *  Bump is best suited to cases which require a large number of short-lived
      22             :  *  chunks where performance matters.  Because bump allocated chunks don't
      23             :  *  have a chunk header, it can fit more chunks on each block.  This means we
      24             :  *  can do more with less memory and fewer cache lines.  The reason it's best
      25             :  *  suited for short-lived usages of memory is that ideally, pointers to bump
      26             :  *  allocated chunks won't be visible to a large amount of code.  The more
      27             :  *  code that operates on memory allocated by this allocator, the more chances
      28             :  *  that some code will try to perform a pfree or one of the other operations
      29             :  *  which are made impossible due to the lack of chunk header.  In order to
      30             :  *  detect accidental usage of the various disallowed operations, we do add a
      31             :  *  MemoryChunk chunk header in MEMORY_CONTEXT_CHECKING builds and have the
      32             :  *  various disallowed functions raise an ERROR.
      33             :  *
      34             :  *  Allocations are MAXALIGNed.
      35             :  *
      36             :  *-------------------------------------------------------------------------
      37             :  */
      38             : 
      39             : #include "postgres.h"
      40             : 
      41             : #include "lib/ilist.h"
      42             : #include "port/pg_bitutils.h"
      43             : #include "utils/memdebug.h"
      44             : #include "utils/memutils.h"
      45             : #include "utils/memutils_memorychunk.h"
      46             : #include "utils/memutils_internal.h"
      47             : 
      48             : #define Bump_BLOCKHDRSZ     MAXALIGN(sizeof(BumpBlock))
      49             : #define FIRST_BLOCKHDRSZ    (MAXALIGN(sizeof(BumpContext)) + \
      50             :                              Bump_BLOCKHDRSZ)
      51             : 
      52             : /* No chunk header unless built with MEMORY_CONTEXT_CHECKING */
      53             : #ifdef MEMORY_CONTEXT_CHECKING
      54             : #define Bump_CHUNKHDRSZ sizeof(MemoryChunk)
      55             : #else
      56             : #define Bump_CHUNKHDRSZ 0
      57             : #endif
      58             : 
      59             : #define Bump_CHUNK_FRACTION 8
      60             : 
      61             : /* The keeper block is allocated in the same allocation as the set */
      62             : #define KeeperBlock(set) ((BumpBlock *) ((char *) (set) + \
      63             :             MAXALIGN(sizeof(BumpContext))))
      64             : #define IsKeeperBlock(set, blk) (KeeperBlock(set) == (blk))
      65             : 
      66             : typedef struct BumpBlock BumpBlock; /* forward reference */
      67             : 
      68             : typedef struct BumpContext
      69             : {
      70             :     MemoryContextData header;   /* Standard memory-context fields */
      71             : 
      72             :     /* Bump context parameters */
      73             :     uint32      initBlockSize;  /* initial block size */
      74             :     uint32      maxBlockSize;   /* maximum block size */
      75             :     uint32      nextBlockSize;  /* next block size to allocate */
      76             :     uint32      allocChunkLimit;    /* effective chunk size limit */
      77             : 
      78             :     dlist_head  blocks;         /* list of blocks with the block currently
      79             :                                  * being filled at the head */
      80             : } BumpContext;
      81             : 
      82             : /*
      83             :  * BumpBlock
      84             :  *      BumpBlock is the unit of memory that is obtained by bump.c from
      85             :  *      malloc().  It contains zero or more allocations, which are the
      86             :  *      units requested by palloc().
      87             :  */
      88             : struct BumpBlock
      89             : {
      90             :     dlist_node  node;           /* doubly-linked list of blocks */
      91             : #ifdef MEMORY_CONTEXT_CHECKING
      92             :     BumpContext *context;       /* pointer back to the owning context */
      93             : #endif
      94             :     char       *freeptr;        /* start of free space in this block */
      95             :     char       *endptr;         /* end of space in this block */
      96             : };
      97             : 
      98             : /*
      99             :  * BumpIsValid
     100             :  *      True iff set is valid bump context.
     101             :  */
     102             : #define BumpIsValid(set) \
     103             :     (PointerIsValid(set) && IsA(set, BumpContext))
     104             : 
     105             : /*
     106             :  * We always store external chunks on a dedicated block.  This makes fetching
     107             :  * the block from an external chunk easy since it's always the first and only
     108             :  * chunk on the block.
     109             :  */
     110             : #define ExternalChunkGetBlock(chunk) \
     111             :     (BumpBlock *) ((char *) chunk - Bump_BLOCKHDRSZ)
     112             : 
     113             : /* Inlined helper functions */
     114             : static inline void BumpBlockInit(BumpContext *context, BumpBlock *block,
     115             :                                  Size blksize);
     116             : static inline bool BumpBlockIsEmpty(BumpBlock *block);
     117             : static inline void BumpBlockMarkEmpty(BumpBlock *block);
     118             : static inline Size BumpBlockFreeBytes(BumpBlock *block);
     119             : static inline void BumpBlockFree(BumpContext *set, BumpBlock *block);
     120             : 
     121             : 
     122             : /*
     123             : * BumpContextCreate
     124             : *       Create a new Bump context.
     125             : *
     126             : * parent: parent context, or NULL if top-level context
     127             : * name: name of context (must be statically allocated)
     128             : * minContextSize: minimum context size
     129             : * initBlockSize: initial allocation block size
     130             : * maxBlockSize: maximum allocation block size
     131             : */
     132             : MemoryContext
     133      507760 : BumpContextCreate(MemoryContext parent, const char *name, Size minContextSize,
     134             :                   Size initBlockSize, Size maxBlockSize)
     135             : {
     136             :     Size        firstBlockSize;
     137             :     Size        allocSize;
     138             :     BumpContext *set;
     139             :     BumpBlock  *block;
     140             : 
     141             :     /* ensure MemoryChunk's size is properly maxaligned */
     142             :     StaticAssertDecl(Bump_CHUNKHDRSZ == MAXALIGN(Bump_CHUNKHDRSZ),
     143             :                      "sizeof(MemoryChunk) is not maxaligned");
     144             : 
     145             :     /*
     146             :      * First, validate allocation parameters.  Asserts seem sufficient because
     147             :      * nobody varies their parameters at runtime.  We somewhat arbitrarily
     148             :      * enforce a minimum 1K block size.  We restrict the maximum block size to
     149             :      * MEMORYCHUNK_MAX_BLOCKOFFSET as MemoryChunks are limited to this in
     150             :      * regards to addressing the offset between the chunk and the block that
     151             :      * the chunk is stored on.  We would be unable to store the offset between
     152             :      * the chunk and block for any chunks that were beyond
     153             :      * MEMORYCHUNK_MAX_BLOCKOFFSET bytes into the block if the block was to be
     154             :      * larger than this.
     155             :      */
     156             :     Assert(initBlockSize == MAXALIGN(initBlockSize) &&
     157             :            initBlockSize >= 1024);
     158             :     Assert(maxBlockSize == MAXALIGN(maxBlockSize) &&
     159             :            maxBlockSize >= initBlockSize &&
     160             :            AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */
     161             :     Assert(minContextSize == 0 ||
     162             :            (minContextSize == MAXALIGN(minContextSize) &&
     163             :             minContextSize >= 1024 &&
     164             :             minContextSize <= maxBlockSize));
     165             :     Assert(maxBlockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET);
     166             : 
     167             :     /* Determine size of initial block */
     168      507760 :     allocSize = MAXALIGN(sizeof(BumpContext)) + Bump_BLOCKHDRSZ +
     169             :         Bump_CHUNKHDRSZ;
     170      507760 :     if (minContextSize != 0)
     171           0 :         allocSize = Max(allocSize, minContextSize);
     172             :     else
     173      507760 :         allocSize = Max(allocSize, initBlockSize);
     174             : 
     175             :     /*
     176             :      * Allocate the initial block.  Unlike other bump.c blocks, it starts with
     177             :      * the context header and its block header follows that.
     178             :      */
     179      507760 :     set = (BumpContext *) malloc(allocSize);
     180      507760 :     if (set == NULL)
     181             :     {
     182           0 :         MemoryContextStats(TopMemoryContext);
     183           0 :         ereport(ERROR,
     184             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
     185             :                  errmsg("out of memory"),
     186             :                  errdetail("Failed while creating memory context \"%s\".",
     187             :                            name)));
     188             :     }
     189             : 
     190             :     /*
     191             :      * Avoid writing code that can fail between here and MemoryContextCreate;
     192             :      * we'd leak the header and initial block if we ereport in this stretch.
     193             :      */
     194             : 
     195             :     /* See comments about Valgrind interactions in aset.c */
     196             :     VALGRIND_CREATE_MEMPOOL(set, 0, false);
     197             :     /* This vchunk covers the BumpContext and the keeper block header */
     198             :     VALGRIND_MEMPOOL_ALLOC(set, set, FIRST_BLOCKHDRSZ);
     199             : 
     200      507760 :     dlist_init(&set->blocks);
     201             : 
     202             :     /* Fill in the initial block's block header */
     203      507760 :     block = KeeperBlock(set);
     204             :     /* determine the block size and initialize it */
     205      507760 :     firstBlockSize = allocSize - MAXALIGN(sizeof(BumpContext));
     206      507760 :     BumpBlockInit(set, block, firstBlockSize);
     207             : 
     208             :     /* add it to the doubly-linked list of blocks */
     209      507760 :     dlist_push_head(&set->blocks, &block->node);
     210             : 
     211             :     /*
     212             :      * Fill in BumpContext-specific header fields.  The Asserts above should
     213             :      * ensure that these all fit inside a uint32.
     214             :      */
     215      507760 :     set->initBlockSize = (uint32) initBlockSize;
     216      507760 :     set->maxBlockSize = (uint32) maxBlockSize;
     217      507760 :     set->nextBlockSize = (uint32) initBlockSize;
     218             : 
     219             :     /*
     220             :      * Compute the allocation chunk size limit for this context.
     221             :      *
     222             :      * Limit the maximum size a non-dedicated chunk can be so that we can fit
     223             :      * at least Bump_CHUNK_FRACTION of chunks this big onto the maximum sized
     224             :      * block.  We must further limit this value so that it's no more than
     225             :      * MEMORYCHUNK_MAX_VALUE.  We're unable to have non-external chunks larger
     226             :      * than that value as we store the chunk size in the MemoryChunk 'value'
     227             :      * field in the call to MemoryChunkSetHdrMask().
     228             :      */
     229      507760 :     set->allocChunkLimit = Min(maxBlockSize, MEMORYCHUNK_MAX_VALUE);
     230      507760 :     while ((Size) (set->allocChunkLimit + Bump_CHUNKHDRSZ) >
     231     2538800 :            (Size) ((Size) (maxBlockSize - Bump_BLOCKHDRSZ) / Bump_CHUNK_FRACTION))
     232     2031040 :         set->allocChunkLimit >>= 1;
     233             : 
     234             :     /* Finally, do the type-independent part of context creation */
     235      507760 :     MemoryContextCreate((MemoryContext) set, T_BumpContext, MCTX_BUMP_ID,
     236             :                         parent, name);
     237             : 
     238      507760 :     ((MemoryContext) set)->mem_allocated = allocSize;
     239             : 
     240      507760 :     return (MemoryContext) set;
     241             : }
     242             : 
     243             : /*
     244             :  * BumpReset
     245             :  *      Frees all memory which is allocated in the given set.
     246             :  *
     247             :  * The code simply frees all the blocks in the context apart from the keeper
     248             :  * block.
     249             :  */
     250             : void
     251      549004 : BumpReset(MemoryContext context)
     252             : {
     253      549004 :     BumpContext *set = (BumpContext *) context;
     254             :     dlist_mutable_iter miter;
     255             : 
     256             :     Assert(BumpIsValid(set));
     257             : 
     258             : #ifdef MEMORY_CONTEXT_CHECKING
     259             :     /* Check for corruption and leaks before freeing */
     260             :     BumpCheck(context);
     261             : #endif
     262             : 
     263     1121306 :     dlist_foreach_modify(miter, &set->blocks)
     264             :     {
     265      572302 :         BumpBlock  *block = dlist_container(BumpBlock, node, miter.cur);
     266             : 
     267      572302 :         if (IsKeeperBlock(set, block))
     268      549004 :             BumpBlockMarkEmpty(block);
     269             :         else
     270       23298 :             BumpBlockFree(set, block);
     271             :     }
     272             : 
     273             :     /*
     274             :      * Instruct Valgrind to throw away all the vchunks associated with this
     275             :      * context, except for the one covering the BumpContext and keeper-block
     276             :      * header.  This gets rid of the vchunks for whatever user data is getting
     277             :      * discarded by the context reset.
     278             :      */
     279             :     VALGRIND_MEMPOOL_TRIM(set, set, FIRST_BLOCKHDRSZ);
     280             : 
     281             :     /* Reset block size allocation sequence, too */
     282      549004 :     set->nextBlockSize = set->initBlockSize;
     283             : 
     284             :     /* Ensure there is only 1 item in the dlist */
     285             :     Assert(!dlist_is_empty(&set->blocks));
     286             :     Assert(!dlist_has_next(&set->blocks, dlist_head_node(&set->blocks)));
     287      549004 : }
     288             : 
     289             : /*
     290             :  * BumpDelete
     291             :  *      Free all memory which is allocated in the given context.
     292             :  */
     293             : void
     294      507760 : BumpDelete(MemoryContext context)
     295             : {
     296             :     /* Reset to release all releasable BumpBlocks */
     297      507760 :     BumpReset(context);
     298             : 
     299             :     /* Destroy the vpool -- see notes in aset.c */
     300             :     VALGRIND_DESTROY_MEMPOOL(context);
     301             : 
     302             :     /* And free the context header and keeper block */
     303      507760 :     free(context);
     304      507760 : }
     305             : 
     306             : /*
     307             :  * Helper for BumpAlloc() that allocates an entire block for the chunk.
     308             :  *
     309             :  * BumpAlloc()'s comment explains why this is separate.
     310             :  */
     311             : pg_noinline
     312             : static void *
     313          18 : BumpAllocLarge(MemoryContext context, Size size, int flags)
     314             : {
     315          18 :     BumpContext *set = (BumpContext *) context;
     316             :     BumpBlock  *block;
     317             : #ifdef MEMORY_CONTEXT_CHECKING
     318             :     MemoryChunk *chunk;
     319             : #endif
     320             :     Size        chunk_size;
     321             :     Size        required_size;
     322             :     Size        blksize;
     323             : 
     324             :     /* validate 'size' is within the limits for the given 'flags' */
     325          18 :     MemoryContextCheckSize(context, size, flags);
     326             : 
     327             : #ifdef MEMORY_CONTEXT_CHECKING
     328             :     /* ensure there's always space for the sentinel byte */
     329             :     chunk_size = MAXALIGN(size + 1);
     330             : #else
     331          18 :     chunk_size = MAXALIGN(size);
     332             : #endif
     333             : 
     334          18 :     required_size = chunk_size + Bump_CHUNKHDRSZ;
     335          18 :     blksize = required_size + Bump_BLOCKHDRSZ;
     336             : 
     337          18 :     block = (BumpBlock *) malloc(blksize);
     338          18 :     if (block == NULL)
     339           0 :         return MemoryContextAllocationFailure(context, size, flags);
     340             : 
     341             :     /* Make a vchunk covering the new block's header */
     342             :     VALGRIND_MEMPOOL_ALLOC(set, block, Bump_BLOCKHDRSZ);
     343             : 
     344          18 :     context->mem_allocated += blksize;
     345             : 
     346             :     /* the block is completely full */
     347          18 :     block->freeptr = block->endptr = ((char *) block) + blksize;
     348             : 
     349             : #ifdef MEMORY_CONTEXT_CHECKING
     350             :     /* block with a single (used) chunk */
     351             :     block->context = set;
     352             : 
     353             :     chunk = (MemoryChunk *) (((char *) block) + Bump_BLOCKHDRSZ);
     354             : 
     355             :     /* mark the MemoryChunk as externally managed */
     356             :     MemoryChunkSetHdrMaskExternal(chunk, MCTX_BUMP_ID);
     357             : 
     358             :     chunk->requested_size = size;
     359             :     /* set mark to catch clobber of "unused" space */
     360             :     Assert(size < chunk_size);
     361             :     set_sentinel(MemoryChunkGetPointer(chunk), size);
     362             : #endif
     363             : #ifdef RANDOMIZE_ALLOCATED_MEMORY
     364             :     /* fill the allocated space with junk */
     365             :     randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
     366             : #endif
     367             : 
     368             :     /*
     369             :      * Add the block to the tail of allocated blocks list.  The current block
     370             :      * is left at the head of the list as it may still have space for
     371             :      * non-large allocations.
     372             :      */
     373          18 :     dlist_push_tail(&set->blocks, &block->node);
     374             : 
     375             : #ifdef MEMORY_CONTEXT_CHECKING
     376             :     /* Ensure any padding bytes are marked NOACCESS. */
     377             :     VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
     378             :                                chunk_size - size);
     379             : 
     380             :     /* Disallow access to the chunk header. */
     381             :     VALGRIND_MAKE_MEM_NOACCESS(chunk, Bump_CHUNKHDRSZ);
     382             : 
     383             :     return MemoryChunkGetPointer(chunk);
     384             : #else
     385          18 :     return (void *) (((char *) block) + Bump_BLOCKHDRSZ);
     386             : #endif
     387             : }
     388             : 
     389             : /*
     390             :  * Small helper for allocating a new chunk from a chunk, to avoid duplicating
     391             :  * the code between BumpAlloc() and BumpAllocFromNewBlock().
     392             :  */
     393             : static inline void *
     394    25043982 : BumpAllocChunkFromBlock(MemoryContext context, BumpBlock *block, Size size,
     395             :                         Size chunk_size)
     396             : {
     397             : #ifdef MEMORY_CONTEXT_CHECKING
     398             :     MemoryChunk *chunk;
     399             : #else
     400             :     void       *ptr;
     401             : #endif
     402             : 
     403             :     /* validate we've been given a block with enough free space */
     404             :     Assert(block != NULL);
     405             :     Assert((block->endptr - block->freeptr) >= Bump_CHUNKHDRSZ + chunk_size);
     406             : 
     407             : #ifdef MEMORY_CONTEXT_CHECKING
     408             :     chunk = (MemoryChunk *) block->freeptr;
     409             : #else
     410    25043982 :     ptr = (void *) block->freeptr;
     411             : #endif
     412             : 
     413             :     /* point the freeptr beyond this chunk */
     414    25043982 :     block->freeptr += (Bump_CHUNKHDRSZ + chunk_size);
     415             :     Assert(block->freeptr <= block->endptr);
     416             : 
     417             : #ifdef MEMORY_CONTEXT_CHECKING
     418             :     /* Prepare to initialize the chunk header. */
     419             :     VALGRIND_MAKE_MEM_UNDEFINED(chunk, Bump_CHUNKHDRSZ);
     420             : 
     421             :     MemoryChunkSetHdrMask(chunk, block, chunk_size, MCTX_BUMP_ID);
     422             :     chunk->requested_size = size;
     423             :     /* set mark to catch clobber of "unused" space */
     424             :     Assert(size < chunk_size);
     425             :     set_sentinel(MemoryChunkGetPointer(chunk), size);
     426             : 
     427             : #ifdef RANDOMIZE_ALLOCATED_MEMORY
     428             :     /* fill the allocated space with junk */
     429             :     randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
     430             : #endif
     431             : 
     432             :     /* Ensure any padding bytes are marked NOACCESS. */
     433             :     VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
     434             :                                chunk_size - size);
     435             : 
     436             :     /* Disallow access to the chunk header. */
     437             :     VALGRIND_MAKE_MEM_NOACCESS(chunk, Bump_CHUNKHDRSZ);
     438             : 
     439             :     return MemoryChunkGetPointer(chunk);
     440             : #else
     441    25043982 :     return ptr;
     442             : #endif                          /* MEMORY_CONTEXT_CHECKING */
     443             : }
     444             : 
     445             : /*
     446             :  * Helper for BumpAlloc() that allocates a new block and returns a chunk
     447             :  * allocated from it.
     448             :  *
     449             :  * BumpAlloc()'s comment explains why this is separate.
     450             :  */
     451             : pg_noinline
     452             : static void *
     453       23280 : BumpAllocFromNewBlock(MemoryContext context, Size size, int flags,
     454             :                       Size chunk_size)
     455             : {
     456       23280 :     BumpContext *set = (BumpContext *) context;
     457             :     BumpBlock  *block;
     458             :     Size        blksize;
     459             :     Size        required_size;
     460             : 
     461             :     /*
     462             :      * The first such block has size initBlockSize, and we double the space in
     463             :      * each succeeding block, but not more than maxBlockSize.
     464             :      */
     465       23280 :     blksize = set->nextBlockSize;
     466       23280 :     set->nextBlockSize <<= 1;
     467       23280 :     if (set->nextBlockSize > set->maxBlockSize)
     468         204 :         set->nextBlockSize = set->maxBlockSize;
     469             : 
     470             :     /* we'll need space for the chunk, chunk hdr and block hdr */
     471       23280 :     required_size = chunk_size + Bump_CHUNKHDRSZ + Bump_BLOCKHDRSZ;
     472             :     /* round the size up to the next power of 2 */
     473       23280 :     if (blksize < required_size)
     474           0 :         blksize = pg_nextpower2_size_t(required_size);
     475             : 
     476       23280 :     block = (BumpBlock *) malloc(blksize);
     477             : 
     478       23280 :     if (block == NULL)
     479           0 :         return MemoryContextAllocationFailure(context, size, flags);
     480             : 
     481             :     /* Make a vchunk covering the new block's header */
     482             :     VALGRIND_MEMPOOL_ALLOC(set, block, Bump_BLOCKHDRSZ);
     483             : 
     484       23280 :     context->mem_allocated += blksize;
     485             : 
     486             :     /* initialize the new block */
     487       23280 :     BumpBlockInit(set, block, blksize);
     488             : 
     489             :     /* add it to the doubly-linked list of blocks */
     490       23280 :     dlist_push_head(&set->blocks, &block->node);
     491             : 
     492       23280 :     return BumpAllocChunkFromBlock(context, block, size, chunk_size);
     493             : }
     494             : 
     495             : /*
     496             :  * BumpAlloc
     497             :  *      Returns a pointer to allocated memory of given size or raises an ERROR
     498             :  *      on allocation failure, or returns NULL when flags contains
     499             :  *      MCXT_ALLOC_NO_OOM.
     500             :  *
     501             :  * No request may exceed:
     502             :  *      MAXALIGN_DOWN(SIZE_MAX) - Bump_BLOCKHDRSZ - Bump_CHUNKHDRSZ
     503             :  * All callers use a much-lower limit.
     504             :  *
     505             :  *
     506             :  * Note: when using valgrind, it doesn't matter how the returned allocation
     507             :  * is marked, as mcxt.c will set it to UNDEFINED.
     508             :  * This function should only contain the most common code paths.  Everything
     509             :  * else should be in pg_noinline helper functions, thus avoiding the overhead
     510             :  * of creating a stack frame for the common cases.  Allocating memory is often
     511             :  * a bottleneck in many workloads, so avoiding stack frame setup is
     512             :  * worthwhile.  Helper functions should always directly return the newly
     513             :  * allocated memory so that we can just return that address directly as a tail
     514             :  * call.
     515             :  */
     516             : void *
     517    25044000 : BumpAlloc(MemoryContext context, Size size, int flags)
     518             : {
     519    25044000 :     BumpContext *set = (BumpContext *) context;
     520             :     BumpBlock  *block;
     521             :     Size        chunk_size;
     522             :     Size        required_size;
     523             : 
     524             :     Assert(BumpIsValid(set));
     525             : 
     526             : #ifdef MEMORY_CONTEXT_CHECKING
     527             :     /* ensure there's always space for the sentinel byte */
     528             :     chunk_size = MAXALIGN(size + 1);
     529             : #else
     530    25044000 :     chunk_size = MAXALIGN(size);
     531             : #endif
     532             : 
     533             :     /*
     534             :      * If requested size exceeds maximum for chunks we hand the request off to
     535             :      * BumpAllocLarge().
     536             :      */
     537    25044000 :     if (chunk_size > set->allocChunkLimit)
     538          18 :         return BumpAllocLarge(context, size, flags);
     539             : 
     540    25043982 :     required_size = chunk_size + Bump_CHUNKHDRSZ;
     541             : 
     542             :     /*
     543             :      * Not an oversized chunk.  We try to first make use of the latest block,
     544             :      * but if there's not enough space in it we must allocate a new block.
     545             :      */
     546    25043982 :     block = dlist_container(BumpBlock, node, dlist_head_node(&set->blocks));
     547             : 
     548    25043982 :     if (BumpBlockFreeBytes(block) < required_size)
     549       23280 :         return BumpAllocFromNewBlock(context, size, flags, chunk_size);
     550             : 
     551             :     /* The current block has space, so just allocate chunk there. */
     552    25020702 :     return BumpAllocChunkFromBlock(context, block, size, chunk_size);
     553             : }
     554             : 
     555             : /*
     556             :  * BumpBlockInit
     557             :  *      Initializes 'block' assuming 'blksize'.  Does not update the context's
     558             :  *      mem_allocated field.
     559             :  */
     560             : static inline void
     561      531040 : BumpBlockInit(BumpContext *context, BumpBlock *block, Size blksize)
     562             : {
     563             : #ifdef MEMORY_CONTEXT_CHECKING
     564             :     block->context = context;
     565             : #endif
     566      531040 :     block->freeptr = ((char *) block) + Bump_BLOCKHDRSZ;
     567      531040 :     block->endptr = ((char *) block) + blksize;
     568             : 
     569             :     /* Mark unallocated space NOACCESS. */
     570             :     VALGRIND_MAKE_MEM_NOACCESS(block->freeptr, blksize - Bump_BLOCKHDRSZ);
     571      531040 : }
     572             : 
     573             : /*
     574             :  * BumpBlockIsEmpty
     575             :  *      Returns true iff 'block' contains no chunks
     576             :  */
     577             : static inline bool
     578           0 : BumpBlockIsEmpty(BumpBlock *block)
     579             : {
     580             :     /* it's empty if the freeptr has not moved */
     581           0 :     return (block->freeptr == ((char *) block + Bump_BLOCKHDRSZ));
     582             : }
     583             : 
     584             : /*
     585             :  * BumpBlockMarkEmpty
     586             :  *      Set a block as empty.  Does not free the block.
     587             :  */
     588             : static inline void
     589      549004 : BumpBlockMarkEmpty(BumpBlock *block)
     590             : {
     591             : #if defined(USE_VALGRIND) || defined(CLOBBER_FREED_MEMORY)
     592             :     char       *datastart = ((char *) block) + Bump_BLOCKHDRSZ;
     593             : #endif
     594             : 
     595             : #ifdef CLOBBER_FREED_MEMORY
     596             :     wipe_mem(datastart, block->freeptr - datastart);
     597             : #else
     598             :     /* wipe_mem() would have done this */
     599             :     VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
     600             : #endif
     601             : 
     602             :     /* Reset the block, but don't return it to malloc */
     603      549004 :     block->freeptr = ((char *) block) + Bump_BLOCKHDRSZ;
     604      549004 : }
     605             : 
     606             : /*
     607             :  * BumpBlockFreeBytes
     608             :  *      Returns the number of bytes free in 'block'
     609             :  */
     610             : static inline Size
     611    25043982 : BumpBlockFreeBytes(BumpBlock *block)
     612             : {
     613    25043982 :     return (block->endptr - block->freeptr);
     614             : }
     615             : 
     616             : /*
     617             :  * BumpBlockFree
     618             :  *      Remove 'block' from 'set' and release the memory consumed by it.
     619             :  */
     620             : static inline void
     621       23298 : BumpBlockFree(BumpContext *set, BumpBlock *block)
     622             : {
     623             :     /* Make sure nobody tries to free the keeper block */
     624             :     Assert(!IsKeeperBlock(set, block));
     625             : 
     626             :     /* release the block from the list of blocks */
     627       23298 :     dlist_delete(&block->node);
     628             : 
     629       23298 :     ((MemoryContext) set)->mem_allocated -= ((char *) block->endptr - (char *) block);
     630             : 
     631             : #ifdef CLOBBER_FREED_MEMORY
     632             :     wipe_mem(block, ((char *) block->endptr - (char *) block));
     633             : #endif
     634             : 
     635             :     /* As in aset.c, free block-header vchunks explicitly */
     636             :     VALGRIND_MEMPOOL_FREE(set, block);
     637             : 
     638       23298 :     free(block);
     639       23298 : }
     640             : 
     641             : /*
     642             :  * BumpFree
     643             :  *      Unsupported.
     644             :  */
     645             : void
     646           0 : BumpFree(void *pointer)
     647             : {
     648           0 :     elog(ERROR, "%s is not supported by the bump memory allocator", "pfree");
     649             : }
     650             : 
     651             : /*
     652             :  * BumpRealloc
     653             :  *      Unsupported.
     654             :  */
     655             : void *
     656           0 : BumpRealloc(void *pointer, Size size, int flags)
     657             : {
     658           0 :     elog(ERROR, "%s is not supported by the bump memory allocator", "realloc");
     659             :     return NULL;                /* keep compiler quiet */
     660             : }
     661             : 
     662             : /*
     663             :  * BumpGetChunkContext
     664             :  *      Unsupported.
     665             :  */
     666             : MemoryContext
     667           0 : BumpGetChunkContext(void *pointer)
     668             : {
     669           0 :     elog(ERROR, "%s is not supported by the bump memory allocator", "GetMemoryChunkContext");
     670             :     return NULL;                /* keep compiler quiet */
     671             : }
     672             : 
     673             : /*
     674             :  * BumpGetChunkSpace
     675             :  *      Unsupported.
     676             :  */
     677             : Size
     678           0 : BumpGetChunkSpace(void *pointer)
     679             : {
     680           0 :     elog(ERROR, "%s is not supported by the bump memory allocator", "GetMemoryChunkSpace");
     681             :     return 0;                   /* keep compiler quiet */
     682             : }
     683             : 
     684             : /*
     685             :  * BumpIsEmpty
     686             :  *      Is a BumpContext empty of any allocated space?
     687             :  */
     688             : bool
     689           0 : BumpIsEmpty(MemoryContext context)
     690             : {
     691           0 :     BumpContext *set = (BumpContext *) context;
     692             :     dlist_iter  iter;
     693             : 
     694             :     Assert(BumpIsValid(set));
     695             : 
     696           0 :     dlist_foreach(iter, &set->blocks)
     697             :     {
     698           0 :         BumpBlock  *block = dlist_container(BumpBlock, node, iter.cur);
     699             : 
     700           0 :         if (!BumpBlockIsEmpty(block))
     701           0 :             return false;
     702             :     }
     703             : 
     704           0 :     return true;
     705             : }
     706             : 
     707             : /*
     708             :  * BumpStats
     709             :  *      Compute stats about memory consumption of a Bump context.
     710             :  *
     711             :  * printfunc: if not NULL, pass a human-readable stats string to this.
     712             :  * passthru: pass this pointer through to printfunc.
     713             :  * totals: if not NULL, add stats about this context into *totals.
     714             :  * print_to_stderr: print stats to stderr if true, elog otherwise.
     715             :  */
     716             : void
     717           6 : BumpStats(MemoryContext context, MemoryStatsPrintFunc printfunc,
     718             :           void *passthru, MemoryContextCounters *totals, bool print_to_stderr)
     719             : {
     720           6 :     BumpContext *set = (BumpContext *) context;
     721           6 :     Size        nblocks = 0;
     722           6 :     Size        totalspace = 0;
     723           6 :     Size        freespace = 0;
     724             :     dlist_iter  iter;
     725             : 
     726             :     Assert(BumpIsValid(set));
     727             : 
     728          18 :     dlist_foreach(iter, &set->blocks)
     729             :     {
     730          12 :         BumpBlock  *block = dlist_container(BumpBlock, node, iter.cur);
     731             : 
     732          12 :         nblocks++;
     733          12 :         totalspace += (block->endptr - (char *) block);
     734          12 :         freespace += (block->endptr - block->freeptr);
     735             :     }
     736             : 
     737           6 :     if (printfunc)
     738             :     {
     739             :         char        stats_string[200];
     740             : 
     741           0 :         snprintf(stats_string, sizeof(stats_string),
     742             :                  "%zu total in %zu blocks; %zu free; %zu used",
     743             :                  totalspace, nblocks, freespace, totalspace - freespace);
     744           0 :         printfunc(context, passthru, stats_string, print_to_stderr);
     745             :     }
     746             : 
     747           6 :     if (totals)
     748             :     {
     749           6 :         totals->nblocks += nblocks;
     750           6 :         totals->totalspace += totalspace;
     751           6 :         totals->freespace += freespace;
     752             :     }
     753           6 : }
     754             : 
     755             : 
     756             : #ifdef MEMORY_CONTEXT_CHECKING
     757             : 
     758             : /*
     759             :  * BumpCheck
     760             :  *      Walk through chunks and check consistency of memory.
     761             :  *
     762             :  * NOTE: report errors as WARNING, *not* ERROR or FATAL.  Otherwise you'll
     763             :  * find yourself in an infinite loop when trouble occurs, because this
     764             :  * routine will be entered again when elog cleanup tries to release memory!
     765             :  */
     766             : void
     767             : BumpCheck(MemoryContext context)
     768             : {
     769             :     BumpContext *bump = (BumpContext *) context;
     770             :     const char *name = context->name;
     771             :     dlist_iter  iter;
     772             :     Size        total_allocated = 0;
     773             : 
     774             :     /* walk all blocks in this context */
     775             :     dlist_foreach(iter, &bump->blocks)
     776             :     {
     777             :         BumpBlock  *block = dlist_container(BumpBlock, node, iter.cur);
     778             :         int         nchunks;
     779             :         char       *ptr;
     780             :         bool        has_external_chunk = false;
     781             : 
     782             :         if (IsKeeperBlock(bump, block))
     783             :             total_allocated += block->endptr - (char *) bump;
     784             :         else
     785             :             total_allocated += block->endptr - (char *) block;
     786             : 
     787             :         /* check block belongs to the correct context */
     788             :         if (block->context != bump)
     789             :             elog(WARNING, "problem in Bump %s: bogus context link in block %p",
     790             :                  name, block);
     791             : 
     792             :         /* now walk through the chunks and count them */
     793             :         nchunks = 0;
     794             :         ptr = ((char *) block) + Bump_BLOCKHDRSZ;
     795             : 
     796             :         while (ptr < block->freeptr)
     797             :         {
     798             :             MemoryChunk *chunk = (MemoryChunk *) ptr;
     799             :             BumpBlock  *chunkblock;
     800             :             Size        chunksize;
     801             : 
     802             :             /* allow access to the chunk header */
     803             :             VALGRIND_MAKE_MEM_DEFINED(chunk, Bump_CHUNKHDRSZ);
     804             : 
     805             :             if (MemoryChunkIsExternal(chunk))
     806             :             {
     807             :                 chunkblock = ExternalChunkGetBlock(chunk);
     808             :                 chunksize = block->endptr - (char *) MemoryChunkGetPointer(chunk);
     809             :                 has_external_chunk = true;
     810             :             }
     811             :             else
     812             :             {
     813             :                 chunkblock = MemoryChunkGetBlock(chunk);
     814             :                 chunksize = MemoryChunkGetValue(chunk);
     815             :             }
     816             : 
     817             :             /* move to the next chunk */
     818             :             ptr += (chunksize + Bump_CHUNKHDRSZ);
     819             : 
     820             :             nchunks += 1;
     821             : 
     822             :             /* chunks have both block and context pointers, so check both */
     823             :             if (chunkblock != block)
     824             :                 elog(WARNING, "problem in Bump %s: bogus block link in block %p, chunk %p",
     825             :                      name, block, chunk);
     826             :         }
     827             : 
     828             :         if (has_external_chunk && nchunks > 1)
     829             :             elog(WARNING, "problem in Bump %s: external chunk on non-dedicated block %p",
     830             :                  name, block);
     831             : 
     832             :     }
     833             : 
     834             :     Assert(total_allocated == context->mem_allocated);
     835             : }
     836             : 
     837             : #endif                          /* MEMORY_CONTEXT_CHECKING */

Generated by: LCOV version 1.16