Adjust memory allocation functions to allow sibling calls
authorDavid Rowley <drowley@postgresql.org>
Tue, 27 Feb 2024 03:39:42 +0000 (16:39 +1300)
committerDavid Rowley <drowley@postgresql.org>
Tue, 27 Feb 2024 03:39:42 +0000 (16:39 +1300)
Many modern compilers are able to optimize function calls to functions
where the parameters of the called function match a leading subset of
the calling function's parameters.  If there are no instructions in the
calling function after the function is called, then the compiler is free
to avoid any stack frame setup and implement the function call as a
"jmp" rather than a "call".  This is called sibling call optimization.

Here we adjust the memory allocation functions in mcxt.c to allow this
optimization.  This requires moving some responsibility into the memory
context implementations themselves.  It's now the responsibility of the
MemoryContext to check for malloc failures.  This is good as it both
allows the sibling call optimization, but also because most small and
medium allocations won't call malloc and just allocate memory to an
existing block.  That can't fail, so checking for NULLs in that case
isn't required.

Also, traditionally it's been the responsibility of palloc and the other
allocation functions in mcxt.c to check for invalid allocation size
requests.  Here we also move the responsibility of checking that into the
MemoryContext.  This isn't to allow the sibling call optimization, but
more because most of our allocators handle large allocations separately
and we can just add the size check when doing large allocations.  We no
longer check this for non-large allocations at all.

To make checking the allocation request sizes and ERROR handling easier,
add some helper functions to mcxt.c for the allocators to use.

Author: Andres Freund
Reviewed-by: David Rowley
Discussion: https://postgr.es/m/20210719195950.gavgs6ujzmjfaiig@alap3.anarazel.de

src/backend/utils/mmgr/alignedalloc.c
src/backend/utils/mmgr/aset.c
src/backend/utils/mmgr/generation.c
src/backend/utils/mmgr/mcxt.c
src/backend/utils/mmgr/slab.c
src/include/nodes/memnodes.h
src/include/utils/memutils_internal.h

index 7204fe64ae6b86f4b777f5c3fd8b6affbb7dbe8e..c266fb3dbb1487d81e223d8e76ba6a2f3190638e 100644 (file)
@@ -57,7 +57,7 @@ AlignedAllocFree(void *pointer)
  *     memory will be uninitialized.
  */
 void *
-AlignedAllocRealloc(void *pointer, Size size)
+AlignedAllocRealloc(void *pointer, Size size, int flags)
 {
    MemoryChunk *redirchunk = PointerGetMemoryChunk(pointer);
    Size        alignto;
@@ -97,14 +97,17 @@ AlignedAllocRealloc(void *pointer, Size size)
 #endif
 
    ctx = GetMemoryChunkContext(unaligned);
-   newptr = MemoryContextAllocAligned(ctx, size, alignto, 0);
+   newptr = MemoryContextAllocAligned(ctx, size, alignto, flags);
 
    /*
     * We may memcpy beyond the end of the original allocation request size,
     * so we must mark the entire allocation as defined.
     */
-   VALGRIND_MAKE_MEM_DEFINED(pointer, old_size);
-   memcpy(newptr, pointer, Min(size, old_size));
+   if (likely(newptr != NULL))
+   {
+       VALGRIND_MAKE_MEM_DEFINED(pointer, old_size);
+       memcpy(newptr, pointer, Min(size, old_size));
+   }
    pfree(unaligned);
 
    return newptr;
index 2f99fa9a2f6a268cd9511534a639ea06b0a3784a..24708f67bb9da3e3c4ab64b299c8110e4e68e664 100644 (file)
@@ -700,7 +700,7 @@ AllocSetDelete(MemoryContext context)
  * return space that is marked NOACCESS - AllocSetRealloc has to beware!
  */
 void *
-AllocSetAlloc(MemoryContext context, Size size)
+AllocSetAlloc(MemoryContext context, Size size, int flags)
 {
    AllocSet    set = (AllocSet) context;
    AllocBlock  block;
@@ -717,6 +717,9 @@ AllocSetAlloc(MemoryContext context, Size size)
     */
    if (size > set->allocChunkLimit)
    {
+       /* only check size in paths where the limits could be hit */
+       MemoryContextCheckSize(context, size, flags);
+
 #ifdef MEMORY_CONTEXT_CHECKING
        /* ensure there's always space for the sentinel byte */
        chunk_size = MAXALIGN(size + 1);
@@ -727,7 +730,7 @@ AllocSetAlloc(MemoryContext context, Size size)
        blksize = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
        block = (AllocBlock) malloc(blksize);
        if (block == NULL)
-           return NULL;
+           return MemoryContextAllocationFailure(context, size, flags);
 
        context->mem_allocated += blksize;
 
@@ -940,7 +943,7 @@ AllocSetAlloc(MemoryContext context, Size size)
        }
 
        if (block == NULL)
-           return NULL;
+           return MemoryContextAllocationFailure(context, size, flags);
 
        context->mem_allocated += blksize;
 
@@ -1106,7 +1109,7 @@ AllocSetFree(void *pointer)
  * request size.)
  */
 void *
-AllocSetRealloc(void *pointer, Size size)
+AllocSetRealloc(void *pointer, Size size, int flags)
 {
    AllocBlock  block;
    AllocSet    set;
@@ -1139,6 +1142,9 @@ AllocSetRealloc(void *pointer, Size size)
 
        set = block->aset;
 
+       /* only check size in paths where the limits could be hit */
+       MemoryContextCheckSize((MemoryContext) set, size, flags);
+
        oldchksize = block->endptr - (char *) pointer;
 
 #ifdef MEMORY_CONTEXT_CHECKING
@@ -1165,7 +1171,7 @@ AllocSetRealloc(void *pointer, Size size)
        {
            /* Disallow access to the chunk header. */
            VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
-           return NULL;
+           return MemoryContextAllocationFailure(&set->header, size, flags);
        }
 
        /* updated separately, not to underflow when (oldblksize > blksize) */
@@ -1325,15 +1331,15 @@ AllocSetRealloc(void *pointer, Size size)
        AllocPointer newPointer;
        Size        oldsize;
 
-       /* allocate new chunk */
-       newPointer = AllocSetAlloc((MemoryContext) set, size);
+       /* allocate new chunk (this also checks size is valid) */
+       newPointer = AllocSetAlloc((MemoryContext) set, size, flags);
 
        /* leave immediately if request was not completed */
        if (newPointer == NULL)
        {
            /* Disallow access to the chunk header. */
            VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
-           return NULL;
+           return MemoryContextAllocationFailure((MemoryContext) set, size, flags);
        }
 
        /*
index f9016a7ed72743b065f6514486ee4003ca128c8f..ae4a7c999ed571165d319aa328497700167aec59 100644 (file)
@@ -344,7 +344,7 @@ GenerationDelete(MemoryContext context)
  * return space that is marked NOACCESS - GenerationRealloc has to beware!
  */
 void *
-GenerationAlloc(MemoryContext context, Size size)
+GenerationAlloc(MemoryContext context, Size size, int flags)
 {
    GenerationContext *set = (GenerationContext *) context;
    GenerationBlock *block;
@@ -367,9 +367,12 @@ GenerationAlloc(MemoryContext context, Size size)
    {
        Size        blksize = required_size + Generation_BLOCKHDRSZ;
 
+       /* only check size in paths where the limits could be hit */
+       MemoryContextCheckSize((MemoryContext) set, size, flags);
+
        block = (GenerationBlock *) malloc(blksize);
        if (block == NULL)
-           return NULL;
+           return MemoryContextAllocationFailure(context, size, flags);
 
        context->mem_allocated += blksize;
 
@@ -472,7 +475,7 @@ GenerationAlloc(MemoryContext context, Size size)
            block = (GenerationBlock *) malloc(blksize);
 
            if (block == NULL)
-               return NULL;
+               return MemoryContextAllocationFailure(context, size, flags);
 
            context->mem_allocated += blksize;
 
@@ -737,7 +740,7 @@ GenerationFree(void *pointer)
  *     into the old chunk - in that case we just update chunk header.
  */
 void *
-GenerationRealloc(void *pointer, Size size)
+GenerationRealloc(void *pointer, Size size, int flags)
 {
    MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
    GenerationContext *set;
@@ -839,15 +842,15 @@ GenerationRealloc(void *pointer, Size size)
        return pointer;
    }
 
-   /* allocate new chunk */
-   newPointer = GenerationAlloc((MemoryContext) set, size);
+   /* allocate new chunk (this also checks size is valid) */
+   newPointer = GenerationAlloc((MemoryContext) set, size, flags);
 
    /* leave immediately if request was not completed */
    if (newPointer == NULL)
    {
        /* Disallow access to the chunk header. */
        VALGRIND_MAKE_MEM_NOACCESS(chunk, Generation_CHUNKHDRSZ);
-       return NULL;
+       return MemoryContextAllocationFailure((MemoryContext) set, size, flags);
    }
 
    /*
index ad7409a02cdda3947c631a84b5e19c234f7c5d3c..41f2390fb8f22d1def66612ff2eccdd268ff5ca0 100644 (file)
@@ -34,7 +34,7 @@
 
 
 static void BogusFree(void *pointer);
-static void *BogusRealloc(void *pointer, Size size);
+static void *BogusRealloc(void *pointer, Size size, int flags);
 static MemoryContext BogusGetChunkContext(void *pointer);
 static Size BogusGetChunkSpace(void *pointer);
 
@@ -237,7 +237,7 @@ BogusFree(void *pointer)
 }
 
 static void *
-BogusRealloc(void *pointer, Size size)
+BogusRealloc(void *pointer, Size size, int flags)
 {
    elog(ERROR, "repalloc called with invalid pointer %p (header 0x%016llx)",
         pointer, (unsigned long long) GetMemoryChunkHeader(pointer));
@@ -1023,6 +1023,38 @@ MemoryContextCreate(MemoryContext node,
    VALGRIND_CREATE_MEMPOOL(node, 0, false);
 }
 
+/*
+ * MemoryContextAllocationFailure
+ *     For use by MemoryContextMethods implementations to handle when malloc
+ *     returns NULL.  The behavior is specific to whether MCXT_ALLOC_NO_OOM
+ *     is in 'flags'.
+ */
+void *
+MemoryContextAllocationFailure(MemoryContext context, Size size, int flags)
+{
+   if ((flags & MCXT_ALLOC_NO_OOM) == 0)
+   {
+       MemoryContextStats(TopMemoryContext);
+       ereport(ERROR,
+               (errcode(ERRCODE_OUT_OF_MEMORY),
+                errmsg("out of memory"),
+                errdetail("Failed on request of size %zu in memory context \"%s\".",
+                          size, context->name)));
+   }
+   return NULL;
+}
+
+/*
+ * MemoryContextSizeFailure
+ *     For use by MemoryContextMethods implementations to handle invalid
+ *     memory allocation request sizes.
+ */
+void
+MemoryContextSizeFailure(MemoryContext context, Size size, int flags)
+{
+   elog(ERROR, "invalid memory alloc request size %zu", size);
+}
+
 /*
  * MemoryContextAlloc
  *     Allocate space within the specified context.
@@ -1038,28 +1070,19 @@ MemoryContextAlloc(MemoryContext context, Size size)
    Assert(MemoryContextIsValid(context));
    AssertNotInCriticalSection(context);
 
-   if (!AllocSizeIsValid(size))
-       elog(ERROR, "invalid memory alloc request size %zu", size);
-
    context->isReset = false;
 
-   ret = context->methods->alloc(context, size);
-   if (unlikely(ret == NULL))
-   {
-       MemoryContextStats(TopMemoryContext);
-
-       /*
-        * Here, and elsewhere in this module, we show the target context's
-        * "name" but not its "ident" (if any) in user-visible error messages.
-        * The "ident" string might contain security-sensitive data, such as
-        * values in SQL commands.
-        */
-       ereport(ERROR,
-               (errcode(ERRCODE_OUT_OF_MEMORY),
-                errmsg("out of memory"),
-                errdetail("Failed on request of size %zu in memory context \"%s\".",
-                          size, context->name)));
-   }
+   /*
+    * For efficiency reasons, we purposefully offload the handling of
+    * allocation failures to the MemoryContextMethods implementation as this
+    * allows these checks to be performed only when an actual malloc needs to
+    * be done to request more memory from the OS.  Additionally, not having
+    * to execute any instructions after this call allows the compiler to use
+    * the sibling call optimization.  If you're considering adding code after
+    * this call, consider making it the responsibility of the 'alloc'
+    * function instead.
+    */
+   ret = context->methods->alloc(context, size, 0);
 
    VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
@@ -1081,21 +1104,9 @@ MemoryContextAllocZero(MemoryContext context, Size size)
    Assert(MemoryContextIsValid(context));
    AssertNotInCriticalSection(context);
 
-   if (!AllocSizeIsValid(size))
-       elog(ERROR, "invalid memory alloc request size %zu", size);
-
    context->isReset = false;
 
-   ret = context->methods->alloc(context, size);
-   if (unlikely(ret == NULL))
-   {
-       MemoryContextStats(TopMemoryContext);
-       ereport(ERROR,
-               (errcode(ERRCODE_OUT_OF_MEMORY),
-                errmsg("out of memory"),
-                errdetail("Failed on request of size %zu in memory context \"%s\".",
-                          size, context->name)));
-   }
+   ret = context->methods->alloc(context, size, 0);
 
    VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
@@ -1122,20 +1133,9 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 
    context->isReset = false;
 
-   ret = context->methods->alloc(context, size);
+   ret = context->methods->alloc(context, size, flags);
    if (unlikely(ret == NULL))
-   {
-       if ((flags & MCXT_ALLOC_NO_OOM) == 0)
-       {
-           MemoryContextStats(TopMemoryContext);
-           ereport(ERROR,
-                   (errcode(ERRCODE_OUT_OF_MEMORY),
-                    errmsg("out of memory"),
-                    errdetail("Failed on request of size %zu in memory context \"%s\".",
-                              size, context->name)));
-       }
        return NULL;
-   }
 
    VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
@@ -1207,22 +1207,21 @@ palloc(Size size)
    Assert(MemoryContextIsValid(context));
    AssertNotInCriticalSection(context);
 
-   if (!AllocSizeIsValid(size))
-       elog(ERROR, "invalid memory alloc request size %zu", size);
-
    context->isReset = false;
 
-   ret = context->methods->alloc(context, size);
-   if (unlikely(ret == NULL))
-   {
-       MemoryContextStats(TopMemoryContext);
-       ereport(ERROR,
-               (errcode(ERRCODE_OUT_OF_MEMORY),
-                errmsg("out of memory"),
-                errdetail("Failed on request of size %zu in memory context \"%s\".",
-                          size, context->name)));
-   }
-
+   /*
+    * For efficiency reasons, we purposefully offload the handling of
+    * allocation failures to the MemoryContextMethods implementation as this
+    * allows these checks to be performed only when an actual malloc needs to
+    * be done to request more memory from the OS.  Additionally, not having
+    * to execute any instructions after this call allows the compiler to use
+    * the sibling call optimization.  If you're considering adding code after
+    * this call, consider making it the responsibility of the 'alloc'
+    * function instead.
+    */
+   ret = context->methods->alloc(context, size, 0);
+   /* We expect OOM to be handled by the alloc function */
+   Assert(ret != NULL);
    VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
    return ret;
@@ -1238,21 +1237,9 @@ palloc0(Size size)
    Assert(MemoryContextIsValid(context));
    AssertNotInCriticalSection(context);
 
-   if (!AllocSizeIsValid(size))
-       elog(ERROR, "invalid memory alloc request size %zu", size);
-
    context->isReset = false;
 
-   ret = context->methods->alloc(context, size);
-   if (unlikely(ret == NULL))
-   {
-       MemoryContextStats(TopMemoryContext);
-       ereport(ERROR,
-               (errcode(ERRCODE_OUT_OF_MEMORY),
-                errmsg("out of memory"),
-                errdetail("Failed on request of size %zu in memory context \"%s\".",
-                          size, context->name)));
-   }
+   ret = context->methods->alloc(context, size, 0);
 
    VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
@@ -1271,24 +1258,11 @@ palloc_extended(Size size, int flags)
    Assert(MemoryContextIsValid(context));
    AssertNotInCriticalSection(context);
 
-   if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) :
-         AllocSizeIsValid(size)))
-       elog(ERROR, "invalid memory alloc request size %zu", size);
-
    context->isReset = false;
 
-   ret = context->methods->alloc(context, size);
+   ret = context->methods->alloc(context, size, flags);
    if (unlikely(ret == NULL))
    {
-       if ((flags & MCXT_ALLOC_NO_OOM) == 0)
-       {
-           MemoryContextStats(TopMemoryContext);
-           ereport(ERROR,
-                   (errcode(ERRCODE_OUT_OF_MEMORY),
-                    errmsg("out of memory"),
-                    errdetail("Failed on request of size %zu in memory context \"%s\".",
-                              size, context->name)));
-       }
        return NULL;
    }
 
@@ -1458,26 +1432,22 @@ repalloc(void *pointer, Size size)
 #endif
    void       *ret;
 
-   if (!AllocSizeIsValid(size))
-       elog(ERROR, "invalid memory alloc request size %zu", size);
-
    AssertNotInCriticalSection(context);
 
    /* isReset must be false already */
    Assert(!context->isReset);
 
-   ret = MCXT_METHOD(pointer, realloc) (pointer, size);
-   if (unlikely(ret == NULL))
-   {
-       MemoryContext cxt = GetMemoryChunkContext(pointer);
-
-       MemoryContextStats(TopMemoryContext);
-       ereport(ERROR,
-               (errcode(ERRCODE_OUT_OF_MEMORY),
-                errmsg("out of memory"),
-                errdetail("Failed on request of size %zu in memory context \"%s\".",
-                          size, cxt->name)));
-   }
+   /*
+    * For efficiency reasons, we purposefully offload the handling of
+    * allocation failures to the MemoryContextMethods implementation as this
+    * allows these checks to be performed only when an actual malloc needs to
+    * be done to request more memory from the OS.  Additionally, not having
+    * to execute any instructions after this call allows the compiler to use
+    * the sibling call optimization.  If you're considering adding code after
+    * this call, consider making it the responsibility of the 'realloc'
+    * function instead.
+    */
+   ret = MCXT_METHOD(pointer, realloc) (pointer, size, 0);
 
 #ifdef USE_VALGRIND
    if (method != MCTX_ALIGNED_REDIRECT_ID)
@@ -1500,31 +1470,24 @@ repalloc_extended(void *pointer, Size size, int flags)
 #endif
    void       *ret;
 
-   if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) :
-         AllocSizeIsValid(size)))
-       elog(ERROR, "invalid memory alloc request size %zu", size);
-
    AssertNotInCriticalSection(context);
 
    /* isReset must be false already */
    Assert(!context->isReset);
 
-   ret = MCXT_METHOD(pointer, realloc) (pointer, size);
+   /*
+    * For efficiency reasons, we purposefully offload the handling of
+    * allocation failures to the MemoryContextMethods implementation as this
+    * allows these checks to be performed only when an actual malloc needs to
+    * be done to request more memory from the OS.  Additionally, not having
+    * to execute any instructions after this call allows the compiler to use
+    * the sibling call optimization.  If you're considering adding code after
+    * this call, consider making it the responsibility of the 'realloc'
+    * function instead.
+    */
+   ret = MCXT_METHOD(pointer, realloc) (pointer, size, flags);
    if (unlikely(ret == NULL))
-   {
-       if ((flags & MCXT_ALLOC_NO_OOM) == 0)
-       {
-           MemoryContext cxt = GetMemoryChunkContext(pointer);
-
-           MemoryContextStats(TopMemoryContext);
-           ereport(ERROR,
-                   (errcode(ERRCODE_OUT_OF_MEMORY),
-                    errmsg("out of memory"),
-                    errdetail("Failed on request of size %zu in memory context \"%s\".",
-                              size, cxt->name)));
-       }
        return NULL;
-   }
 
    VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
 
@@ -1565,21 +1528,19 @@ MemoryContextAllocHuge(MemoryContext context, Size size)
    Assert(MemoryContextIsValid(context));
    AssertNotInCriticalSection(context);
 
-   if (!AllocHugeSizeIsValid(size))
-       elog(ERROR, "invalid memory alloc request size %zu", size);
-
    context->isReset = false;
 
-   ret = context->methods->alloc(context, size);
-   if (unlikely(ret == NULL))
-   {
-       MemoryContextStats(TopMemoryContext);
-       ereport(ERROR,
-               (errcode(ERRCODE_OUT_OF_MEMORY),
-                errmsg("out of memory"),
-                errdetail("Failed on request of size %zu in memory context \"%s\".",
-                          size, context->name)));
-   }
+   /*
+    * For efficiency reasons, we purposefully offload the handling of
+    * allocation failures to the MemoryContextMethods implementation as this
+    * allows these checks to be performed only when an actual malloc needs to
+    * be done to request more memory from the OS.  Additionally, not having
+    * to execute any instructions after this call allows the compiler to use
+    * the sibling call optimization.  If you're considering adding code after
+    * this call, consider making it the responsibility of the 'alloc'
+    * function instead.
+    */
+   ret = context->methods->alloc(context, size, MCXT_ALLOC_HUGE);
 
    VALGRIND_MEMPOOL_ALLOC(context, ret, size);
 
index b8f00cfc7cc9bb52da5b7b5233e59edb80b7919d..bc91446cb3a935e436ebb0185580b5cf00db5cdd 100644 (file)
@@ -496,7 +496,7 @@ SlabDelete(MemoryContext context)
  *     request could not be completed; memory is added to the slab.
  */
 void *
-SlabAlloc(MemoryContext context, Size size)
+SlabAlloc(MemoryContext context, Size size, int flags)
 {
    SlabContext *slab = (SlabContext *) context;
    SlabBlock  *block;
@@ -508,7 +508,10 @@ SlabAlloc(MemoryContext context, Size size)
    Assert(slab->curBlocklistIndex >= 0);
    Assert(slab->curBlocklistIndex <= SlabBlocklistIndex(slab, slab->chunksPerBlock));
 
-   /* make sure we only allow correct request size */
+   /*
+    * Make sure we only allow correct request size.  This doubles as the
+    * MemoryContextCheckSize check.
+    */
    if (unlikely(size != slab->chunkSize))
        elog(ERROR, "unexpected alloc chunk size %zu (expected %u)",
             size, slab->chunkSize);
@@ -546,7 +549,7 @@ SlabAlloc(MemoryContext context, Size size)
            block = (SlabBlock *) malloc(slab->blockSize);
 
            if (unlikely(block == NULL))
-               return NULL;
+               return MemoryContextAllocationFailure(context, size, flags);
 
            block->slab = slab;
            context->mem_allocated += slab->blockSize;
@@ -770,7 +773,7 @@ SlabFree(void *pointer)
  * realloc is usually used to enlarge the chunk.
  */
 void *
-SlabRealloc(void *pointer, Size size)
+SlabRealloc(void *pointer, Size size, int flags)
 {
    MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
    SlabBlock  *block;
index a48f7e5a18749e0555f5ad71e91e7b6c2babe911..edc0257f36d4dae7da545f74a48b27eb46c25618 100644 (file)
@@ -57,20 +57,58 @@ typedef void (*MemoryStatsPrintFunc) (MemoryContext context, void *passthru,
 
 typedef struct MemoryContextMethods
 {
-   void       *(*alloc) (MemoryContext context, Size size);
+   /*
+    * Function to handle memory allocation requests of 'size' to allocate
+    * memory into the given 'context'.  The function must handle flags
+    * MCXT_ALLOC_HUGE and MCXT_ALLOC_NO_OOM.  MCXT_ALLOC_ZERO is handled by
+    * the calling function.
+    */
+   void       *(*alloc) (MemoryContext context, Size size, int flags);
+
    /* call this free_p in case someone #define's free() */
    void        (*free_p) (void *pointer);
-   void       *(*realloc) (void *pointer, Size size);
+
+   /*
+    * Function to handle a size change request for an existing allocation.
+    * The implementation must handle flags MCXT_ALLOC_HUGE and
+    * MCXT_ALLOC_NO_OOM.  MCXT_ALLOC_ZERO is handled by the calling function.
+    */
+   void       *(*realloc) (void *pointer, Size size, int flags);
+
+   /*
+    * Invalidate all previous allocations in the given memory context and
+    * prepare the context for a new set of allocations.  Implementations may
+    * optionally free() excess memory back to the OS during this time.
+    */
    void        (*reset) (MemoryContext context);
+
+   /* Free all memory consumed by the given MemoryContext. */
    void        (*delete_context) (MemoryContext context);
+
+   /* Return the MemoryContext that the given pointer belongs to. */
    MemoryContext (*get_chunk_context) (void *pointer);
+
+   /*
+    * Return the number of bytes consumed by the given pointer within its
+    * memory context, including the overhead of alignment and chunk headers.
+    */
    Size        (*get_chunk_space) (void *pointer);
+
+   /*
+    * Return true if the given MemoryContext has not had any allocations
+    * since it was created or last reset.
+    */
    bool        (*is_empty) (MemoryContext context);
    void        (*stats) (MemoryContext context,
                          MemoryStatsPrintFunc printfunc, void *passthru,
                          MemoryContextCounters *totals,
                          bool print_to_stderr);
 #ifdef MEMORY_CONTEXT_CHECKING
+
+   /*
+    * Perform validation checks on the given context and raise any discovered
+    * anomalies as WARNINGs.
+    */
    void        (*check) (MemoryContext context);
 #endif
 } MemoryContextMethods;
index e0c4f3d5af824ddccab2f735e2994d2009e944fc..ad1048fd829a42c78a991d6438205ab296abdf94 100644 (file)
@@ -19,9 +19,9 @@
 #include "utils/memutils.h"
 
 /* These functions implement the MemoryContext API for AllocSet context. */
-extern void *AllocSetAlloc(MemoryContext context, Size size);
+extern void *AllocSetAlloc(MemoryContext context, Size size, int flags);
 extern void AllocSetFree(void *pointer);
-extern void *AllocSetRealloc(void *pointer, Size size);
+extern void *AllocSetRealloc(void *pointer, Size size, int flags);
 extern void AllocSetReset(MemoryContext context);
 extern void AllocSetDelete(MemoryContext context);
 extern MemoryContext AllocSetGetChunkContext(void *pointer);
@@ -36,9 +36,9 @@ extern void AllocSetCheck(MemoryContext context);
 #endif
 
 /* These functions implement the MemoryContext API for Generation context. */
-extern void *GenerationAlloc(MemoryContext context, Size size);
+extern void *GenerationAlloc(MemoryContext context, Size size, int flags);
 extern void GenerationFree(void *pointer);
-extern void *GenerationRealloc(void *pointer, Size size);
+extern void *GenerationRealloc(void *pointer, Size size, int flags);
 extern void GenerationReset(MemoryContext context);
 extern void GenerationDelete(MemoryContext context);
 extern MemoryContext GenerationGetChunkContext(void *pointer);
@@ -54,9 +54,9 @@ extern void GenerationCheck(MemoryContext context);
 
 
 /* These functions implement the MemoryContext API for Slab context. */
-extern void *SlabAlloc(MemoryContext context, Size size);
+extern void *SlabAlloc(MemoryContext context, Size size, int flags);
 extern void SlabFree(void *pointer);
-extern void *SlabRealloc(void *pointer, Size size);
+extern void *SlabRealloc(void *pointer, Size size, int flags);
 extern void SlabReset(MemoryContext context);
 extern void SlabDelete(MemoryContext context);
 extern MemoryContext SlabGetChunkContext(void *pointer);
@@ -75,7 +75,7 @@ extern void SlabCheck(MemoryContext context);
  * part of a fully-fledged MemoryContext type.
  */
 extern void AlignedAllocFree(void *pointer);
-extern void *AlignedAllocRealloc(void *pointer, Size size);
+extern void *AlignedAllocRealloc(void *pointer, Size size, int flags);
 extern MemoryContext AlignedAllocGetChunkContext(void *pointer);
 extern Size AlignedAllocGetChunkSpace(void *pointer);
 
@@ -133,4 +133,20 @@ extern void MemoryContextCreate(MemoryContext node,
                                MemoryContext parent,
                                const char *name);
 
+extern void *MemoryContextAllocationFailure(MemoryContext context, Size size,
+                                           int flags);
+
+extern void MemoryContextSizeFailure(MemoryContext context, Size size,
+                                    int flags) pg_attribute_noreturn();
+
+static inline void
+MemoryContextCheckSize(MemoryContext context, Size size, int flags)
+{
+   if (unlikely(!AllocSizeIsValid(size)))
+   {
+       if (!(flags & MCXT_ALLOC_HUGE) || !AllocHugeSizeIsValid(size))
+           MemoryContextSizeFailure(context, size, flags);
+   }
+}
+
 #endif                         /* MEMUTILS_INTERNAL_H */