#include "postgres.h"
#include "lib/ilist.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"
#include "utils/memutils_internal.h"
#include "utils/memutils_memorychunk.h"
#define Slab_BLOCKHDRSZ MAXALIGN(sizeof(SlabBlock))
#ifdef MEMORY_CONTEXT_CHECKING
#define Slab_CONTEXT_HDRSZ(chunksPerBlock) \
(sizeof(SlabContext) + ((chunksPerBlock) * sizeof(bool)))
#else
#define Slab_CONTEXT_HDRSZ(chunksPerBlock) sizeof(SlabContext)
#endif
#define SLAB_BLOCKLIST_COUNT 3
#define SLAB_MAXIMUM_EMPTY_BLOCKS 10
typedef struct SlabContext
{
MemoryContextData header;
uint32 chunkSize;
uint32 fullChunkSize;
uint32 blockSize;
int32 chunksPerBlock;
int32 curBlocklistIndex;
#ifdef MEMORY_CONTEXT_CHECKING
bool *isChunkFree;
#endif
int32 blocklist_shift;
dclist_head emptyblocks;
dlist_head blocklist[SLAB_BLOCKLIST_COUNT];
} SlabContext;
typedef struct SlabBlock
{
SlabContext *slab;
int32 nfree;
int32 nunused;
MemoryChunk *freehead;
MemoryChunk *unused;
dlist_node node;
} SlabBlock;
#define Slab_CHUNKHDRSZ sizeof(MemoryChunk)
#define SlabChunkGetPointer(chk) \
((void *) (((char *) (chk)) + sizeof(MemoryChunk)))
#define SlabBlockGetChunk(slab, block, n) \
((MemoryChunk *) ((char *) (block) + Slab_BLOCKHDRSZ \
+ ((n) * (slab)->fullChunkSize)))
#if defined(MEMORY_CONTEXT_CHECKING) || defined(USE_ASSERT_CHECKING)
#define SlabChunkIndex(slab, block, chunk) \
(((char *) (chunk) - (char *) SlabBlockGetChunk(slab, block, 0)) / \
(slab)->fullChunkSize)
#define SlabChunkMod(slab, block, chunk) \
(((char *) (chunk) - (char *) SlabBlockGetChunk(slab, block, 0)) % \
(slab)->fullChunkSize)
#endif
#define SlabIsValid(set) (PointerIsValid(set) && IsA(set, SlabContext))
#define SlabBlockIsValid(block) \
(PointerIsValid(block) && SlabIsValid((block)->slab))
static inline int32
SlabBlocklistIndex(SlabContext *slab, int nfree)
{
int32 index;
int32 blocklist_shift = slab->blocklist_shift;
Assert(nfree >= 0 && nfree <= slab->chunksPerBlock);
index = -((-nfree) >> blocklist_shift);
if (nfree == 0)
Assert(index == 0);
else
Assert(index >= 1 && index < SLAB_BLOCKLIST_COUNT);
return index;
}
static int32
SlabFindNextBlockListIndex(SlabContext *slab)
{
for (int i = 1; i < SLAB_BLOCKLIST_COUNT; i++)
{
if (!dlist_is_empty(&slab->blocklist[i]))
return i;
}
return 0;
}
static inline MemoryChunk *
SlabGetNextFreeChunk(SlabContext *slab, SlabBlock *block)
{
MemoryChunk *chunk;
Assert(block->nfree > 0);
if (block->freehead != NULL)
{
chunk = block->freehead;
VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(chunk), sizeof(MemoryChunk *));
block->freehead = *(MemoryChunk **) SlabChunkGetPointer(chunk);
Assert(block->freehead == NULL ||
(block->freehead >= SlabBlockGetChunk(slab, block, 0) &&
block->freehead <= SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1) &&
SlabChunkMod(slab, block, block->freehead) == 0));
}
else
{
Assert(block->nunused > 0);
chunk = block->unused;
block->unused = (MemoryChunk *) (((char *) block->unused) + slab->fullChunkSize);
block->nunused--;
}
block->nfree--;
return chunk;
}
#ifdef MEMORY_CONTEXT_CHECKING
#else
#endif
#ifdef MEMORY_CONTEXT_CHECKING
#endif
void
SlabReset(MemoryContext context)
{
SlabContext *slab = (SlabContext *) context;
dlist_mutable_iter miter;
int i;
Assert(SlabIsValid(slab));
#ifdef MEMORY_CONTEXT_CHECKING
SlabCheck(context);
#endif
dclist_foreach_modify(miter, &slab->emptyblocks)
{
SlabBlock *block = dlist_container(SlabBlock, node, miter.cur);
dclist_delete_from(&slab->emptyblocks, miter.cur);
#ifdef CLOBBER_FREED_MEMORY
wipe_mem(block, slab->blockSize);
#endif
free(block);
context->mem_allocated -= slab->blockSize;
}
for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
{
dlist_foreach_modify(miter, &slab->blocklist[i])
{
SlabBlock *block = dlist_container(SlabBlock, node, miter.cur);
dlist_delete(miter.cur);
#ifdef CLOBBER_FREED_MEMORY
wipe_mem(block, slab->blockSize);
#endif
free(block);
context->mem_allocated -= slab->blockSize;
}
}
slab->curBlocklistIndex = 0;
Assert(context->mem_allocated == 0);
}
void
SlabDelete(MemoryContext context)
{
SlabReset(context);
free(context);
}
static inline void *
SlabAllocSetupNewChunk(MemoryContext context, SlabBlock *block,
MemoryChunk *chunk, Size size)
{
SlabContext *slab = (SlabContext *) context;
Assert(chunk >= SlabBlockGetChunk(slab, block, 0));
Assert(chunk <= SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1));
Assert(SlabChunkMod(slab, block, chunk) == 0);
VALGRIND_MAKE_MEM_UNDEFINED(chunk, Slab_CHUNKHDRSZ);
MemoryChunkSetHdrMask(chunk, block, MAXALIGN(slab->chunkSize), MCTX_SLAB_ID);
#ifdef MEMORY_CONTEXT_CHECKING
Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ));
set_sentinel(MemoryChunkGetPointer(chunk), size);
VALGRIND_MAKE_MEM_NOACCESS(((char *) chunk) + Slab_CHUNKHDRSZ +
slab->chunkSize,
slab->fullChunkSize -
(slab->chunkSize + Slab_CHUNKHDRSZ));
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
#endif
VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
return MemoryChunkGetPointer(chunk);
}
pg_noinline
static void *
SlabAllocFromNewBlock(MemoryContext context, Size size, int flags)
{
SlabContext *slab = (SlabContext *) context;
SlabBlock *block;
MemoryChunk *chunk;
dlist_head *blocklist;
int blocklist_idx;
if (dclist_count(&slab->emptyblocks) > 0)
{
dlist_node *node = dclist_pop_head_node(&slab->emptyblocks);
block = dlist_container(SlabBlock, node, node);
Assert(block->nfree == slab->chunksPerBlock);
chunk = SlabGetNextFreeChunk(slab, block);
}
else
{
block = (SlabBlock *) malloc(slab->blockSize);
if (unlikely(block == NULL))
return MemoryContextAllocationFailure(context, size, flags);
block->slab = slab;
context->mem_allocated += slab->blockSize;
chunk = SlabBlockGetChunk(slab, block, 0);
block->nfree = slab->chunksPerBlock - 1;
block->unused = SlabBlockGetChunk(slab, block, 1);
block->freehead = NULL;
block->nunused = slab->chunksPerBlock - 1;
}
blocklist_idx = SlabBlocklistIndex(slab, block->nfree);
blocklist = &slab->blocklist[blocklist_idx];
Assert(dlist_is_empty(blocklist));
dlist_push_head(blocklist, &block->node);
slab->curBlocklistIndex = blocklist_idx;
return SlabAllocSetupNewChunk(context, block, chunk, size);
}
pg_noinline
static void
pg_attribute_noreturn()
SlabAllocInvalidSize(MemoryContext context, Size size)
{
SlabContext *slab = (SlabContext *) context;
elog(ERROR, "unexpected alloc chunk size %zu (expected %u)", size,
slab->chunkSize);
}
void *
SlabAlloc(MemoryContext context, Size size, int flags)
{
SlabContext *slab = (SlabContext *) context;
SlabBlock *block;
MemoryChunk *chunk;
Assert(SlabIsValid(slab));
Assert(slab->curBlocklistIndex >= 0);
Assert(slab->curBlocklistIndex <= SlabBlocklistIndex(slab, slab->chunksPerBlock));
if (unlikely(size != slab->chunkSize))
SlabAllocInvalidSize(context, size);
if (unlikely(slab->curBlocklistIndex == 0))
{
return SlabAllocFromNewBlock(context, size, flags);
}
else
{
dlist_head *blocklist = &slab->blocklist[slab->curBlocklistIndex];
int new_blocklist_idx;
Assert(!dlist_is_empty(blocklist));
block = dlist_head_element(SlabBlock, node, blocklist);
Assert(block != NULL);
Assert(slab->curBlocklistIndex == SlabBlocklistIndex(slab, block->nfree));
Assert(block->nfree > 0);
chunk = SlabGetNextFreeChunk(slab, block);
new_blocklist_idx = SlabBlocklistIndex(slab, block->nfree);
if (unlikely(slab->curBlocklistIndex != new_blocklist_idx))
{
dlist_delete_from(blocklist, &block->node);
dlist_push_head(&slab->blocklist[new_blocklist_idx], &block->node);
if (dlist_is_empty(blocklist))
slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab);
}
}
return SlabAllocSetupNewChunk(context, block, chunk, size);
}
void
SlabFree(void *pointer)
{
MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
SlabBlock *block;
SlabContext *slab;
int curBlocklistIdx;
int newBlocklistIdx;
VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
block = MemoryChunkGetBlock(chunk);
Assert(SlabBlockIsValid(block));
slab = block->slab;
#ifdef MEMORY_CONTEXT_CHECKING
Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ));
if (!sentinel_ok(pointer, slab->chunkSize))
elog(WARNING, "detected write past chunk end in %s %p",
slab->header.name, chunk);
#endif
*(MemoryChunk **) pointer = block->freehead;
block->freehead = chunk;
block->nfree++;
Assert(block->nfree > 0);
Assert(block->nfree <= slab->chunksPerBlock);
#ifdef CLOBBER_FREED_MEMORY
wipe_mem((char *) pointer + sizeof(MemoryChunk *),
slab->chunkSize - sizeof(MemoryChunk *));
#endif
curBlocklistIdx = SlabBlocklistIndex(slab, block->nfree - 1);
newBlocklistIdx = SlabBlocklistIndex(slab, block->nfree);
if (unlikely(curBlocklistIdx != newBlocklistIdx))
{
dlist_delete_from(&slab->blocklist[curBlocklistIdx], &block->node);
dlist_push_head(&slab->blocklist[newBlocklistIdx], &block->node);
if (slab->curBlocklistIndex >= curBlocklistIdx)
{
slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab);
Assert(slab->curBlocklistIndex > 0);
}
}
if (unlikely(block->nfree == slab->chunksPerBlock))
{
dlist_delete_from(&slab->blocklist[newBlocklistIdx], &block->node);
if (dclist_count(&slab->emptyblocks) < SLAB_MAXIMUM_EMPTY_BLOCKS)
dclist_push_head(&slab->emptyblocks, &block->node);
else
{
#ifdef CLOBBER_FREED_MEMORY
wipe_mem(block, slab->blockSize);
#endif
free(block);
slab->header.mem_allocated -= slab->blockSize;
}
if (slab->curBlocklistIndex == newBlocklistIdx &&
dlist_is_empty(&slab->blocklist[newBlocklistIdx]))
slab->curBlocklistIndex = SlabFindNextBlockListIndex(slab);
}
}
void *
SlabRealloc(void *pointer, Size size, int flags)
{
MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
SlabBlock *block;
SlabContext *slab;
VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
block = MemoryChunkGetBlock(chunk);
VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
if (!SlabBlockIsValid(block))
elog(ERROR, "could not find block containing chunk %p", chunk);
slab = block->slab;
if (size == slab->chunkSize)
return pointer;
elog(ERROR, "slab allocator does not support realloc()");
return NULL;
}
MemoryContext
SlabGetChunkContext(void *pointer)
{
MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
SlabBlock *block;
VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
block = MemoryChunkGetBlock(chunk);
VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
Assert(SlabBlockIsValid(block));
return &block->slab->header;
}
Size
SlabGetChunkSpace(void *pointer)
{
MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
SlabBlock *block;
SlabContext *slab;
VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
block = MemoryChunkGetBlock(chunk);
VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
Assert(SlabBlockIsValid(block));
slab = block->slab;
return slab->fullChunkSize;
}
bool
SlabIsEmpty(MemoryContext context)
{
Assert(SlabIsValid((SlabContext *) context));
return (context->mem_allocated == 0);
}
void
SlabStats(MemoryContext context,
MemoryStatsPrintFunc printfunc, void *passthru,
MemoryContextCounters *totals,
bool print_to_stderr)
{
SlabContext *slab = (SlabContext *) context;
Size nblocks = 0;
Size freechunks = 0;
Size totalspace;
Size freespace = 0;
int i;
Assert(SlabIsValid(slab));
totalspace = Slab_CONTEXT_HDRSZ(slab->chunksPerBlock);
totalspace += dclist_count(&slab->emptyblocks) * slab->blockSize;
for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
{
dlist_iter iter;
dlist_foreach(iter, &slab->blocklist[i])
{
SlabBlock *block = dlist_container(SlabBlock, node, iter.cur);
nblocks++;
totalspace += slab->blockSize;
freespace += slab->fullChunkSize * block->nfree;
freechunks += block->nfree;
}
}
if (printfunc)
{
char stats_string[200];
snprintf(stats_string, sizeof(stats_string),
"%zu total in %zu blocks; %u empty blocks; %zu free (%zu chunks); %zu used",
totalspace, nblocks, dclist_count(&slab->emptyblocks),
freespace, freechunks, totalspace - freespace);
printfunc(context, passthru, stats_string, print_to_stderr);
}
if (totals)
{
totals->nblocks += nblocks;
totals->freechunks += freechunks;
totals->totalspace += totalspace;
totals->freespace += freespace;
}
}
#ifdef MEMORY_CONTEXT_CHECKING
void
SlabCheck(MemoryContext context)
{
SlabContext *slab = (SlabContext *) context;
int i;
int nblocks = 0;
const char *name = slab->header.name;
dlist_iter iter;
Assert(SlabIsValid(slab));
Assert(slab->chunksPerBlock > 0);
dclist_foreach(iter, &slab->emptyblocks)
{
SlabBlock *block = dlist_container(SlabBlock, node, iter.cur);
if (block->nfree != slab->chunksPerBlock)
elog(WARNING, "problem in slab %s: empty block %p should have %d free chunks but has %d chunks free",
name, block, slab->chunksPerBlock, block->nfree);
}
for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
{
int j,
nfree;
dlist_foreach(iter, &slab->blocklist[i])
{
SlabBlock *block = dlist_container(SlabBlock, node, iter.cur);
MemoryChunk *cur_chunk;
if (SlabBlocklistIndex(slab, block->nfree) != i)
elog(WARNING, "problem in slab %s: block %p is on blocklist %d but should be on blocklist %d",
name, block, i, SlabBlocklistIndex(slab, block->nfree));
if (block->nfree >= slab->chunksPerBlock)
elog(WARNING, "problem in slab %s: empty block %p incorrectly stored on blocklist element %d",
name, block, i);
if (block->slab != slab)
elog(WARNING, "problem in slab %s: bogus slab link in block %p",
name, block);
memset(slab->isChunkFree, 0, (slab->chunksPerBlock * sizeof(bool)));
nfree = 0;
cur_chunk = block->freehead;
while (cur_chunk != NULL)
{
int chunkidx = SlabChunkIndex(slab, block, cur_chunk);
if (cur_chunk < SlabBlockGetChunk(slab, block, 0) ||
cur_chunk > SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1) ||
SlabChunkMod(slab, block, cur_chunk) != 0)
elog(WARNING, "problem in slab %s: bogus free list link %p in block %p",
name, cur_chunk, block);
nfree++;
slab->isChunkFree[chunkidx] = true;
VALGRIND_MAKE_MEM_DEFINED(MemoryChunkGetPointer(cur_chunk), sizeof(MemoryChunk *));
cur_chunk = *(MemoryChunk **) SlabChunkGetPointer(cur_chunk);
}
if (SlabBlockGetChunk(slab, block, slab->chunksPerBlock - block->nunused) !=
block->unused)
elog(WARNING, "problem in slab %s: mismatch detected between nunused chunks and unused pointer in block %p",
name, block);
cur_chunk = block->unused;
for (j = 0; j < block->nunused; j++)
{
int chunkidx = SlabChunkIndex(slab, block, cur_chunk);
nfree++;
if (chunkidx < slab->chunksPerBlock)
slab->isChunkFree[chunkidx] = true;
cur_chunk = (MemoryChunk *) (((char *) cur_chunk) + slab->fullChunkSize);
}
for (j = 0; j < slab->chunksPerBlock; j++)
{
if (!slab->isChunkFree[j])
{
MemoryChunk *chunk = SlabBlockGetChunk(slab, block, j);
SlabBlock *chunkblock;
VALGRIND_MAKE_MEM_DEFINED(chunk, Slab_CHUNKHDRSZ);
chunkblock = (SlabBlock *) MemoryChunkGetBlock(chunk);
VALGRIND_MAKE_MEM_NOACCESS(chunk, Slab_CHUNKHDRSZ);
if (chunkblock != block)
elog(WARNING, "problem in slab %s: bogus block link in block %p, chunk %p",
name, block, chunk);
Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ));
if (!sentinel_ok(chunk, Slab_CHUNKHDRSZ + slab->chunkSize))
elog(WARNING, "problem in slab %s: detected write past chunk end in block %p, chunk %p",
name, block, chunk);
}
}
if (nfree != block->nfree)
elog(WARNING, "problem in slab %s: nfree in block %p is %d but %d chunk were found as free",
name, block, block->nfree, nfree);
nblocks++;
}
}
nblocks += dclist_count(&slab->emptyblocks);
Assert(nblocks * slab->blockSize == context->mem_allocated);
}
#endif