LCOV - code coverage report
Current view: top level - src/backend/utils/mmgr - slab.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 69.6 % 365 254
Test Date: 2026-01-26 10:56:24 Functions: 64.7 % 17 11
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 38.2 % 249 95

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * slab.c
       4                 :             :  *        SLAB allocator definitions.
       5                 :             :  *
       6                 :             :  * SLAB is a MemoryContext implementation designed for cases where large
       7                 :             :  * numbers of equally-sized objects can be allocated and freed efficiently
       8                 :             :  * with minimal memory wastage and fragmentation.
       9                 :             :  *
      10                 :             :  *
      11                 :             :  * Portions Copyright (c) 2017-2026, PostgreSQL Global Development Group
      12                 :             :  *
      13                 :             :  * IDENTIFICATION
      14                 :             :  *        src/backend/utils/mmgr/slab.c
      15                 :             :  *
      16                 :             :  *
      17                 :             :  * NOTE:
      18                 :             :  *      The constant allocation size allows significant simplification and various
      19                 :             :  *      optimizations over more general purpose allocators. The blocks are carved
      20                 :             :  *      into chunks of exactly the right size, wasting only the space required to
      21                 :             :  *      MAXALIGN the allocated chunks.
      22                 :             :  *
      23                 :             :  *      Slab can also help reduce memory fragmentation in cases where longer-lived
      24                 :             :  *      chunks remain stored on blocks while most of the other chunks have already
      25                 :             :  *      been pfree'd.  We give priority to putting new allocations into the
      26                 :             :  *      "fullest" block.  This help avoid having too many sparsely used blocks
      27                 :             :  *      around and allows blocks to more easily become completely unused which
      28                 :             :  *      allows them to be eventually free'd.
      29                 :             :  *
      30                 :             :  *      We identify the "fullest" block to put new allocations on by using a block
      31                 :             :  *      from the lowest populated element of the context's "blocklist" array.
      32                 :             :  *      This is an array of dlists containing blocks which we partition by the
      33                 :             :  *      number of free chunks which block has.  Blocks with fewer free chunks are
      34                 :             :  *      stored in a lower indexed dlist array slot.  Full blocks go on the 0th
      35                 :             :  *      element of the blocklist array.  So that we don't have to have too many
      36                 :             :  *      elements in the array, each dlist in the array is responsible for a range
      37                 :             :  *      of free chunks.  When a chunk is palloc'd or pfree'd we may need to move
      38                 :             :  *      the block onto another dlist if the number of free chunks crosses the
      39                 :             :  *      range boundary that the current list is responsible for.  Having just a
      40                 :             :  *      few blocklist elements reduces the number of times we must move the block
      41                 :             :  *      onto another dlist element.
      42                 :             :  *
      43                 :             :  *      We keep track of free chunks within each block by using a block-level free
      44                 :             :  *      list.  We consult this list when we allocate a new chunk in the block.
      45                 :             :  *      The free list is a linked list, the head of which is pointed to with
      46                 :             :  *      SlabBlock's freehead field.  Each subsequent list item is stored in the
      47                 :             :  *      free chunk's memory.  We ensure chunks are large enough to store this
      48                 :             :  *      address.
      49                 :             :  *
      50                 :             :  *      When we allocate a new block, technically all chunks are free, however, to
      51                 :             :  *      avoid having to write out the entire block to set the linked list for the
      52                 :             :  *      free chunks for every chunk in the block, we instead store a pointer to
      53                 :             :  *      the next "unused" chunk on the block and keep track of how many of these
      54                 :             :  *      unused chunks there are.  When a new block is malloc'd, all chunks are
      55                 :             :  *      unused.  The unused pointer starts with the first chunk on the block and
      56                 :             :  *      as chunks are allocated, the unused pointer is incremented.  As chunks are
      57                 :             :  *      pfree'd, the unused pointer never goes backwards.  The unused pointer can
      58                 :             :  *      be thought of as a high watermark for the maximum number of chunks in the
      59                 :             :  *      block which have been in use concurrently.  When a chunk is pfree'd the
      60                 :             :  *      chunk is put onto the head of the free list and the unused pointer is not
      61                 :             :  *      changed.  We only consume more unused chunks if we run out of free chunks
      62                 :             :  *      on the free list.  This method effectively gives priority to using
      63                 :             :  *      previously used chunks over previously unused chunks, which should perform
      64                 :             :  *      better due to CPU caching effects.
      65                 :             :  *
      66                 :             :  *-------------------------------------------------------------------------
      67                 :             :  */
      68                 :             : 
      69                 :             : #include "postgres.h"
      70                 :             : 
      71                 :             : #include "lib/ilist.h"
      72                 :             : #include "utils/memdebug.h"
      73                 :             : #include "utils/memutils.h"
      74                 :             : #include "utils/memutils_internal.h"
      75                 :             : #include "utils/memutils_memorychunk.h"
      76                 :             : 
      77                 :             : #define Slab_BLOCKHDRSZ MAXALIGN(sizeof(SlabBlock))
      78                 :             : 
      79                 :             : #ifdef MEMORY_CONTEXT_CHECKING
      80                 :             : /*
      81                 :             :  * Size of the memory required to store the SlabContext.
      82                 :             :  * MEMORY_CONTEXT_CHECKING builds need some extra memory for the isChunkFree
      83                 :             :  * array.
      84                 :             :  */
      85                 :             : #define Slab_CONTEXT_HDRSZ(chunksPerBlock)      \
      86                 :             :         (sizeof(SlabContext) + ((chunksPerBlock) * sizeof(bool)))
      87                 :             : #else
      88                 :             : #define Slab_CONTEXT_HDRSZ(chunksPerBlock)      sizeof(SlabContext)
      89                 :             : #endif
      90                 :             : 
      91                 :             : /*
      92                 :             :  * The number of partitions to divide the blocklist into based their number of
      93                 :             :  * free chunks.  There must be at least 2.
      94                 :             :  */
      95                 :             : #define SLAB_BLOCKLIST_COUNT 3
      96                 :             : 
      97                 :             : /* The maximum number of completely empty blocks to keep around for reuse. */
      98                 :             : #define SLAB_MAXIMUM_EMPTY_BLOCKS 10
      99                 :             : 
     100                 :             : /*
     101                 :             :  * SlabContext is a specialized implementation of MemoryContext.
     102                 :             :  */
     103                 :             : typedef struct SlabContext
     104                 :             : {
     105                 :             :         MemoryContextData header;       /* Standard memory-context fields */
     106                 :             :         /* Allocation parameters for this context: */
     107                 :             :         uint32          chunkSize;              /* the requested (non-aligned) chunk size */
     108                 :             :         uint32          fullChunkSize;  /* chunk size with chunk header and alignment */
     109                 :             :         uint32          blockSize;              /* the size to make each block of chunks */
     110                 :             :         int32           chunksPerBlock; /* number of chunks that fit in 1 block */
     111                 :             :         int32           curBlocklistIndex;      /* index into the blocklist[] element
     112                 :             :                                                                          * containing the fullest, blocks */
     113                 :             : #ifdef MEMORY_CONTEXT_CHECKING
     114                 :             :         bool       *isChunkFree;        /* array to mark free chunks in a block during
     115                 :             :                                                                  * SlabCheck */
     116                 :             : #endif
     117                 :             : 
     118                 :             :         int32           blocklist_shift;        /* number of bits to shift the nfree count
     119                 :             :                                                                          * by to get the index into blocklist[] */
     120                 :             :         dclist_head emptyblocks;        /* empty blocks to use up first instead of
     121                 :             :                                                                  * mallocing new blocks */
     122                 :             : 
     123                 :             :         /*
     124                 :             :          * Blocks with free space, grouped by the number of free chunks they
     125                 :             :          * contain.  Completely full blocks are stored in the 0th element.
     126                 :             :          * Completely empty blocks are stored in emptyblocks or free'd if we have
     127                 :             :          * enough empty blocks already.
     128                 :             :          */
     129                 :             :         dlist_head      blocklist[SLAB_BLOCKLIST_COUNT];
     130                 :             : } SlabContext;
     131                 :             : 
     132                 :             : /*
     133                 :             :  * SlabBlock
     134                 :             :  *              Structure of a single slab block.
     135                 :             :  *
     136                 :             :  * slab: pointer back to the owning MemoryContext
     137                 :             :  * nfree: number of chunks on the block which are unallocated
     138                 :             :  * nunused: number of chunks on the block unallocated and not on the block's
     139                 :             :  * freelist.
     140                 :             :  * freehead: linked-list header storing a pointer to the first free chunk on
     141                 :             :  * the block.  Subsequent pointers are stored in the chunk's memory.  NULL
     142                 :             :  * indicates the end of the list.
     143                 :             :  * unused: pointer to the next chunk which has yet to be used.
     144                 :             :  * node: doubly-linked list node for the context's blocklist
     145                 :             :  */
     146                 :             : typedef struct SlabBlock
     147                 :             : {
     148                 :             :         SlabContext *slab;                      /* owning context */
     149                 :             :         int32           nfree;                  /* number of chunks on free + unused chunks */
     150                 :             :         int32           nunused;                /* number of unused chunks */
     151                 :             :         MemoryChunk *freehead;          /* pointer to the first free chunk */
     152                 :             :         MemoryChunk *unused;            /* pointer to the next unused chunk */
     153                 :             :         dlist_node      node;                   /* doubly-linked list for blocklist[] */
     154                 :             : } SlabBlock;
     155                 :             : 
     156                 :             : 
     157                 :             : #define Slab_CHUNKHDRSZ sizeof(MemoryChunk)
     158                 :             : #define SlabChunkGetPointer(chk)        \
     159                 :             :         ((void *) (((char *) (chk)) + sizeof(MemoryChunk)))
     160                 :             : 
     161                 :             : /*
     162                 :             :  * SlabBlockGetChunk
     163                 :             :  *              Obtain a pointer to the nth (0-based) chunk in the block
     164                 :             :  */
     165                 :             : #define SlabBlockGetChunk(slab, block, n) \
     166                 :             :         ((MemoryChunk *) ((char *) (block) + Slab_BLOCKHDRSZ    \
     167                 :             :                                         + ((n) * (slab)->fullChunkSize)))
     168                 :             : 
     169                 :             : #if defined(MEMORY_CONTEXT_CHECKING) || defined(USE_ASSERT_CHECKING)
     170                 :             : 
     171                 :             : /*
     172                 :             :  * SlabChunkIndex
     173                 :             :  *              Get the 0-based index of how many chunks into the block the given
     174                 :             :  *              chunk is.
     175                 :             : */
     176                 :             : #define SlabChunkIndex(slab, block, chunk)      \
     177                 :             :         (((char *) (chunk) - (char *) SlabBlockGetChunk(slab, block, 0)) / \
     178                 :             :         (slab)->fullChunkSize)
     179                 :             : 
     180                 :             : /*
     181                 :             :  * SlabChunkMod
     182                 :             :  *              A MemoryChunk should always be at an address which is a multiple of
     183                 :             :  *              fullChunkSize starting from the 0th chunk position.  This will return
     184                 :             :  *              non-zero if it's not.
     185                 :             :  */
     186                 :             : #define SlabChunkMod(slab, block, chunk)        \
     187                 :             :         (((char *) (chunk) - (char *) SlabBlockGetChunk(slab, block, 0)) % \
     188                 :             :         (slab)->fullChunkSize)
     189                 :             : 
     190                 :             : #endif
     191                 :             : 
     192                 :             : /*
     193                 :             :  * SlabIsValid
     194                 :             :  *              True iff set is a valid slab allocation set.
     195                 :             :  */
     196                 :             : #define SlabIsValid(set) ((set) && IsA(set, SlabContext))
     197                 :             : 
     198                 :             : /*
     199                 :             :  * SlabBlockIsValid
     200                 :             :  *              True iff block is a valid block of slab allocation set.
     201                 :             :  */
     202                 :             : #define SlabBlockIsValid(block) \
     203                 :             :         ((block) && SlabIsValid((block)->slab))
     204                 :             : 
     205                 :             : /*
     206                 :             :  * SlabBlocklistIndex
     207                 :             :  *              Determine the blocklist index that a block should be in for the given
     208                 :             :  *              number of free chunks.
     209                 :             :  */
     210                 :             : static inline int32
     211                 :        2266 : SlabBlocklistIndex(SlabContext *slab, int nfree)
     212                 :             : {
     213                 :        2266 :         int32           index;
     214                 :        2266 :         int32           blocklist_shift = slab->blocklist_shift;
     215                 :             : 
     216         [ +  - ]:        2266 :         Assert(nfree >= 0 && nfree <= slab->chunksPerBlock);
     217                 :             : 
     218                 :             :         /*
     219                 :             :          * Determine the blocklist index based on the number of free chunks.  We
     220                 :             :          * must ensure that 0 free chunks is dedicated to index 0.  Everything
     221                 :             :          * else must be >= 1 and < SLAB_BLOCKLIST_COUNT.
     222                 :             :          *
     223                 :             :          * To make this as efficient as possible, we exploit some two's complement
     224                 :             :          * arithmetic where we reverse the sign before bit shifting.  This results
     225                 :             :          * in an nfree of 0 using index 0 and anything non-zero staying non-zero.
     226                 :             :          * This is exploiting 0 and -0 being the same in two's complement.  When
     227                 :             :          * we're done, we just need to flip the sign back over again for a
     228                 :             :          * positive index.
     229                 :             :          */
     230                 :        2266 :         index = -((-nfree) >> blocklist_shift);
     231                 :             : 
     232         [ +  - ]:        2266 :         if (nfree == 0)
     233         [ #  # ]:           0 :                 Assert(index == 0);
     234                 :             :         else
     235         [ +  - ]:        2266 :                 Assert(index >= 1 && index < SLAB_BLOCKLIST_COUNT);
     236                 :             : 
     237                 :        4532 :         return index;
     238                 :        2266 : }
     239                 :             : 
     240                 :             : /*
     241                 :             :  * SlabFindNextBlockListIndex
     242                 :             :  *              Search blocklist for blocks which have free chunks and return the
     243                 :             :  *              index of the blocklist found containing at least 1 block with free
     244                 :             :  *              chunks.  If no block can be found we return 0.
     245                 :             :  *
     246                 :             :  * Note: We give priority to fuller blocks so that these are filled before
     247                 :             :  * emptier blocks.  This is done to increase the chances that mostly-empty
     248                 :             :  * blocks will eventually become completely empty so they can be free'd.
     249                 :             :  */
     250                 :             : static int32
     251                 :          41 : SlabFindNextBlockListIndex(SlabContext *slab)
     252                 :             : {
     253                 :             :         /* start at 1 as blocklist[0] is for full blocks. */
     254   [ +  +  -  -  :         123 :         for (int i = 1; i < SLAB_BLOCKLIST_COUNT; i++)
                      + ]
     255                 :             :         {
     256                 :             :                 /* return the first found non-empty index */
     257         [ -  + ]:          82 :                 if (!dlist_is_empty(&slab->blocklist[i]))
     258                 :           0 :                         return i;
     259                 :          82 :         }
     260                 :             : 
     261                 :             :         /* no blocks with free space */
     262                 :          41 :         return 0;
     263                 :          41 : }
     264                 :             : 
     265                 :             : /*
     266                 :             :  * SlabGetNextFreeChunk
     267                 :             :  *              Return the next free chunk in block and update the block to account
     268                 :             :  *              for the returned chunk now being used.
     269                 :             :  */
     270                 :             : static inline MemoryChunk *
     271                 :           5 : SlabGetNextFreeChunk(SlabContext *slab, SlabBlock *block)
     272                 :             : {
     273                 :           5 :         MemoryChunk *chunk;
     274                 :             : 
     275         [ +  - ]:           5 :         Assert(block->nfree > 0);
     276                 :             : 
     277         [ +  + ]:           5 :         if (block->freehead != NULL)
     278                 :             :         {
     279                 :           4 :                 chunk = block->freehead;
     280                 :             : 
     281                 :             :                 /*
     282                 :             :                  * Pop the chunk from the linked list of free chunks.  The pointer to
     283                 :             :                  * the next free chunk is stored in the chunk itself.
     284                 :             :                  */
     285                 :           4 :                 VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(chunk), sizeof(MemoryChunk *));
     286                 :           4 :                 block->freehead = *(MemoryChunk **) SlabChunkGetPointer(chunk);
     287                 :             : 
     288                 :             :                 /* check nothing stomped on the free chunk's memory */
     289   [ +  -  #  # ]:           4 :                 Assert(block->freehead == NULL ||
     290                 :             :                            (block->freehead >= SlabBlockGetChunk(slab, block, 0) &&
     291                 :             :                                 block->freehead <= SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1) &&
     292                 :             :                                 SlabChunkMod(slab, block, block->freehead) == 0));
     293                 :           4 :         }
     294                 :             :         else
     295                 :             :         {
     296         [ +  - ]:           1 :                 Assert(block->nunused > 0);
     297                 :             : 
     298                 :           1 :                 chunk = block->unused;
     299                 :           1 :                 block->unused = (MemoryChunk *) (((char *) block->unused) + slab->fullChunkSize);
     300                 :           1 :                 block->nunused--;
     301                 :             :         }
     302                 :             : 
     303                 :           5 :         block->nfree--;
     304                 :             : 
     305                 :          10 :         return chunk;
     306                 :           5 : }
     307                 :             : 
     308                 :             : /*
     309                 :             :  * SlabContextCreate
     310                 :             :  *              Create a new Slab context.
     311                 :             :  *
     312                 :             :  * parent: parent context, or NULL if top-level context
     313                 :             :  * name: name of context (must be statically allocated)
     314                 :             :  * blockSize: allocation block size
     315                 :             :  * chunkSize: allocation chunk size
     316                 :             :  *
     317                 :             :  * The Slab_CHUNKHDRSZ + MAXALIGN(chunkSize + 1) may not exceed
     318                 :             :  * MEMORYCHUNK_MAX_VALUE.
     319                 :             :  * 'blockSize' may not exceed MEMORYCHUNK_MAX_BLOCKOFFSET.
     320                 :             :  */
     321                 :             : MemoryContext
     322                 :        3485 : SlabContextCreate(MemoryContext parent,
     323                 :             :                                   const char *name,
     324                 :             :                                   Size blockSize,
     325                 :             :                                   Size chunkSize)
     326                 :             : {
     327                 :        3485 :         int                     chunksPerBlock;
     328                 :        3485 :         Size            fullChunkSize;
     329                 :        3485 :         SlabContext *slab;
     330                 :        3485 :         int                     i;
     331                 :             : 
     332                 :             :         /* ensure MemoryChunk's size is properly maxaligned */
     333                 :             :         StaticAssertDecl(Slab_CHUNKHDRSZ == MAXALIGN(Slab_CHUNKHDRSZ),
     334                 :             :                                          "sizeof(MemoryChunk) is not maxaligned");
     335         [ +  - ]:        3485 :         Assert(blockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET);
     336                 :             : 
     337                 :             :         /*
     338                 :             :          * Ensure there's enough space to store the pointer to the next free chunk
     339                 :             :          * in the memory of the (otherwise) unused allocation.
     340                 :             :          */
     341         [ +  - ]:        3485 :         if (chunkSize < sizeof(MemoryChunk *))
     342                 :           0 :                 chunkSize = sizeof(MemoryChunk *);
     343                 :             : 
     344                 :             :         /* length of the maxaligned chunk including the chunk header  */
     345                 :             : #ifdef MEMORY_CONTEXT_CHECKING
     346                 :             :         /* ensure there's always space for the sentinel byte */
     347                 :        3485 :         fullChunkSize = Slab_CHUNKHDRSZ + MAXALIGN(chunkSize + 1);
     348                 :             : #else
     349                 :             :         fullChunkSize = Slab_CHUNKHDRSZ + MAXALIGN(chunkSize);
     350                 :             : #endif
     351                 :             : 
     352         [ +  - ]:        3485 :         Assert(fullChunkSize <= MEMORYCHUNK_MAX_VALUE);
     353                 :             : 
     354                 :             :         /* compute the number of chunks that will fit on each block */
     355                 :        3485 :         chunksPerBlock = (blockSize - Slab_BLOCKHDRSZ) / fullChunkSize;
     356                 :             : 
     357                 :             :         /* Make sure the block can store at least one chunk. */
     358         [ +  - ]:        3485 :         if (chunksPerBlock == 0)
     359   [ #  #  #  # ]:           0 :                 elog(ERROR, "block size %zu for slab is too small for %zu-byte chunks",
     360                 :             :                          blockSize, chunkSize);
     361                 :             : 
     362                 :             : 
     363                 :             : 
     364                 :        3485 :         slab = (SlabContext *) malloc(Slab_CONTEXT_HDRSZ(chunksPerBlock));
     365         [ +  - ]:        3485 :         if (slab == NULL)
     366                 :             :         {
     367                 :           0 :                 MemoryContextStats(TopMemoryContext);
     368   [ #  #  #  # ]:           0 :                 ereport(ERROR,
     369                 :             :                                 (errcode(ERRCODE_OUT_OF_MEMORY),
     370                 :             :                                  errmsg("out of memory"),
     371                 :             :                                  errdetail("Failed while creating memory context \"%s\".",
     372                 :             :                                                    name)));
     373                 :           0 :         }
     374                 :             : 
     375                 :             :         /*
     376                 :             :          * Avoid writing code that can fail between here and MemoryContextCreate;
     377                 :             :          * we'd leak the header if we ereport in this stretch.
     378                 :             :          */
     379                 :             : 
     380                 :             :         /* See comments about Valgrind interactions in aset.c */
     381                 :        3485 :         VALGRIND_CREATE_MEMPOOL(slab, 0, false);
     382                 :             :         /* This vchunk covers the SlabContext only */
     383                 :        3485 :         VALGRIND_MEMPOOL_ALLOC(slab, slab, sizeof(SlabContext));
     384                 :             : 
     385                 :             :         /* Fill in SlabContext-specific header fields */
     386                 :        3485 :         slab->chunkSize = (uint32) chunkSize;
     387                 :        3485 :         slab->fullChunkSize = (uint32) fullChunkSize;
     388                 :        3485 :         slab->blockSize = (uint32) blockSize;
     389                 :        3485 :         slab->chunksPerBlock = chunksPerBlock;
     390                 :        3485 :         slab->curBlocklistIndex = 0;
     391                 :             : 
     392                 :             :         /*
     393                 :             :          * Compute a shift that guarantees that shifting chunksPerBlock with it is
     394                 :             :          * < SLAB_BLOCKLIST_COUNT - 1.  The reason that we subtract 1 from
     395                 :             :          * SLAB_BLOCKLIST_COUNT in this calculation is that we reserve the 0th
     396                 :             :          * blocklist element for blocks which have no free chunks.
     397                 :             :          *
     398                 :             :          * We calculate the number of bits to shift by rather than a divisor to
     399                 :             :          * divide by as performing division each time we need to find the
     400                 :             :          * blocklist index would be much slower.
     401                 :             :          */
     402                 :        3485 :         slab->blocklist_shift = 0;
     403         [ +  + ]:       21607 :         while ((slab->chunksPerBlock >> slab->blocklist_shift) >= (SLAB_BLOCKLIST_COUNT - 1))
     404                 :       18122 :                 slab->blocklist_shift++;
     405                 :             : 
     406                 :             :         /* initialize the list to store empty blocks to be reused */
     407                 :        3485 :         dclist_init(&slab->emptyblocks);
     408                 :             : 
     409                 :             :         /* initialize each blocklist slot */
     410         [ +  + ]:       13940 :         for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
     411                 :       10455 :                 dlist_init(&slab->blocklist[i]);
     412                 :             : 
     413                 :             : #ifdef MEMORY_CONTEXT_CHECKING
     414                 :             :         /* set the isChunkFree pointer right after the end of the context */
     415                 :        3485 :         slab->isChunkFree = (bool *) ((char *) slab + sizeof(SlabContext));
     416                 :             : #endif
     417                 :             : 
     418                 :             :         /* Finally, do the type-independent part of context creation */
     419                 :        6970 :         MemoryContextCreate((MemoryContext) slab,
     420                 :             :                                                 T_SlabContext,
     421                 :             :                                                 MCTX_SLAB_ID,
     422                 :        3485 :                                                 parent,
     423                 :        3485 :                                                 name);
     424                 :             : 
     425                 :        6970 :         return (MemoryContext) slab;
     426                 :        3485 : }
     427                 :             : 
     428                 :             : /*
     429                 :             :  * SlabReset
     430                 :             :  *              Frees all memory which is allocated in the given set.
     431                 :             :  *
     432                 :             :  * The code simply frees all the blocks in the context - we don't keep any
     433                 :             :  * keeper blocks or anything like that.
     434                 :             :  */
     435                 :             : void
     436                 :        3485 : SlabReset(MemoryContext context)
     437                 :             : {
     438                 :        3485 :         SlabContext *slab = (SlabContext *) context;
     439                 :        3485 :         dlist_mutable_iter miter;
     440                 :        3485 :         int                     i;
     441                 :             : 
     442         [ +  - ]:        3485 :         Assert(SlabIsValid(slab));
     443                 :             : 
     444                 :             : #ifdef MEMORY_CONTEXT_CHECKING
     445                 :             :         /* Check for corruption and leaks before freeing */
     446                 :        3485 :         SlabCheck(context);
     447                 :             : #endif
     448                 :             : 
     449                 :             :         /* release any retained empty blocks */
     450   [ +  -  +  + ]:        3522 :         dclist_foreach_modify(miter, &slab->emptyblocks)
     451                 :             :         {
     452                 :          37 :                 SlabBlock  *block = dlist_container(SlabBlock, node, miter.cur);
     453                 :             : 
     454                 :          37 :                 dclist_delete_from(&slab->emptyblocks, miter.cur);
     455                 :             : 
     456                 :             : #ifdef CLOBBER_FREED_MEMORY
     457                 :          37 :                 wipe_mem(block, slab->blockSize);
     458                 :             : #endif
     459                 :             : 
     460                 :             :                 /* As in aset.c, free block-header vchunks explicitly */
     461                 :          37 :                 VALGRIND_MEMPOOL_FREE(slab, block);
     462                 :             : 
     463                 :          37 :                 free(block);
     464                 :          37 :                 context->mem_allocated -= slab->blockSize;
     465                 :          37 :         }
     466                 :             : 
     467                 :             :         /* walk over blocklist and free the blocks */
     468         [ +  + ]:       13940 :         for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
     469                 :             :         {
     470   [ +  -  +  + ]:       11154 :                 dlist_foreach_modify(miter, &slab->blocklist[i])
     471                 :             :                 {
     472                 :         699 :                         SlabBlock  *block = dlist_container(SlabBlock, node, miter.cur);
     473                 :             : 
     474                 :         699 :                         dlist_delete(miter.cur);
     475                 :             : 
     476                 :             : #ifdef CLOBBER_FREED_MEMORY
     477                 :         699 :                         wipe_mem(block, slab->blockSize);
     478                 :             : #endif
     479                 :             : 
     480                 :             :                         /* As in aset.c, free block-header vchunks explicitly */
     481                 :         699 :                         VALGRIND_MEMPOOL_FREE(slab, block);
     482                 :             : 
     483                 :         699 :                         free(block);
     484                 :         699 :                         context->mem_allocated -= slab->blockSize;
     485                 :         699 :                 }
     486                 :       10455 :         }
     487                 :             : 
     488                 :             :         /*
     489                 :             :          * Instruct Valgrind to throw away all the vchunks associated with this
     490                 :             :          * context, except for the one covering the SlabContext.  This gets rid of
     491                 :             :          * the vchunks for whatever user data is getting discarded by the context
     492                 :             :          * reset.
     493                 :             :          */
     494                 :        3485 :         VALGRIND_MEMPOOL_TRIM(slab, slab, sizeof(SlabContext));
     495                 :             : 
     496                 :        3485 :         slab->curBlocklistIndex = 0;
     497                 :             : 
     498         [ +  - ]:        3485 :         Assert(context->mem_allocated == 0);
     499                 :        3485 : }
     500                 :             : 
     501                 :             : /*
     502                 :             :  * SlabDelete
     503                 :             :  *              Free all memory which is allocated in the given context.
     504                 :             :  */
     505                 :             : void
     506                 :        3485 : SlabDelete(MemoryContext context)
     507                 :             : {
     508                 :             :         /* Reset to release all the SlabBlocks */
     509                 :        3485 :         SlabReset(context);
     510                 :             : 
     511                 :             :         /* Destroy the vpool -- see notes in aset.c */
     512                 :        3485 :         VALGRIND_DESTROY_MEMPOOL(context);
     513                 :             : 
     514                 :             :         /* And free the context header */
     515                 :        3485 :         free(context);
     516                 :        3485 : }
     517                 :             : 
     518                 :             : /*
     519                 :             :  * Small helper for allocating a new chunk from a chunk, to avoid duplicating
     520                 :             :  * the code between SlabAlloc() and SlabAllocFromNewBlock().
     521                 :             :  */
     522                 :             : static inline void *
     523                 :         741 : SlabAllocSetupNewChunk(MemoryContext context, SlabBlock *block,
     524                 :             :                                            MemoryChunk *chunk, Size size)
     525                 :             : {
     526                 :         741 :         SlabContext *slab = (SlabContext *) context;
     527                 :             : 
     528                 :             :         /*
     529                 :             :          * Check that the chunk pointer is actually somewhere on the block and is
     530                 :             :          * aligned as expected.
     531                 :             :          */
     532         [ +  - ]:         741 :         Assert(chunk >= SlabBlockGetChunk(slab, block, 0));
     533         [ +  - ]:         741 :         Assert(chunk <= SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1));
     534         [ +  - ]:         741 :         Assert(SlabChunkMod(slab, block, chunk) == 0);
     535                 :             : 
     536                 :             :         /* Prepare to initialize the chunk header. */
     537                 :         741 :         VALGRIND_MAKE_MEM_UNDEFINED(chunk, Slab_CHUNKHDRSZ);
     538                 :             : 
     539                 :         741 :         MemoryChunkSetHdrMask(chunk, block, MAXALIGN(slab->chunkSize), MCTX_SLAB_ID);
     540                 :             : 
     541                 :             : #ifdef MEMORY_CONTEXT_CHECKING
     542                 :             :         /* slab mark to catch clobber of "unused" space */
     543         [ +  - ]:         741 :         Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ));
     544                 :         741 :         set_sentinel(MemoryChunkGetPointer(chunk), size);
     545                 :         741 :         VALGRIND_MAKE_MEM_NOACCESS(((char *) chunk) + Slab_CHUNKHDRSZ +
     546                 :             :                                                            slab->chunkSize,
     547                 :             :                                                            slab->fullChunkSize -
     548                 :             :                                                            (slab->chunkSize + Slab_CHUNKHDRSZ));
     549                 :             : #endif
     550                 :             : 
     551                 :             : #ifdef RANDOMIZE_ALLOCATED_MEMORY
     552                 :             :         /* fill the allocated space with junk */
     553                 :             :         randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
     554                 :             : #endif
     555                 :             : 
     556                 :             :         /* Disallow access to the chunk header. */
     557                 :         741 :         VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
     558                 :             : 
     559                 :        1482 :         return MemoryChunkGetPointer(chunk);
     560                 :         741 : }
     561                 :             : 
     562                 :             : pg_noinline
     563                 :             : static void *
     564                 :         740 : SlabAllocFromNewBlock(MemoryContext context, Size size, int flags)
     565                 :             : {
     566                 :         740 :         SlabContext *slab = (SlabContext *) context;
     567                 :         740 :         SlabBlock  *block;
     568                 :         740 :         MemoryChunk *chunk;
     569                 :         740 :         dlist_head *blocklist;
     570                 :         740 :         int                     blocklist_idx;
     571                 :             : 
     572                 :             :         /* to save allocating a new one, first check the empty blocks list */
     573         [ +  + ]:         740 :         if (dclist_count(&slab->emptyblocks) > 0)
     574                 :             :         {
     575                 :           4 :                 dlist_node *node = dclist_pop_head_node(&slab->emptyblocks);
     576                 :             : 
     577                 :           4 :                 block = dlist_container(SlabBlock, node, node);
     578                 :             : 
     579                 :             :                 /*
     580                 :             :                  * SlabFree() should have left this block in a valid state with all
     581                 :             :                  * chunks free.  Ensure that's the case.
     582                 :             :                  */
     583         [ +  - ]:           4 :                 Assert(block->nfree == slab->chunksPerBlock);
     584                 :             : 
     585                 :             :                 /* fetch the next chunk from this block */
     586                 :           4 :                 chunk = SlabGetNextFreeChunk(slab, block);
     587                 :           4 :         }
     588                 :             :         else
     589                 :             :         {
     590                 :         736 :                 block = (SlabBlock *) malloc(slab->blockSize);
     591                 :             : 
     592         [ -  + ]:         736 :                 if (unlikely(block == NULL))
     593                 :           0 :                         return MemoryContextAllocationFailure(context, size, flags);
     594                 :             : 
     595                 :             :                 /* Make a vchunk covering the new block's header */
     596                 :         736 :                 VALGRIND_MEMPOOL_ALLOC(slab, block, Slab_BLOCKHDRSZ);
     597                 :             : 
     598                 :         736 :                 block->slab = slab;
     599                 :         736 :                 context->mem_allocated += slab->blockSize;
     600                 :             : 
     601                 :             :                 /* use the first chunk in the new block */
     602                 :         736 :                 chunk = SlabBlockGetChunk(slab, block, 0);
     603                 :             : 
     604                 :         736 :                 block->nfree = slab->chunksPerBlock - 1;
     605                 :         736 :                 block->unused = SlabBlockGetChunk(slab, block, 1);
     606                 :         736 :                 block->freehead = NULL;
     607                 :         736 :                 block->nunused = slab->chunksPerBlock - 1;
     608                 :             :         }
     609                 :             : 
     610                 :             :         /* find the blocklist element for storing blocks with 1 used chunk */
     611                 :         740 :         blocklist_idx = SlabBlocklistIndex(slab, block->nfree);
     612                 :         740 :         blocklist = &slab->blocklist[blocklist_idx];
     613                 :             : 
     614                 :             :         /* this better be empty.  We just added a block thinking it was */
     615         [ +  - ]:         740 :         Assert(dlist_is_empty(blocklist));
     616                 :             : 
     617                 :         740 :         dlist_push_head(blocklist, &block->node);
     618                 :             : 
     619                 :         740 :         slab->curBlocklistIndex = blocklist_idx;
     620                 :             : 
     621                 :         740 :         return SlabAllocSetupNewChunk(context, block, chunk, size);
     622                 :         740 : }
     623                 :             : 
     624                 :             : /*
     625                 :             :  * SlabAllocInvalidSize
     626                 :             :  *              Handle raising an ERROR for an invalid size request.  We don't do this
     627                 :             :  *              in slab alloc as calling the elog functions would force the compiler
     628                 :             :  *              to setup the stack frame in SlabAlloc.  For performance reasons, we
     629                 :             :  *              want to avoid that.
     630                 :             :  */
     631                 :             : pg_noinline
     632                 :             : pg_noreturn
     633                 :             : static void
     634                 :           0 : SlabAllocInvalidSize(MemoryContext context, Size size)
     635                 :             : {
     636                 :           0 :         SlabContext *slab = (SlabContext *) context;
     637                 :             : 
     638   [ #  #  #  # ]:           0 :         elog(ERROR, "unexpected alloc chunk size %zu (expected %u)", size,
     639                 :             :                  slab->chunkSize);
     640                 :           0 : }
     641                 :             : 
     642                 :             : /*
     643                 :             :  * SlabAlloc
     644                 :             :  *              Returns a pointer to a newly allocated memory chunk or raises an ERROR
     645                 :             :  *              on allocation failure, or returns NULL when flags contains
     646                 :             :  *              MCXT_ALLOC_NO_OOM.  'size' must be the same size as was specified
     647                 :             :  *              during SlabContextCreate().
     648                 :             :  *
     649                 :             :  * This function should only contain the most common code paths.  Everything
     650                 :             :  * else should be in pg_noinline helper functions, thus avoiding the overhead
     651                 :             :  * of creating a stack frame for the common cases.  Allocating memory is often
     652                 :             :  * a bottleneck in many workloads, so avoiding stack frame setup is
     653                 :             :  * worthwhile.  Helper functions should always directly return the newly
     654                 :             :  * allocated memory so that we can just return that address directly as a tail
     655                 :             :  * call.
     656                 :             :  */
     657                 :             : void *
     658                 :         741 : SlabAlloc(MemoryContext context, Size size, int flags)
     659                 :             : {
     660                 :         741 :         SlabContext *slab = (SlabContext *) context;
     661                 :         741 :         SlabBlock  *block;
     662                 :         741 :         MemoryChunk *chunk;
     663                 :             : 
     664         [ +  - ]:         741 :         Assert(SlabIsValid(slab));
     665                 :             : 
     666                 :             :         /* sanity check that this is pointing to a valid blocklist */
     667         [ +  - ]:         741 :         Assert(slab->curBlocklistIndex >= 0);
     668         [ +  - ]:         741 :         Assert(slab->curBlocklistIndex <= SlabBlocklistIndex(slab, slab->chunksPerBlock));
     669                 :             : 
     670                 :             :         /*
     671                 :             :          * Make sure we only allow correct request size.  This doubles as the
     672                 :             :          * MemoryContextCheckSize check.
     673                 :             :          */
     674         [ +  - ]:         741 :         if (unlikely(size != slab->chunkSize))
     675                 :           0 :                 SlabAllocInvalidSize(context, size);
     676                 :             : 
     677         [ +  + ]:         741 :         if (unlikely(slab->curBlocklistIndex == 0))
     678                 :             :         {
     679                 :             :                 /*
     680                 :             :                  * Handle the case when there are no partially filled blocks
     681                 :             :                  * available.  This happens either when the last allocation took the
     682                 :             :                  * last chunk in the block, or when SlabFree() free'd the final block.
     683                 :             :                  */
     684                 :         740 :                 return SlabAllocFromNewBlock(context, size, flags);
     685                 :             :         }
     686                 :             :         else
     687                 :             :         {
     688                 :           1 :                 dlist_head *blocklist = &slab->blocklist[slab->curBlocklistIndex];
     689                 :           1 :                 int                     new_blocklist_idx;
     690                 :             : 
     691         [ +  - ]:           1 :                 Assert(!dlist_is_empty(blocklist));
     692                 :             : 
     693                 :             :                 /* grab the block from the blocklist */
     694                 :           1 :                 block = dlist_head_element(SlabBlock, node, blocklist);
     695                 :             : 
     696                 :             :                 /* make sure we actually got a valid block, with matching nfree */
     697         [ +  - ]:           1 :                 Assert(block != NULL);
     698         [ +  - ]:           1 :                 Assert(slab->curBlocklistIndex == SlabBlocklistIndex(slab, block->nfree));
     699         [ +  - ]:           1 :                 Assert(block->nfree > 0);
     700                 :             : 
     701                 :             :                 /* fetch the next chunk from this block */
     702                 :           1 :                 chunk = SlabGetNextFreeChunk(slab, block);
     703                 :             : 
     704                 :             :                 /* get the new blocklist index based on the new free chunk count */
     705                 :           1 :                 new_blocklist_idx = SlabBlocklistIndex(slab, block->nfree);
     706                 :             : 
     707                 :             :                 /*
     708                 :             :                  * Handle the case where the blocklist index changes.  This also deals
     709                 :             :                  * with blocks becoming full as only full blocks go at index 0.
     710                 :             :                  */
     711         [ +  - ]:           1 :                 if (unlikely(slab->curBlocklistIndex != new_blocklist_idx))
     712                 :             :                 {
     713                 :           0 :                         dlist_delete_from(blocklist, &block->node);
     714                 :           0 :                         dlist_push_head(&slab->blocklist[new_blocklist_idx], &block->node);
     715                 :             : 
     716         [ #  # ]:           0 :                         if (dlist_is_empty(blocklist))
     717                 :           0 :                                 slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab);
     718                 :           0 :                 }
     719                 :           1 :         }
     720                 :             : 
     721                 :           1 :         return SlabAllocSetupNewChunk(context, block, chunk, size);
     722                 :         741 : }
     723                 :             : 
     724                 :             : /*
     725                 :             :  * SlabFree
     726                 :             :  *              Frees allocated memory; memory is removed from the slab.
     727                 :             :  */
     728                 :             : void
     729                 :          42 : SlabFree(void *pointer)
     730                 :             : {
     731                 :          42 :         MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
     732                 :          42 :         SlabBlock  *block;
     733                 :          42 :         SlabContext *slab;
     734                 :          42 :         int                     curBlocklistIdx;
     735                 :          42 :         int                     newBlocklistIdx;
     736                 :             : 
     737                 :             :         /* Allow access to the chunk header. */
     738                 :          42 :         VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
     739                 :             : 
     740                 :          42 :         block = MemoryChunkGetBlock(chunk);
     741                 :             : 
     742                 :             :         /*
     743                 :             :          * For speed reasons we just Assert that the referenced block is good.
     744                 :             :          * Future field experience may show that this Assert had better become a
     745                 :             :          * regular runtime test-and-elog check.
     746                 :             :          */
     747         [ +  - ]:          42 :         Assert(SlabBlockIsValid(block));
     748                 :          42 :         slab = block->slab;
     749                 :             : 
     750                 :             : #ifdef MEMORY_CONTEXT_CHECKING
     751                 :             :         /* Test for someone scribbling on unused space in chunk */
     752         [ +  - ]:          42 :         Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ));
     753         [ +  - ]:          42 :         if (!sentinel_ok(pointer, slab->chunkSize))
     754   [ #  #  #  # ]:           0 :                 elog(WARNING, "detected write past chunk end in %s %p",
     755                 :             :                          slab->header.name, chunk);
     756                 :             : #endif
     757                 :             : 
     758                 :             :         /* push this chunk onto the head of the block's free list */
     759                 :          42 :         *(MemoryChunk **) pointer = block->freehead;
     760                 :          42 :         block->freehead = chunk;
     761                 :             : 
     762                 :          42 :         block->nfree++;
     763                 :             : 
     764         [ +  - ]:          42 :         Assert(block->nfree > 0);
     765         [ +  - ]:          42 :         Assert(block->nfree <= slab->chunksPerBlock);
     766                 :             : 
     767                 :             : #ifdef CLOBBER_FREED_MEMORY
     768                 :             :         /* don't wipe the free list MemoryChunk pointer stored in the chunk */
     769                 :          84 :         wipe_mem((char *) pointer + sizeof(MemoryChunk *),
     770                 :          42 :                          slab->chunkSize - sizeof(MemoryChunk *));
     771                 :             : #endif
     772                 :             : 
     773                 :          42 :         curBlocklistIdx = SlabBlocklistIndex(slab, block->nfree - 1);
     774                 :          42 :         newBlocklistIdx = SlabBlocklistIndex(slab, block->nfree);
     775                 :             : 
     776                 :             :         /*
     777                 :             :          * Check if the block needs to be moved to another element on the
     778                 :             :          * blocklist based on it now having 1 more free chunk.
     779                 :             :          */
     780         [ +  - ]:          42 :         if (unlikely(curBlocklistIdx != newBlocklistIdx))
     781                 :             :         {
     782                 :             :                 /* do the move */
     783                 :           0 :                 dlist_delete_from(&slab->blocklist[curBlocklistIdx], &block->node);
     784                 :           0 :                 dlist_push_head(&slab->blocklist[newBlocklistIdx], &block->node);
     785                 :             : 
     786                 :             :                 /*
     787                 :             :                  * The blocklist[curBlocklistIdx] may now be empty or we may now be
     788                 :             :                  * able to use a lower-element blocklist.  We'll need to redetermine
     789                 :             :                  * what the slab->curBlocklistIndex is if the current blocklist was
     790                 :             :                  * changed or if a lower element one was changed.  We must ensure we
     791                 :             :                  * use the list with the fullest block(s).
     792                 :             :                  */
     793         [ #  # ]:           0 :                 if (slab->curBlocklistIndex >= curBlocklistIdx)
     794                 :             :                 {
     795                 :           0 :                         slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab);
     796                 :             : 
     797                 :             :                         /*
     798                 :             :                          * We know there must be a block with at least 1 unused chunk as
     799                 :             :                          * we just pfree'd one.  Ensure curBlocklistIndex reflects this.
     800                 :             :                          */
     801         [ #  # ]:           0 :                         Assert(slab->curBlocklistIndex > 0);
     802                 :           0 :                 }
     803                 :           0 :         }
     804                 :             : 
     805                 :             :         /* Handle when a block becomes completely empty */
     806         [ +  + ]:          42 :         if (unlikely(block->nfree == slab->chunksPerBlock))
     807                 :             :         {
     808                 :             :                 /* remove the block */
     809                 :          41 :                 dlist_delete_from(&slab->blocklist[newBlocklistIdx], &block->node);
     810                 :             : 
     811                 :             :                 /*
     812                 :             :                  * To avoid thrashing malloc/free, we keep a list of empty blocks that
     813                 :             :                  * we can reuse again instead of having to malloc a new one.
     814                 :             :                  */
     815         [ +  - ]:          41 :                 if (dclist_count(&slab->emptyblocks) < SLAB_MAXIMUM_EMPTY_BLOCKS)
     816                 :          41 :                         dclist_push_head(&slab->emptyblocks, &block->node);
     817                 :             :                 else
     818                 :             :                 {
     819                 :             :                         /*
     820                 :             :                          * When we have enough empty blocks stored already, we actually
     821                 :             :                          * free the block.
     822                 :             :                          */
     823                 :             : #ifdef CLOBBER_FREED_MEMORY
     824                 :           0 :                         wipe_mem(block, slab->blockSize);
     825                 :             : #endif
     826                 :             : 
     827                 :             :                         /* As in aset.c, free block-header vchunks explicitly */
     828                 :           0 :                         VALGRIND_MEMPOOL_FREE(slab, block);
     829                 :             : 
     830                 :           0 :                         free(block);
     831                 :           0 :                         slab->header.mem_allocated -= slab->blockSize;
     832                 :             :                 }
     833                 :             : 
     834                 :             :                 /*
     835                 :             :                  * Check if we need to reset the blocklist index.  This is required
     836                 :             :                  * when the blocklist this block is on has become completely empty.
     837                 :             :                  */
     838   [ +  -  -  + ]:          41 :                 if (slab->curBlocklistIndex == newBlocklistIdx &&
     839                 :          41 :                         dlist_is_empty(&slab->blocklist[newBlocklistIdx]))
     840                 :          41 :                         slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab);
     841                 :          41 :         }
     842                 :          42 : }
     843                 :             : 
     844                 :             : /*
     845                 :             :  * SlabRealloc
     846                 :             :  *              Change the allocated size of a chunk.
     847                 :             :  *
     848                 :             :  * As Slab is designed for allocating equally-sized chunks of memory, it can't
     849                 :             :  * do an actual chunk size change.  We try to be gentle and allow calls with
     850                 :             :  * exactly the same size, as in that case we can simply return the same
     851                 :             :  * chunk.  When the size differs, we throw an error.
     852                 :             :  *
     853                 :             :  * We could also allow requests with size < chunkSize.  That however seems
     854                 :             :  * rather pointless - Slab is meant for chunks of constant size, and moreover
     855                 :             :  * realloc is usually used to enlarge the chunk.
     856                 :             :  */
     857                 :             : void *
     858                 :           0 : SlabRealloc(void *pointer, Size size, int flags)
     859                 :             : {
     860                 :           0 :         MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
     861                 :           0 :         SlabBlock  *block;
     862                 :           0 :         SlabContext *slab;
     863                 :             : 
     864                 :             :         /* Allow access to the chunk header. */
     865                 :           0 :         VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
     866                 :             : 
     867                 :           0 :         block = MemoryChunkGetBlock(chunk);
     868                 :             : 
     869                 :             :         /* Disallow access to the chunk header. */
     870                 :           0 :         VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
     871                 :             : 
     872                 :             :         /*
     873                 :             :          * Try to verify that we have a sane block pointer: the block header
     874                 :             :          * should reference a slab context.  (We use a test-and-elog, not just
     875                 :             :          * Assert, because it seems highly likely that we're here in error in the
     876                 :             :          * first place.)
     877                 :             :          */
     878         [ #  # ]:           0 :         if (!SlabBlockIsValid(block))
     879   [ #  #  #  # ]:           0 :                 elog(ERROR, "could not find block containing chunk %p", chunk);
     880                 :           0 :         slab = block->slab;
     881                 :             : 
     882                 :             :         /* can't do actual realloc with slab, but let's try to be gentle */
     883         [ #  # ]:           0 :         if (size == slab->chunkSize)
     884                 :           0 :                 return pointer;
     885                 :             : 
     886   [ #  #  #  # ]:           0 :         elog(ERROR, "slab allocator does not support realloc()");
     887                 :           0 :         return NULL;                            /* keep compiler quiet */
     888                 :           0 : }
     889                 :             : 
     890                 :             : /*
     891                 :             :  * SlabGetChunkContext
     892                 :             :  *              Return the MemoryContext that 'pointer' belongs to.
     893                 :             :  */
     894                 :             : MemoryContext
     895                 :           0 : SlabGetChunkContext(void *pointer)
     896                 :             : {
     897                 :           0 :         MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
     898                 :           0 :         SlabBlock  *block;
     899                 :             : 
     900                 :             :         /* Allow access to the chunk header. */
     901                 :           0 :         VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
     902                 :             : 
     903                 :           0 :         block = MemoryChunkGetBlock(chunk);
     904                 :             : 
     905                 :             :         /* Disallow access to the chunk header. */
     906                 :           0 :         VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
     907                 :             : 
     908         [ #  # ]:           0 :         Assert(SlabBlockIsValid(block));
     909                 :             : 
     910                 :           0 :         return &block->slab->header;
     911                 :           0 : }
     912                 :             : 
     913                 :             : /*
     914                 :             :  * SlabGetChunkSpace
     915                 :             :  *              Given a currently-allocated chunk, determine the total space
     916                 :             :  *              it occupies (including all memory-allocation overhead).
     917                 :             :  */
     918                 :             : Size
     919                 :           0 : SlabGetChunkSpace(void *pointer)
     920                 :             : {
     921                 :           0 :         MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
     922                 :           0 :         SlabBlock  *block;
     923                 :           0 :         SlabContext *slab;
     924                 :             : 
     925                 :             :         /* Allow access to the chunk header. */
     926                 :           0 :         VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
     927                 :             : 
     928                 :           0 :         block = MemoryChunkGetBlock(chunk);
     929                 :             : 
     930                 :             :         /* Disallow access to the chunk header. */
     931                 :           0 :         VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
     932                 :             : 
     933         [ #  # ]:           0 :         Assert(SlabBlockIsValid(block));
     934                 :           0 :         slab = block->slab;
     935                 :             : 
     936                 :           0 :         return slab->fullChunkSize;
     937                 :           0 : }
     938                 :             : 
     939                 :             : /*
     940                 :             :  * SlabIsEmpty
     941                 :             :  *              Is the slab empty of any allocated space?
     942                 :             :  */
     943                 :             : bool
     944                 :           0 : SlabIsEmpty(MemoryContext context)
     945                 :             : {
     946         [ #  # ]:           0 :         Assert(SlabIsValid((SlabContext *) context));
     947                 :             : 
     948                 :           0 :         return (context->mem_allocated == 0);
     949                 :             : }
     950                 :             : 
     951                 :             : /*
     952                 :             :  * SlabStats
     953                 :             :  *              Compute stats about memory consumption of a Slab context.
     954                 :             :  *
     955                 :             :  * printfunc: if not NULL, pass a human-readable stats string to this.
     956                 :             :  * passthru: pass this pointer through to printfunc.
     957                 :             :  * totals: if not NULL, add stats about this context into *totals.
     958                 :             :  * print_to_stderr: print stats to stderr if true, elog otherwise.
     959                 :             :  */
     960                 :             : void
     961                 :           0 : SlabStats(MemoryContext context,
     962                 :             :                   MemoryStatsPrintFunc printfunc, void *passthru,
     963                 :             :                   MemoryContextCounters *totals,
     964                 :             :                   bool print_to_stderr)
     965                 :             : {
     966                 :           0 :         SlabContext *slab = (SlabContext *) context;
     967                 :           0 :         Size            nblocks = 0;
     968                 :           0 :         Size            freechunks = 0;
     969                 :           0 :         Size            totalspace;
     970                 :           0 :         Size            freespace = 0;
     971                 :           0 :         int                     i;
     972                 :             : 
     973         [ #  # ]:           0 :         Assert(SlabIsValid(slab));
     974                 :             : 
     975                 :             :         /* Include context header in totalspace */
     976                 :           0 :         totalspace = Slab_CONTEXT_HDRSZ(slab->chunksPerBlock);
     977                 :             : 
     978                 :             :         /* Add the space consumed by blocks in the emptyblocks list */
     979                 :           0 :         totalspace += dclist_count(&slab->emptyblocks) * slab->blockSize;
     980                 :             : 
     981         [ #  # ]:           0 :         for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
     982                 :             :         {
     983                 :           0 :                 dlist_iter      iter;
     984                 :             : 
     985   [ #  #  #  # ]:           0 :                 dlist_foreach(iter, &slab->blocklist[i])
     986                 :             :                 {
     987                 :           0 :                         SlabBlock  *block = dlist_container(SlabBlock, node, iter.cur);
     988                 :             : 
     989                 :           0 :                         nblocks++;
     990                 :           0 :                         totalspace += slab->blockSize;
     991                 :           0 :                         freespace += slab->fullChunkSize * block->nfree;
     992                 :           0 :                         freechunks += block->nfree;
     993                 :           0 :                 }
     994                 :           0 :         }
     995                 :             : 
     996         [ #  # ]:           0 :         if (printfunc)
     997                 :             :         {
     998                 :           0 :                 char            stats_string[200];
     999                 :             : 
    1000                 :             :                 /* XXX should we include free chunks on empty blocks? */
    1001                 :           0 :                 snprintf(stats_string, sizeof(stats_string),
    1002                 :             :                                  "%zu total in %zu blocks; %u empty blocks; %zu free (%zu chunks); %zu used",
    1003                 :           0 :                                  totalspace, nblocks, dclist_count(&slab->emptyblocks),
    1004                 :           0 :                                  freespace, freechunks, totalspace - freespace);
    1005                 :           0 :                 printfunc(context, passthru, stats_string, print_to_stderr);
    1006                 :           0 :         }
    1007                 :             : 
    1008         [ #  # ]:           0 :         if (totals)
    1009                 :             :         {
    1010                 :           0 :                 totals->nblocks += nblocks;
    1011                 :           0 :                 totals->freechunks += freechunks;
    1012                 :           0 :                 totals->totalspace += totalspace;
    1013                 :           0 :                 totals->freespace += freespace;
    1014                 :           0 :         }
    1015                 :           0 : }
    1016                 :             : 
    1017                 :             : 
    1018                 :             : #ifdef MEMORY_CONTEXT_CHECKING
    1019                 :             : 
    1020                 :             : /*
    1021                 :             :  * SlabCheck
    1022                 :             :  *              Walk through all blocks looking for inconsistencies.
    1023                 :             :  *
    1024                 :             :  * NOTE: report errors as WARNING, *not* ERROR or FATAL.  Otherwise you'll
    1025                 :             :  * find yourself in an infinite loop when trouble occurs, because this
    1026                 :             :  * routine will be entered again when elog cleanup tries to release memory!
    1027                 :             :  */
    1028                 :             : void
    1029                 :        3485 : SlabCheck(MemoryContext context)
    1030                 :             : {
    1031                 :        3485 :         SlabContext *slab = (SlabContext *) context;
    1032                 :        3485 :         int                     i;
    1033                 :        3485 :         int                     nblocks = 0;
    1034                 :        3485 :         const char *name = slab->header.name;
    1035                 :        3485 :         dlist_iter      iter;
    1036                 :             : 
    1037         [ +  - ]:        3485 :         Assert(SlabIsValid(slab));
    1038         [ +  - ]:        3485 :         Assert(slab->chunksPerBlock > 0);
    1039                 :             : 
    1040                 :             :         /*
    1041                 :             :          * Have a look at the empty blocks.  These should have all their chunks
    1042                 :             :          * marked as free.  Ensure that's the case.
    1043                 :             :          */
    1044   [ +  -  +  + ]:        3522 :         dclist_foreach(iter, &slab->emptyblocks)
    1045                 :             :         {
    1046                 :          37 :                 SlabBlock  *block = dlist_container(SlabBlock, node, iter.cur);
    1047                 :             : 
    1048         [ +  - ]:          37 :                 if (block->nfree != slab->chunksPerBlock)
    1049   [ #  #  #  # ]:           0 :                         elog(WARNING, "problem in slab %s: empty block %p should have %d free chunks but has %d chunks free",
    1050                 :             :                                  name, block, slab->chunksPerBlock, block->nfree);
    1051                 :          37 :         }
    1052                 :             : 
    1053                 :             :         /* walk the non-empty block lists */
    1054         [ +  + ]:       13940 :         for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
    1055                 :             :         {
    1056                 :       10455 :                 int                     j,
    1057                 :             :                                         nfree;
    1058                 :             : 
    1059                 :             :                 /* walk all blocks on this blocklist */
    1060   [ +  -  +  + ]:       11154 :                 dlist_foreach(iter, &slab->blocklist[i])
    1061                 :             :                 {
    1062                 :         699 :                         SlabBlock  *block = dlist_container(SlabBlock, node, iter.cur);
    1063                 :         699 :                         MemoryChunk *cur_chunk;
    1064                 :             : 
    1065                 :             :                         /*
    1066                 :             :                          * Make sure the number of free chunks (in the block header)
    1067                 :             :                          * matches the position in the blocklist.
    1068                 :             :                          */
    1069         [ +  - ]:         699 :                         if (SlabBlocklistIndex(slab, block->nfree) != i)
    1070   [ #  #  #  # ]:           0 :                                 elog(WARNING, "problem in slab %s: block %p is on blocklist %d but should be on blocklist %d",
    1071                 :             :                                          name, block, i, SlabBlocklistIndex(slab, block->nfree));
    1072                 :             : 
    1073                 :             :                         /* make sure the block is not empty */
    1074         [ +  - ]:         699 :                         if (block->nfree >= slab->chunksPerBlock)
    1075   [ #  #  #  # ]:           0 :                                 elog(WARNING, "problem in slab %s: empty block %p incorrectly stored on blocklist element %d",
    1076                 :             :                                          name, block, i);
    1077                 :             : 
    1078                 :             :                         /* make sure the slab pointer correctly points to this context */
    1079         [ +  - ]:         699 :                         if (block->slab != slab)
    1080   [ #  #  #  # ]:           0 :                                 elog(WARNING, "problem in slab %s: bogus slab link in block %p",
    1081                 :             :                                          name, block);
    1082                 :             : 
    1083                 :             :                         /* reset the array of free chunks for this block */
    1084                 :         699 :                         memset(slab->isChunkFree, 0, (slab->chunksPerBlock * sizeof(bool)));
    1085                 :         699 :                         nfree = 0;
    1086                 :             : 
    1087                 :             :                         /* walk through the block's free list chunks */
    1088                 :         699 :                         cur_chunk = block->freehead;
    1089         [ +  + ]:         700 :                         while (cur_chunk != NULL)
    1090                 :             :                         {
    1091                 :           1 :                                 int                     chunkidx = SlabChunkIndex(slab, block, cur_chunk);
    1092                 :             : 
    1093                 :             :                                 /*
    1094                 :             :                                  * Ensure the free list link points to something on the block
    1095                 :             :                                  * at an address aligned according to the full chunk size.
    1096                 :             :                                  */
    1097         [ +  - ]:           1 :                                 if (cur_chunk < SlabBlockGetChunk(slab, block, 0) ||
    1098   [ +  -  -  + ]:           1 :                                         cur_chunk > SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1) ||
    1099                 :           1 :                                         SlabChunkMod(slab, block, cur_chunk) != 0)
    1100   [ #  #  #  # ]:           0 :                                         elog(WARNING, "problem in slab %s: bogus free list link %p in block %p",
    1101                 :             :                                                  name, cur_chunk, block);
    1102                 :             : 
    1103                 :             :                                 /* count the chunk and mark it free on the free chunk array */
    1104                 :           1 :                                 nfree++;
    1105                 :           1 :                                 slab->isChunkFree[chunkidx] = true;
    1106                 :             : 
    1107                 :             :                                 /* read pointer of the next free chunk */
    1108                 :           1 :                                 VALGRIND_MAKE_MEM_DEFINED(MemoryChunkGetPointer(cur_chunk), sizeof(MemoryChunk *));
    1109                 :           1 :                                 cur_chunk = *(MemoryChunk **) SlabChunkGetPointer(cur_chunk);
    1110                 :           1 :                         }
    1111                 :             : 
    1112                 :             :                         /* check that the unused pointer matches what nunused claims */
    1113   [ +  -  +  - ]:        1398 :                         if (SlabBlockGetChunk(slab, block, slab->chunksPerBlock - block->nunused) !=
    1114                 :         699 :                                 block->unused)
    1115   [ #  #  #  # ]:           0 :                                 elog(WARNING, "problem in slab %s: mismatch detected between nunused chunks and unused pointer in block %p",
    1116                 :             :                                          name, block);
    1117                 :             : 
    1118                 :             :                         /*
    1119                 :             :                          * count the remaining free chunks that have yet to make it onto
    1120                 :             :                          * the block's free list.
    1121                 :             :                          */
    1122                 :         699 :                         cur_chunk = block->unused;
    1123         [ +  + ]:       87035 :                         for (j = 0; j < block->nunused; j++)
    1124                 :             :                         {
    1125                 :       86336 :                                 int                     chunkidx = SlabChunkIndex(slab, block, cur_chunk);
    1126                 :             : 
    1127                 :             : 
    1128                 :             :                                 /* count the chunk as free and mark it as so in the array */
    1129                 :       86336 :                                 nfree++;
    1130         [ -  + ]:       86336 :                                 if (chunkidx < slab->chunksPerBlock)
    1131                 :       86336 :                                         slab->isChunkFree[chunkidx] = true;
    1132                 :             : 
    1133                 :             :                                 /* move forward 1 chunk */
    1134                 :       86336 :                                 cur_chunk = (MemoryChunk *) (((char *) cur_chunk) + slab->fullChunkSize);
    1135                 :       86336 :                         }
    1136                 :             : 
    1137         [ +  + ]:       87735 :                         for (j = 0; j < slab->chunksPerBlock; j++)
    1138                 :             :                         {
    1139         [ +  + ]:       87036 :                                 if (!slab->isChunkFree[j])
    1140                 :             :                                 {
    1141                 :         699 :                                         MemoryChunk *chunk = SlabBlockGetChunk(slab, block, j);
    1142                 :         699 :                                         SlabBlock  *chunkblock;
    1143                 :             : 
    1144                 :             :                                         /* Allow access to the chunk header. */
    1145                 :         699 :                                         VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
    1146                 :             : 
    1147                 :         699 :                                         chunkblock = (SlabBlock *) MemoryChunkGetBlock(chunk);
    1148                 :             : 
    1149                 :             :                                         /* Disallow access to the chunk header. */
    1150                 :         699 :                                         VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
    1151                 :             : 
    1152                 :             :                                         /*
    1153                 :             :                                          * check the chunk's blockoffset correctly points back to
    1154                 :             :                                          * the block
    1155                 :             :                                          */
    1156         [ +  - ]:         699 :                                         if (chunkblock != block)
    1157   [ #  #  #  # ]:           0 :                                                 elog(WARNING, "problem in slab %s: bogus block link in block %p, chunk %p",
    1158                 :             :                                                          name, block, chunk);
    1159                 :             : 
    1160                 :             :                                         /* check the sentinel byte is intact */
    1161         [ -  + ]:         699 :                                         Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ));
    1162         [ +  - ]:         699 :                                         if (!sentinel_ok(chunk, Slab_CHUNKHDRSZ + slab->chunkSize))
    1163   [ #  #  #  # ]:           0 :                                                 elog(WARNING, "problem in slab %s: detected write past chunk end in block %p, chunk %p",
    1164                 :             :                                                          name, block, chunk);
    1165                 :         699 :                                 }
    1166                 :       87036 :                         }
    1167                 :             : 
    1168                 :             :                         /*
    1169                 :             :                          * Make sure we got the expected number of free chunks (as tracked
    1170                 :             :                          * in the block header).
    1171                 :             :                          */
    1172         [ +  - ]:         699 :                         if (nfree != block->nfree)
    1173   [ #  #  #  # ]:           0 :                                 elog(WARNING, "problem in slab %s: nfree in block %p is %d but %d chunk were found as free",
    1174                 :             :                                          name, block, block->nfree, nfree);
    1175                 :             : 
    1176                 :         699 :                         nblocks++;
    1177                 :         699 :                 }
    1178                 :       10455 :         }
    1179                 :             : 
    1180                 :             :         /* the stored empty blocks are tracked in mem_allocated too */
    1181                 :        3485 :         nblocks += dclist_count(&slab->emptyblocks);
    1182                 :             : 
    1183         [ +  - ]:        3485 :         Assert(nblocks * slab->blockSize == context->mem_allocated);
    1184                 :        3485 : }
    1185                 :             : 
    1186                 :             : #endif                                                  /* MEMORY_CONTEXT_CHECKING */
        

Generated by: LCOV version 2.3.2-1