1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-22 12:22:45 +03:00

Adjust memory allocation functions to allow sibling calls

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
This commit is contained in:
David Rowley
2024-02-27 16:39:42 +13:00
parent 17a3f79f81
commit 743112a2e9
7 changed files with 196 additions and 166 deletions

View 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;

View 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);
}
/*

View 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);
}
/*

View 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);

View 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;