sources/minipool.c (54 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "cql.h"
#include "minipool.h"
#include <stdlib.h>
#define MAX(a,b) ((a >b ) ? a : b)
// This is the stupidest pool allocator ever, it's only useful for cases where everything lives
// until the end. Currently it's used to hold ast nodes and duplicated strings. It may hold
// more. The old CQL strategy was to just let exit clean up everything since we were only
// an executable and we can't free the tree until we're exiting anyway. However there is some
// desire to use CQL in library form now which means it has to be able to do a compile and
// then end up clean. To help with this we make these ultra-dumb pools that simply keep
// the allocated items together. This means that we don't have to do zillions of seperate free
// calls when we're exiting. It also helps with locality and fragmentation in the client.
// It's dumb as rocks.
// Make a pool node, set it's size to MINIBLOCK
cql_noexport void minipool_open(minipool **pool) {
*pool = _new(minipool);
(*pool)->bytes = malloc(MINIBLOCK);
(*pool)->current = (*pool)->bytes;
(*pool)->available = MINIBLOCK;
(*pool)->next = NULL;
}
// Give back all the memory in the pool and nil out the pool pointer
// To accomplish this all we need to to is walk the chain of blocks
// freeing the bytes from each block as well as the minipool object.
cql_noexport void minipool_close(minipool **head) {
minipool *pool = *head;
while (pool) {
minipool *next = pool->next;
free(pool->bytes);
free(pool);
pool = next;
}
*head = NULL;
}
// Get needed memory; if the memory is not available then
// we allocate a new block and thread it into the list. Note
// that this is different than the other CQL helper bytebuf.
// This allocation never moves bytes or copies bytes. Once
// you get memory it is valid until you close the buffer.
// Bytebuf cannot hold pointers because the structure moves when it
// grows and it is one continuous allocation so it's only for byte streams.
// Minipool doesn't move objects around so pointers remain valid.
cql_noexport void *minipool_alloc(minipool *pool, uint32_t needed) {
void *result;
// for alignment, all allocs will be on a 8 byte boundary
needed += 7;
needed &= u32_not(7);
if (needed > pool->available) {
// Make a copy of the most recent head node and link to it
// we have to do this because the head of the pool never
// changes once it's created. Note that none of the byte
// buffers are copied, only the minipool struct. Once
// we've copied it, we re-initialize the minipool to a size
// that is at least big enough for the next allocation.
minipool *old = malloc(sizeof(minipool));
*old = *pool;
uint32_t blocksize = MAX(needed, MINIBLOCK);
pool->bytes = malloc(blocksize);
pool->current = pool->bytes;
pool->available = blocksize;
pool->next = old;
}
// For sure safe to get the memory now, so get it.π
result = pool->current;
pool->current += needed;
pool->available -= needed;
return result;
}
static lazy_free *_Nullable lazy_frees;
cql_noexport void add_lazy_free(lazy_free *p) {
p->next = lazy_frees;
lazy_frees = p;
}
cql_noexport void run_lazy_frees() {
lazy_free *head = lazy_frees;
while (head) {
lazy_free *next = head->next;
head->teardown(head->context);
free(head);
head = next;
}
lazy_frees = NULL;
}