aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/restricted/aws/aws-c-common/source/allocator_sba.c
diff options
context:
space:
mode:
authororivej <orivej@yandex-team.ru>2022-02-10 16:44:49 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 16:44:49 +0300
commit718c552901d703c502ccbefdfc3c9028d608b947 (patch)
tree46534a98bbefcd7b1f3faa5b52c138ab27db75b7 /contrib/restricted/aws/aws-c-common/source/allocator_sba.c
parente9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (diff)
downloadydb-718c552901d703c502ccbefdfc3c9028d608b947.tar.gz
Restoring authorship annotation for <orivej@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/restricted/aws/aws-c-common/source/allocator_sba.c')
-rw-r--r--contrib/restricted/aws/aws-c-common/source/allocator_sba.c840
1 files changed, 420 insertions, 420 deletions
diff --git a/contrib/restricted/aws/aws-c-common/source/allocator_sba.c b/contrib/restricted/aws/aws-c-common/source/allocator_sba.c
index d30c67c37e..127f26da10 100644
--- a/contrib/restricted/aws/aws-c-common/source/allocator_sba.c
+++ b/contrib/restricted/aws/aws-c-common/source/allocator_sba.c
@@ -1,420 +1,420 @@
-/**
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0.
- */
-
-#include <aws/common/allocator.h>
-#include <aws/common/array_list.h>
-#include <aws/common/assert.h>
-#include <aws/common/mutex.h>
-
-/*
- * Small Block Allocator
- * This is a fairly standard approach, the idea is to always allocate aligned pages of memory so that for
- * any address you can round to the nearest page boundary to find the bookkeeping data. The idea is to reduce
- * overhead per alloc and greatly improve runtime speed by doing as little actual allocation work as possible,
- * preferring instead to re-use (hopefully still cached) chunks in FIFO order, or chunking up a page if there's
- * no free chunks. When all chunks in a page are freed, the page is returned to the OS.
- *
- * The allocator itself is simply an array of bins, each representing a power of 2 size from 32 - N (512 tends to be
- * a good upper bound). Thread safety is guaranteed by a mutex per bin, and locks are only necessary around the
- * lowest level alloc and free operations.
- *
- * Note: this allocator gets its internal memory for data structures from the parent allocator, but does not
- * use the parent to allocate pages. Pages are allocated directly from the OS-specific aligned malloc implementation,
- * which allows the OS to do address re-mapping for us instead of over-allocating to fulfill alignment.
- */
-
-#ifdef _WIN32
-# include <malloc.h>
-#elif __linux__ || __APPLE__
-# include <stdlib.h>
-#endif
-
-#if !defined(AWS_SBA_PAGE_SIZE)
-# if defined(PAGE_SIZE)
-# define AWS_SBA_PAGE_SIZE ((uintptr_t)(PAGE_SIZE))
-# else
-# define AWS_SBA_PAGE_SIZE ((uintptr_t)(4096))
-# endif
-#endif
-
-#define AWS_SBA_PAGE_MASK ((uintptr_t) ~(AWS_SBA_PAGE_SIZE - 1))
-#define AWS_SBA_TAG_VALUE 0x736f6d6570736575ULL
-
-/* list of sizes of bins, must be powers of 2, and less than AWS_SBA_PAGE_SIZE * 0.5 */
-#define AWS_SBA_BIN_COUNT 5
-static const size_t s_bin_sizes[AWS_SBA_BIN_COUNT] = {32, 64, 128, 256, 512};
-static const size_t s_max_bin_size = 512;
-
-struct sba_bin {
- size_t size; /* size of allocs in this bin */
- struct aws_mutex mutex; /* lock protecting this bin */
- uint8_t *page_cursor; /* pointer to working page, currently being chunked from */
- struct aws_array_list active_pages; /* all pages in use by this bin, could be optimized at scale by being a set */
- struct aws_array_list free_chunks; /* free chunks available in this bin */
-};
-
-/* Header stored at the base of each page.
- * As long as this is under 32 bytes, all is well.
- * Above that, there's potentially more waste per page */
-struct page_header {
- uint64_t tag; /* marker to identify/validate pages */
- struct sba_bin *bin; /* bin this page belongs to */
- uint32_t alloc_count; /* number of outstanding allocs from this page */
- uint64_t tag2;
-};
-
-/* This is the impl for the aws_allocator */
-struct small_block_allocator {
- struct aws_allocator *allocator; /* parent allocator, for large allocs */
- struct sba_bin bins[AWS_SBA_BIN_COUNT];
- int (*lock)(struct aws_mutex *);
- int (*unlock)(struct aws_mutex *);
-};
-
-static int s_null_lock(struct aws_mutex *mutex) {
- (void)mutex;
- /* NO OP */
- return 0;
-}
-
-static int s_null_unlock(struct aws_mutex *mutex) {
- (void)mutex;
- /* NO OP */
- return 0;
-}
-
-static int s_mutex_lock(struct aws_mutex *mutex) {
- return aws_mutex_lock(mutex);
-}
-
-static int s_mutex_unlock(struct aws_mutex *mutex) {
- return aws_mutex_unlock(mutex);
-}
-
-static void *s_page_base(const void *addr) {
- /* mask off the address to round it to page alignment */
- uint8_t *page_base = (uint8_t *)(((uintptr_t)addr) & AWS_SBA_PAGE_MASK);
- return page_base;
-}
-
-static void *s_page_bind(void *addr, struct sba_bin *bin) {
- /* insert the header at the base of the page and advance past it */
- struct page_header *page = (struct page_header *)addr;
- page->tag = page->tag2 = AWS_SBA_TAG_VALUE;
- page->bin = bin;
- page->alloc_count = 0;
- return (uint8_t *)addr + sizeof(struct page_header);
-}
-
-/* Wraps OS-specific aligned malloc implementation */
-static void *s_aligned_alloc(size_t size, size_t align) {
-#ifdef _WIN32
- return _aligned_malloc(size, align);
-#else
- void *mem = NULL;
- int return_code = posix_memalign(&mem, align, size);
- if (return_code) {
- aws_raise_error(AWS_ERROR_OOM);
- return NULL;
- }
- return mem;
-#endif
-}
-
-/* wraps OS-specific aligned free implementation */
-static void s_aligned_free(void *addr) {
-#ifdef _WIN32
- _aligned_free(addr);
-#else
- free(addr);
-#endif
-}
-
-/* aws_allocator vtable template */
-static void *s_sba_mem_acquire(struct aws_allocator *allocator, size_t size);
-static void s_sba_mem_release(struct aws_allocator *allocator, void *ptr);
-static void *s_sba_mem_realloc(struct aws_allocator *allocator, void *old_ptr, size_t old_size, size_t new_size);
-static void *s_sba_mem_calloc(struct aws_allocator *allocator, size_t num, size_t size);
-
-static struct aws_allocator s_sba_allocator = {
- .mem_acquire = s_sba_mem_acquire,
- .mem_release = s_sba_mem_release,
- .mem_realloc = s_sba_mem_realloc,
- .mem_calloc = s_sba_mem_calloc,
-};
-
-static int s_sba_init(struct small_block_allocator *sba, struct aws_allocator *allocator, bool multi_threaded) {
- sba->allocator = allocator;
- AWS_ZERO_ARRAY(sba->bins);
- sba->lock = multi_threaded ? s_mutex_lock : s_null_lock;
- sba->unlock = multi_threaded ? s_mutex_unlock : s_null_unlock;
-
- for (unsigned idx = 0; idx < AWS_SBA_BIN_COUNT; ++idx) {
- struct sba_bin *bin = &sba->bins[idx];
- bin->size = s_bin_sizes[idx];
- if (multi_threaded && aws_mutex_init(&bin->mutex)) {
- goto cleanup;
- }
- if (aws_array_list_init_dynamic(&bin->active_pages, sba->allocator, 16, sizeof(void *))) {
- goto cleanup;
- }
- /* start with enough chunks for 1 page */
- if (aws_array_list_init_dynamic(
- &bin->free_chunks, sba->allocator, aws_max_size(AWS_SBA_PAGE_SIZE / bin->size, 16), sizeof(void *))) {
- goto cleanup;
- }
- }
-
- return AWS_OP_SUCCESS;
-
-cleanup:
- for (unsigned idx = 0; idx < AWS_SBA_BIN_COUNT; ++idx) {
- struct sba_bin *bin = &sba->bins[idx];
- aws_mutex_clean_up(&bin->mutex);
- aws_array_list_clean_up(&bin->active_pages);
- aws_array_list_clean_up(&bin->free_chunks);
- }
- return AWS_OP_ERR;
-}
-
-static void s_sba_clean_up(struct small_block_allocator *sba) {
- /* free all known pages, then free the working page */
- for (unsigned idx = 0; idx < AWS_SBA_BIN_COUNT; ++idx) {
- struct sba_bin *bin = &sba->bins[idx];
- for (size_t page_idx = 0; page_idx < bin->active_pages.length; ++page_idx) {
- void *page_addr = NULL;
- aws_array_list_get_at(&bin->active_pages, &page_addr, page_idx);
- s_aligned_free(page_addr);
- }
- if (bin->page_cursor) {
- void *page_addr = s_page_base(bin->page_cursor);
- s_aligned_free(page_addr);
- }
-
- aws_array_list_clean_up(&bin->active_pages);
- aws_array_list_clean_up(&bin->free_chunks);
- aws_mutex_clean_up(&bin->mutex);
- }
-}
-
-struct aws_allocator *aws_small_block_allocator_new(struct aws_allocator *allocator, bool multi_threaded) {
- struct small_block_allocator *sba = NULL;
- struct aws_allocator *sba_allocator = NULL;
- aws_mem_acquire_many(
- allocator, 2, &sba, sizeof(struct small_block_allocator), &sba_allocator, sizeof(struct aws_allocator));
-
- if (!sba || !sba_allocator) {
- return NULL;
- }
-
- AWS_ZERO_STRUCT(*sba);
- AWS_ZERO_STRUCT(*sba_allocator);
-
- /* copy the template vtable */
- *sba_allocator = s_sba_allocator;
- sba_allocator->impl = sba;
-
- if (s_sba_init(sba, allocator, multi_threaded)) {
- s_sba_clean_up(sba);
- aws_mem_release(allocator, sba);
- return NULL;
- }
- return sba_allocator;
-}
-
-void aws_small_block_allocator_destroy(struct aws_allocator *sba_allocator) {
- if (!sba_allocator) {
- return;
- }
- struct small_block_allocator *sba = sba_allocator->impl;
- if (!sba) {
- return;
- }
-
- struct aws_allocator *allocator = sba->allocator;
- s_sba_clean_up(sba);
- aws_mem_release(allocator, sba);
-}
-
-/* NOTE: Expects the mutex to be held by the caller */
-static void *s_sba_alloc_from_bin(struct sba_bin *bin) {
- /* check the free list, hand chunks out in FIFO order */
- if (bin->free_chunks.length > 0) {
- void *chunk = NULL;
- if (aws_array_list_back(&bin->free_chunks, &chunk)) {
- return NULL;
- }
- if (aws_array_list_pop_back(&bin->free_chunks)) {
- return NULL;
- }
-
- AWS_ASSERT(chunk);
- struct page_header *page = s_page_base(chunk);
- page->alloc_count++;
- return chunk;
- }
-
- /* If there is a working page to chunk from, use it */
- if (bin->page_cursor) {
- struct page_header *page = s_page_base(bin->page_cursor);
- AWS_ASSERT(page);
- size_t space_left = AWS_SBA_PAGE_SIZE - (bin->page_cursor - (uint8_t *)page);
- if (space_left >= bin->size) {
- void *chunk = bin->page_cursor;
- page->alloc_count++;
- bin->page_cursor += bin->size;
- space_left -= bin->size;
- if (space_left < bin->size) {
- aws_array_list_push_back(&bin->active_pages, &page);
- bin->page_cursor = NULL;
- }
- return chunk;
- }
- }
-
- /* Nothing free to use, allocate a page and restart */
- uint8_t *new_page = s_aligned_alloc(AWS_SBA_PAGE_SIZE, AWS_SBA_PAGE_SIZE);
- new_page = s_page_bind(new_page, bin);
- bin->page_cursor = new_page;
- return s_sba_alloc_from_bin(bin);
-}
-
-/* NOTE: Expects the mutex to be held by the caller */
-static void s_sba_free_to_bin(struct sba_bin *bin, void *addr) {
- AWS_PRECONDITION(addr);
- struct page_header *page = s_page_base(addr);
- AWS_ASSERT(page->bin == bin);
- page->alloc_count--;
- if (page->alloc_count == 0 && page != s_page_base(bin->page_cursor)) { /* empty page, free it */
- uint8_t *page_start = (uint8_t *)page + sizeof(struct page_header);
- uint8_t *page_end = page_start + AWS_SBA_PAGE_SIZE;
- /* Remove all chunks in the page from the free list */
- intptr_t chunk_idx = bin->free_chunks.length;
- for (; chunk_idx >= 0; --chunk_idx) {
- uint8_t *chunk = NULL;
- aws_array_list_get_at(&bin->free_chunks, &chunk, chunk_idx);
- if (chunk >= page_start && chunk < page_end) {
- aws_array_list_swap(&bin->free_chunks, chunk_idx, bin->free_chunks.length - 1);
- aws_array_list_pop_back(&bin->free_chunks);
- }
- }
-
- /* Find page in pages list and remove it */
- for (size_t page_idx = 0; page_idx < bin->active_pages.length; ++page_idx) {
- void *page_addr = NULL;
- aws_array_list_get_at(&bin->active_pages, &page_addr, page_idx);
- if (page_addr == page) {
- aws_array_list_swap(&bin->active_pages, page_idx, bin->active_pages.length - 1);
- aws_array_list_pop_back(&bin->active_pages);
- break;
- }
- }
- /* ensure that the page tag is erased, in case nearby memory is re-used */
- page->tag = page->tag2 = 0;
- s_aligned_free(page);
- return;
- }
-
- aws_array_list_push_back(&bin->free_chunks, &addr);
-}
-
-/* No lock required for this function, it's all read-only access to constant data */
-static struct sba_bin *s_sba_find_bin(struct small_block_allocator *sba, size_t size) {
- AWS_PRECONDITION(size <= s_max_bin_size);
-
- /* map bits 5(32) to 9(512) to indices 0-4 */
- size_t next_pow2 = 0;
- aws_round_up_to_power_of_two(size, &next_pow2);
- size_t lz = aws_clz_i32((int32_t)next_pow2);
- size_t idx = aws_sub_size_saturating(31 - lz, 5);
- AWS_ASSERT(idx <= 4);
- struct sba_bin *bin = &sba->bins[idx];
- AWS_ASSERT(bin->size >= size);
- return bin;
-}
-
-static void *s_sba_alloc(struct small_block_allocator *sba, size_t size) {
- if (size <= s_max_bin_size) {
- struct sba_bin *bin = s_sba_find_bin(sba, size);
- AWS_FATAL_ASSERT(bin);
- /* BEGIN CRITICAL SECTION */
- sba->lock(&bin->mutex);
- void *mem = s_sba_alloc_from_bin(bin);
- sba->unlock(&bin->mutex);
- /* END CRITICAL SECTION */
- return mem;
- }
- return aws_mem_acquire(sba->allocator, size);
-}
-
-static void s_sba_free(struct small_block_allocator *sba, void *addr) {
- if (!addr) {
- return;
- }
-
- struct page_header *page = (struct page_header *)s_page_base(addr);
- /* Check to see if this page is tagged by the sba */
- /* this check causes a read of (possibly) memory we didn't allocate, but it will always be
- * heap memory, so should not cause any issues. TSan will see this as a data race, but it
- * is not, that's a false positive
- */
- if (page->tag == AWS_SBA_TAG_VALUE && page->tag2 == AWS_SBA_TAG_VALUE) {
- struct sba_bin *bin = page->bin;
- /* BEGIN CRITICAL SECTION */
- sba->lock(&bin->mutex);
- s_sba_free_to_bin(bin, addr);
- sba->unlock(&bin->mutex);
- /* END CRITICAL SECTION */
- return;
- }
- /* large alloc, give back to underlying allocator */
- aws_mem_release(sba->allocator, addr);
-}
-
-static void *s_sba_mem_acquire(struct aws_allocator *allocator, size_t size) {
- struct small_block_allocator *sba = allocator->impl;
- return s_sba_alloc(sba, size);
-}
-
-static void s_sba_mem_release(struct aws_allocator *allocator, void *ptr) {
- struct small_block_allocator *sba = allocator->impl;
- s_sba_free(sba, ptr);
-}
-
-static void *s_sba_mem_realloc(struct aws_allocator *allocator, void *old_ptr, size_t old_size, size_t new_size) {
- struct small_block_allocator *sba = allocator->impl;
- /* If both allocations come from the parent, let the parent do it */
- if (old_size > s_max_bin_size && new_size > s_max_bin_size) {
- void *ptr = old_ptr;
- if (aws_mem_realloc(sba->allocator, &ptr, old_size, new_size)) {
- return NULL;
- }
- return ptr;
- }
-
- if (new_size == 0) {
- s_sba_free(sba, old_ptr);
- return NULL;
- }
-
- if (old_size > new_size) {
- return old_ptr;
- }
-
- void *new_mem = s_sba_alloc(sba, new_size);
- if (old_ptr && old_size) {
- memcpy(new_mem, old_ptr, old_size);
- s_sba_free(sba, old_ptr);
- }
-
- return new_mem;
-}
-
-static void *s_sba_mem_calloc(struct aws_allocator *allocator, size_t num, size_t size) {
- struct small_block_allocator *sba = allocator->impl;
- void *mem = s_sba_alloc(sba, size * num);
- memset(mem, 0, size * num);
- return mem;
-}
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+
+#include <aws/common/allocator.h>
+#include <aws/common/array_list.h>
+#include <aws/common/assert.h>
+#include <aws/common/mutex.h>
+
+/*
+ * Small Block Allocator
+ * This is a fairly standard approach, the idea is to always allocate aligned pages of memory so that for
+ * any address you can round to the nearest page boundary to find the bookkeeping data. The idea is to reduce
+ * overhead per alloc and greatly improve runtime speed by doing as little actual allocation work as possible,
+ * preferring instead to re-use (hopefully still cached) chunks in FIFO order, or chunking up a page if there's
+ * no free chunks. When all chunks in a page are freed, the page is returned to the OS.
+ *
+ * The allocator itself is simply an array of bins, each representing a power of 2 size from 32 - N (512 tends to be
+ * a good upper bound). Thread safety is guaranteed by a mutex per bin, and locks are only necessary around the
+ * lowest level alloc and free operations.
+ *
+ * Note: this allocator gets its internal memory for data structures from the parent allocator, but does not
+ * use the parent to allocate pages. Pages are allocated directly from the OS-specific aligned malloc implementation,
+ * which allows the OS to do address re-mapping for us instead of over-allocating to fulfill alignment.
+ */
+
+#ifdef _WIN32
+# include <malloc.h>
+#elif __linux__ || __APPLE__
+# include <stdlib.h>
+#endif
+
+#if !defined(AWS_SBA_PAGE_SIZE)
+# if defined(PAGE_SIZE)
+# define AWS_SBA_PAGE_SIZE ((uintptr_t)(PAGE_SIZE))
+# else
+# define AWS_SBA_PAGE_SIZE ((uintptr_t)(4096))
+# endif
+#endif
+
+#define AWS_SBA_PAGE_MASK ((uintptr_t) ~(AWS_SBA_PAGE_SIZE - 1))
+#define AWS_SBA_TAG_VALUE 0x736f6d6570736575ULL
+
+/* list of sizes of bins, must be powers of 2, and less than AWS_SBA_PAGE_SIZE * 0.5 */
+#define AWS_SBA_BIN_COUNT 5
+static const size_t s_bin_sizes[AWS_SBA_BIN_COUNT] = {32, 64, 128, 256, 512};
+static const size_t s_max_bin_size = 512;
+
+struct sba_bin {
+ size_t size; /* size of allocs in this bin */
+ struct aws_mutex mutex; /* lock protecting this bin */
+ uint8_t *page_cursor; /* pointer to working page, currently being chunked from */
+ struct aws_array_list active_pages; /* all pages in use by this bin, could be optimized at scale by being a set */
+ struct aws_array_list free_chunks; /* free chunks available in this bin */
+};
+
+/* Header stored at the base of each page.
+ * As long as this is under 32 bytes, all is well.
+ * Above that, there's potentially more waste per page */
+struct page_header {
+ uint64_t tag; /* marker to identify/validate pages */
+ struct sba_bin *bin; /* bin this page belongs to */
+ uint32_t alloc_count; /* number of outstanding allocs from this page */
+ uint64_t tag2;
+};
+
+/* This is the impl for the aws_allocator */
+struct small_block_allocator {
+ struct aws_allocator *allocator; /* parent allocator, for large allocs */
+ struct sba_bin bins[AWS_SBA_BIN_COUNT];
+ int (*lock)(struct aws_mutex *);
+ int (*unlock)(struct aws_mutex *);
+};
+
+static int s_null_lock(struct aws_mutex *mutex) {
+ (void)mutex;
+ /* NO OP */
+ return 0;
+}
+
+static int s_null_unlock(struct aws_mutex *mutex) {
+ (void)mutex;
+ /* NO OP */
+ return 0;
+}
+
+static int s_mutex_lock(struct aws_mutex *mutex) {
+ return aws_mutex_lock(mutex);
+}
+
+static int s_mutex_unlock(struct aws_mutex *mutex) {
+ return aws_mutex_unlock(mutex);
+}
+
+static void *s_page_base(const void *addr) {
+ /* mask off the address to round it to page alignment */
+ uint8_t *page_base = (uint8_t *)(((uintptr_t)addr) & AWS_SBA_PAGE_MASK);
+ return page_base;
+}
+
+static void *s_page_bind(void *addr, struct sba_bin *bin) {
+ /* insert the header at the base of the page and advance past it */
+ struct page_header *page = (struct page_header *)addr;
+ page->tag = page->tag2 = AWS_SBA_TAG_VALUE;
+ page->bin = bin;
+ page->alloc_count = 0;
+ return (uint8_t *)addr + sizeof(struct page_header);
+}
+
+/* Wraps OS-specific aligned malloc implementation */
+static void *s_aligned_alloc(size_t size, size_t align) {
+#ifdef _WIN32
+ return _aligned_malloc(size, align);
+#else
+ void *mem = NULL;
+ int return_code = posix_memalign(&mem, align, size);
+ if (return_code) {
+ aws_raise_error(AWS_ERROR_OOM);
+ return NULL;
+ }
+ return mem;
+#endif
+}
+
+/* wraps OS-specific aligned free implementation */
+static void s_aligned_free(void *addr) {
+#ifdef _WIN32
+ _aligned_free(addr);
+#else
+ free(addr);
+#endif
+}
+
+/* aws_allocator vtable template */
+static void *s_sba_mem_acquire(struct aws_allocator *allocator, size_t size);
+static void s_sba_mem_release(struct aws_allocator *allocator, void *ptr);
+static void *s_sba_mem_realloc(struct aws_allocator *allocator, void *old_ptr, size_t old_size, size_t new_size);
+static void *s_sba_mem_calloc(struct aws_allocator *allocator, size_t num, size_t size);
+
+static struct aws_allocator s_sba_allocator = {
+ .mem_acquire = s_sba_mem_acquire,
+ .mem_release = s_sba_mem_release,
+ .mem_realloc = s_sba_mem_realloc,
+ .mem_calloc = s_sba_mem_calloc,
+};
+
+static int s_sba_init(struct small_block_allocator *sba, struct aws_allocator *allocator, bool multi_threaded) {
+ sba->allocator = allocator;
+ AWS_ZERO_ARRAY(sba->bins);
+ sba->lock = multi_threaded ? s_mutex_lock : s_null_lock;
+ sba->unlock = multi_threaded ? s_mutex_unlock : s_null_unlock;
+
+ for (unsigned idx = 0; idx < AWS_SBA_BIN_COUNT; ++idx) {
+ struct sba_bin *bin = &sba->bins[idx];
+ bin->size = s_bin_sizes[idx];
+ if (multi_threaded && aws_mutex_init(&bin->mutex)) {
+ goto cleanup;
+ }
+ if (aws_array_list_init_dynamic(&bin->active_pages, sba->allocator, 16, sizeof(void *))) {
+ goto cleanup;
+ }
+ /* start with enough chunks for 1 page */
+ if (aws_array_list_init_dynamic(
+ &bin->free_chunks, sba->allocator, aws_max_size(AWS_SBA_PAGE_SIZE / bin->size, 16), sizeof(void *))) {
+ goto cleanup;
+ }
+ }
+
+ return AWS_OP_SUCCESS;
+
+cleanup:
+ for (unsigned idx = 0; idx < AWS_SBA_BIN_COUNT; ++idx) {
+ struct sba_bin *bin = &sba->bins[idx];
+ aws_mutex_clean_up(&bin->mutex);
+ aws_array_list_clean_up(&bin->active_pages);
+ aws_array_list_clean_up(&bin->free_chunks);
+ }
+ return AWS_OP_ERR;
+}
+
+static void s_sba_clean_up(struct small_block_allocator *sba) {
+ /* free all known pages, then free the working page */
+ for (unsigned idx = 0; idx < AWS_SBA_BIN_COUNT; ++idx) {
+ struct sba_bin *bin = &sba->bins[idx];
+ for (size_t page_idx = 0; page_idx < bin->active_pages.length; ++page_idx) {
+ void *page_addr = NULL;
+ aws_array_list_get_at(&bin->active_pages, &page_addr, page_idx);
+ s_aligned_free(page_addr);
+ }
+ if (bin->page_cursor) {
+ void *page_addr = s_page_base(bin->page_cursor);
+ s_aligned_free(page_addr);
+ }
+
+ aws_array_list_clean_up(&bin->active_pages);
+ aws_array_list_clean_up(&bin->free_chunks);
+ aws_mutex_clean_up(&bin->mutex);
+ }
+}
+
+struct aws_allocator *aws_small_block_allocator_new(struct aws_allocator *allocator, bool multi_threaded) {
+ struct small_block_allocator *sba = NULL;
+ struct aws_allocator *sba_allocator = NULL;
+ aws_mem_acquire_many(
+ allocator, 2, &sba, sizeof(struct small_block_allocator), &sba_allocator, sizeof(struct aws_allocator));
+
+ if (!sba || !sba_allocator) {
+ return NULL;
+ }
+
+ AWS_ZERO_STRUCT(*sba);
+ AWS_ZERO_STRUCT(*sba_allocator);
+
+ /* copy the template vtable */
+ *sba_allocator = s_sba_allocator;
+ sba_allocator->impl = sba;
+
+ if (s_sba_init(sba, allocator, multi_threaded)) {
+ s_sba_clean_up(sba);
+ aws_mem_release(allocator, sba);
+ return NULL;
+ }
+ return sba_allocator;
+}
+
+void aws_small_block_allocator_destroy(struct aws_allocator *sba_allocator) {
+ if (!sba_allocator) {
+ return;
+ }
+ struct small_block_allocator *sba = sba_allocator->impl;
+ if (!sba) {
+ return;
+ }
+
+ struct aws_allocator *allocator = sba->allocator;
+ s_sba_clean_up(sba);
+ aws_mem_release(allocator, sba);
+}
+
+/* NOTE: Expects the mutex to be held by the caller */
+static void *s_sba_alloc_from_bin(struct sba_bin *bin) {
+ /* check the free list, hand chunks out in FIFO order */
+ if (bin->free_chunks.length > 0) {
+ void *chunk = NULL;
+ if (aws_array_list_back(&bin->free_chunks, &chunk)) {
+ return NULL;
+ }
+ if (aws_array_list_pop_back(&bin->free_chunks)) {
+ return NULL;
+ }
+
+ AWS_ASSERT(chunk);
+ struct page_header *page = s_page_base(chunk);
+ page->alloc_count++;
+ return chunk;
+ }
+
+ /* If there is a working page to chunk from, use it */
+ if (bin->page_cursor) {
+ struct page_header *page = s_page_base(bin->page_cursor);
+ AWS_ASSERT(page);
+ size_t space_left = AWS_SBA_PAGE_SIZE - (bin->page_cursor - (uint8_t *)page);
+ if (space_left >= bin->size) {
+ void *chunk = bin->page_cursor;
+ page->alloc_count++;
+ bin->page_cursor += bin->size;
+ space_left -= bin->size;
+ if (space_left < bin->size) {
+ aws_array_list_push_back(&bin->active_pages, &page);
+ bin->page_cursor = NULL;
+ }
+ return chunk;
+ }
+ }
+
+ /* Nothing free to use, allocate a page and restart */
+ uint8_t *new_page = s_aligned_alloc(AWS_SBA_PAGE_SIZE, AWS_SBA_PAGE_SIZE);
+ new_page = s_page_bind(new_page, bin);
+ bin->page_cursor = new_page;
+ return s_sba_alloc_from_bin(bin);
+}
+
+/* NOTE: Expects the mutex to be held by the caller */
+static void s_sba_free_to_bin(struct sba_bin *bin, void *addr) {
+ AWS_PRECONDITION(addr);
+ struct page_header *page = s_page_base(addr);
+ AWS_ASSERT(page->bin == bin);
+ page->alloc_count--;
+ if (page->alloc_count == 0 && page != s_page_base(bin->page_cursor)) { /* empty page, free it */
+ uint8_t *page_start = (uint8_t *)page + sizeof(struct page_header);
+ uint8_t *page_end = page_start + AWS_SBA_PAGE_SIZE;
+ /* Remove all chunks in the page from the free list */
+ intptr_t chunk_idx = bin->free_chunks.length;
+ for (; chunk_idx >= 0; --chunk_idx) {
+ uint8_t *chunk = NULL;
+ aws_array_list_get_at(&bin->free_chunks, &chunk, chunk_idx);
+ if (chunk >= page_start && chunk < page_end) {
+ aws_array_list_swap(&bin->free_chunks, chunk_idx, bin->free_chunks.length - 1);
+ aws_array_list_pop_back(&bin->free_chunks);
+ }
+ }
+
+ /* Find page in pages list and remove it */
+ for (size_t page_idx = 0; page_idx < bin->active_pages.length; ++page_idx) {
+ void *page_addr = NULL;
+ aws_array_list_get_at(&bin->active_pages, &page_addr, page_idx);
+ if (page_addr == page) {
+ aws_array_list_swap(&bin->active_pages, page_idx, bin->active_pages.length - 1);
+ aws_array_list_pop_back(&bin->active_pages);
+ break;
+ }
+ }
+ /* ensure that the page tag is erased, in case nearby memory is re-used */
+ page->tag = page->tag2 = 0;
+ s_aligned_free(page);
+ return;
+ }
+
+ aws_array_list_push_back(&bin->free_chunks, &addr);
+}
+
+/* No lock required for this function, it's all read-only access to constant data */
+static struct sba_bin *s_sba_find_bin(struct small_block_allocator *sba, size_t size) {
+ AWS_PRECONDITION(size <= s_max_bin_size);
+
+ /* map bits 5(32) to 9(512) to indices 0-4 */
+ size_t next_pow2 = 0;
+ aws_round_up_to_power_of_two(size, &next_pow2);
+ size_t lz = aws_clz_i32((int32_t)next_pow2);
+ size_t idx = aws_sub_size_saturating(31 - lz, 5);
+ AWS_ASSERT(idx <= 4);
+ struct sba_bin *bin = &sba->bins[idx];
+ AWS_ASSERT(bin->size >= size);
+ return bin;
+}
+
+static void *s_sba_alloc(struct small_block_allocator *sba, size_t size) {
+ if (size <= s_max_bin_size) {
+ struct sba_bin *bin = s_sba_find_bin(sba, size);
+ AWS_FATAL_ASSERT(bin);
+ /* BEGIN CRITICAL SECTION */
+ sba->lock(&bin->mutex);
+ void *mem = s_sba_alloc_from_bin(bin);
+ sba->unlock(&bin->mutex);
+ /* END CRITICAL SECTION */
+ return mem;
+ }
+ return aws_mem_acquire(sba->allocator, size);
+}
+
+static void s_sba_free(struct small_block_allocator *sba, void *addr) {
+ if (!addr) {
+ return;
+ }
+
+ struct page_header *page = (struct page_header *)s_page_base(addr);
+ /* Check to see if this page is tagged by the sba */
+ /* this check causes a read of (possibly) memory we didn't allocate, but it will always be
+ * heap memory, so should not cause any issues. TSan will see this as a data race, but it
+ * is not, that's a false positive
+ */
+ if (page->tag == AWS_SBA_TAG_VALUE && page->tag2 == AWS_SBA_TAG_VALUE) {
+ struct sba_bin *bin = page->bin;
+ /* BEGIN CRITICAL SECTION */
+ sba->lock(&bin->mutex);
+ s_sba_free_to_bin(bin, addr);
+ sba->unlock(&bin->mutex);
+ /* END CRITICAL SECTION */
+ return;
+ }
+ /* large alloc, give back to underlying allocator */
+ aws_mem_release(sba->allocator, addr);
+}
+
+static void *s_sba_mem_acquire(struct aws_allocator *allocator, size_t size) {
+ struct small_block_allocator *sba = allocator->impl;
+ return s_sba_alloc(sba, size);
+}
+
+static void s_sba_mem_release(struct aws_allocator *allocator, void *ptr) {
+ struct small_block_allocator *sba = allocator->impl;
+ s_sba_free(sba, ptr);
+}
+
+static void *s_sba_mem_realloc(struct aws_allocator *allocator, void *old_ptr, size_t old_size, size_t new_size) {
+ struct small_block_allocator *sba = allocator->impl;
+ /* If both allocations come from the parent, let the parent do it */
+ if (old_size > s_max_bin_size && new_size > s_max_bin_size) {
+ void *ptr = old_ptr;
+ if (aws_mem_realloc(sba->allocator, &ptr, old_size, new_size)) {
+ return NULL;
+ }
+ return ptr;
+ }
+
+ if (new_size == 0) {
+ s_sba_free(sba, old_ptr);
+ return NULL;
+ }
+
+ if (old_size > new_size) {
+ return old_ptr;
+ }
+
+ void *new_mem = s_sba_alloc(sba, new_size);
+ if (old_ptr && old_size) {
+ memcpy(new_mem, old_ptr, old_size);
+ s_sba_free(sba, old_ptr);
+ }
+
+ return new_mem;
+}
+
+static void *s_sba_mem_calloc(struct aws_allocator *allocator, size_t num, size_t size) {
+ struct small_block_allocator *sba = allocator->impl;
+ void *mem = s_sba_alloc(sba, size * num);
+ memset(mem, 0, size * num);
+ return mem;
+}