From 794f593390a95b791ec334f1cabd08147ce5938c Mon Sep 17 00:00:00 2001 From: Akilan Date: Mon, 18 Nov 2024 13:04:44 +0000 Subject: [PATCH] added new memory allocator files and README --- allocator/alloc.c | 0 allocator/bitmap/bitmap_alloc.c | 204 ++++++++++++++++++++++++ allocator/bitmap/bitmap_alloc.h | 0 allocator/bump_alloc/bump_alloc.c | 78 +++++++++ allocator/bump_alloc/bump_alloc.h | 18 +++ allocator/freelist/binary_trees.c | 119 ++++++++++++++ allocator/freelist/freelist.c | 166 +++++++++++++++++++ allocator/freelist/freelist_allocator.h | 53 ++++++ allocator/readme.md | 46 ++++++ 9 files changed, 684 insertions(+) create mode 100644 allocator/alloc.c create mode 100644 allocator/bitmap/bitmap_alloc.c create mode 100644 allocator/bitmap/bitmap_alloc.h create mode 100644 allocator/bump_alloc/bump_alloc.c create mode 100644 allocator/bump_alloc/bump_alloc.h create mode 100644 allocator/freelist/binary_trees.c create mode 100644 allocator/freelist/freelist.c create mode 100644 allocator/freelist/freelist_allocator.h create mode 100644 allocator/readme.md diff --git a/allocator/alloc.c b/allocator/alloc.c new file mode 100644 index 0000000..e69de29 diff --git a/allocator/bitmap/bitmap_alloc.c b/allocator/bitmap/bitmap_alloc.c new file mode 100644 index 0000000..737449a --- /dev/null +++ b/allocator/bitmap/bitmap_alloc.c @@ -0,0 +1,204 @@ +/********************************** + * bitmap_alloc.c + * Jeremy.Singer@glasgow.ac.uk + * + * This is a simple fixed-size bitmap allocator. + * It mmaps a large buffer of + * NUM_CHUNKS * CHUNK_SIZE bytes + * then allocates this space in equally-sized + * chunks to client code. + * A side bitmap is required to keep track of which + * chunks are in use (corresponding bit set to 1) + * and which chunks are free (corresponding bit + * set to 0). There is one bit per allocatable chunk. + * + * This is _not_ a clever allocator, since it + * does a linear scan of the bitmap to find the + * first free chunk, which is expensive! + * More efficient scans could be easily incorporated. + * + * This is _not_ a general-purpose allocator, since + * it only allocates chunks of a fixed size. Further, + * this size is constrained to be small enough to allow + * exact bounds representation in CHERI capabilities. + * + * This is an initial simple memory allocator test + * for CHERI / Capable VMs. + * We explore capability alignment, + * representable bounds, narrowing operations + * and compiler intrinsic support. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "bitmap_alloc.h" + +#define BITS_PER_BYTE 8 + +char *buffer = NULL; /* allocation buffer */ +unsigned char *bitmap = NULL; /* bitmap for the buffer */ + +int buffer_size = 0; /* size of buffer (in bytes) */ +int bitmap_size = 0; /* size of bitmap (in bytes) */ +int bytes_per_chunk = 0; /* size of single chunk (in bytes) */ + +void init_alloc(int num_chunks, int chunk_size) +{ + int i = 0; + + /* we need to increase the num_chunks + * so every bit in bitmap will be used + */ + int adjusted_num_chunks = (num_chunks % BITS_PER_BYTE == 0) + ? num_chunks + : (num_chunks + (BITS_PER_BYTE - (num_chunks % BITS_PER_BYTE))); + + /* we need to increase the chunk_size + * so chunks will be CHERI aligned + * (i.e. 16 bytes for RISC-V 64-bit arch) + */ + int adjusted_chunk_size = + (chunk_size % (sizeof(void *)) == 0) + ? chunk_size + : (chunk_size + (sizeof(void *)) - (chunk_size % (sizeof(void *)))); + + /* check this chunk size is small enough so we can represent + * bounds precisely with CHERI compressed representation + */ + adjusted_chunk_size = cheri_representable_length(adjusted_chunk_size); + + /* request memory for our allocation buffer */ + char *res = mmap(NULL, adjusted_num_chunks * adjusted_chunk_size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + /* request memory for our bitmap */ + bitmap = (unsigned char *) mmap(NULL, adjusted_num_chunks / BITS_PER_BYTE, + PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + + if (res == MAP_FAILED || bitmap == MAP_FAILED) + { + perror("error in initial mem allocation"); + exit(-1); + } + + /* NB mmap min bounds for capability is 1 page (4K) */ + buffer = res; + /* check buffer is aligned */ + assert((uintptr_t) buffer % sizeof(void *) == 0); + /* check bitmap is aligned */ + assert((uintptr_t) bitmap % sizeof(void *) == 0); + + bytes_per_chunk = adjusted_chunk_size; + buffer_size = adjusted_num_chunks * adjusted_chunk_size; + bitmap_size = adjusted_num_chunks / BITS_PER_BYTE; + + /* zero bitmap, since all chunks are free initially */ + for (i = 0; i < bitmap_size; i++) + { + bitmap[i] = 0; + } + + // set exact bounds for buffer and bitmap? + buffer = cheri_setbounds(buffer, buffer_size); + bitmap = cheri_setbounds(bitmap, bitmap_size); + return; +} + +/* + * allocate fixed size chunk with bitmap allocator + * this is our simplistic `malloc` function + */ +char *malloc() +{ + unsigned char updated_byte = 0; + int chunk_index = 0; + char *chunk = NULL; + // iterate over all bits in bitmap, looking for a 0 + // when we find a 0, set it to 1 and + // return the corresponding chunk + // (setting its capability bounds) + int i = 0; + while (bitmap[i] == (unsigned char) 0xff) + { + i++; + if (i >= bitmap_size) + break; + } + // do we have a 0? + if (i < bitmap_size && bitmap[i] != (unsigned char) 0xff) + { + // find the lowest 0 ... + int j = 0; + // right shift until bottom bit is 0 + for (j = 0; j < BITS_PER_BYTE; j++) + { + int bit = (bitmap[i] >> j) & 1; + if (bit == 0) + { + break; + } + } + // now i is the word index, j is the bit index + // set this bit to 1 ... + // and work out the chunk to allocate + updated_byte = bitmap[i] + (unsigned char) (1 << j); + bitmap[i] = updated_byte; + + chunk_index = i * BITS_PER_BYTE + j; + chunk = buffer + (chunk_index * bytes_per_chunk); + + /* restrict capability range before returning ptr */ + chunk = cheri_setbounds(chunk, bytes_per_chunk); + } + + return chunk; +} + +void free(void *chunk) +{ + vaddr_t base = cheri_getbase(chunk); + vaddr_t buff_base = cheri_getbase(buffer); + /* calculate chunk index in buffer */ + int chunk_index = (base - buff_base) / bytes_per_chunk; + assert(chunk_index >= 0); + /* calculate corresponding bitmap index */ + int bitmap_index = chunk_index / BITS_PER_BYTE; + assert(bitmap_index < bitmap_size); + int bitmap_offset = chunk_index % BITS_PER_BYTE; + /* set this bitmap entry to 0 */ + unsigned char updated_byte = bitmap[bitmap_index] & (unsigned char) (~(1 << bitmap_offset)); + bitmap[bitmap_index] = updated_byte; + return; +} + +int num_used_chunks() +{ + int i = 0; + int used_chunks = 0; + + while (i < bitmap_size) + { + unsigned char x = bitmap[i]; + if (x != 0) + { + /* some used chunks here */ + unsigned char j; + for (j = 1; j <= x; j = j << 1) + { + if (x & j) + { + used_chunks++; + } + } + } + i++; + } + return used_chunks; +} \ No newline at end of file diff --git a/allocator/bitmap/bitmap_alloc.h b/allocator/bitmap/bitmap_alloc.h new file mode 100644 index 0000000..e69de29 diff --git a/allocator/bump_alloc/bump_alloc.c b/allocator/bump_alloc/bump_alloc.c new file mode 100644 index 0000000..b55bb27 --- /dev/null +++ b/allocator/bump_alloc/bump_alloc.c @@ -0,0 +1,78 @@ +/********************************** + * bump_alloc.c + * Jeremy.Singer@glasgow.ac.uk + * + * This is a simple bump-pointer allocator. + * It mmaps a large buffer of SIZE bytes, + * then allocates this space in word-sized + * chunks to client code (in main fn). + * + * Initial simple memory allocator test. + * Explore capability narrowing operations + * and intrinsics for bound reporting. + */ + +#include +#include +#include +#include +#include + +#include "bump_alloc.h" + +int count = 0; /* number of bytes allocated so far*/ +int max = 0; /* upper limit for count */ +char *buffer = NULL; /* the allocation buffer */ + +void init_alloc(int size_in_bytes) +{ + /* request memory for our allocation buffer + * NB mmap min bounds for capability is 1 page (4K) + */ + char *res = mmap(NULL, size_in_bytes, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + + if (res == MAP_FAILED) + { + perror("error in initial mem allocation"); + exit(-1); + } + + buffer = res; + max = size_in_bytes; + return; +} + +/* + * allocate len bytes with bump pointer allocator + * this is our simplistic `malloc` function + */ +char *malloc(int len) +{ + char *chunk = buffer + count; + size_t rounded_len; /* for CHERI alignment */ + size_t new_count; /* for buffer overflow check */ + + /* ensure we can represent the capability accurately, + * see p30 of CHERI C/C++ Prog Guide (June 2020) + * www.cl.cam.ac.uk/techreports/UCAM-CL-TR-947 + */ + chunk = __builtin_align_up(chunk, ~cheri_representable_alignment_mask(len) + 1); + rounded_len = cheri_representable_length(len); + + new_count = (chunk + rounded_len) - buffer; + if (new_count > max) + { + /* out of bounds - don't allocate anything */ + chunk = 0; + } + else + { + /* restrict capability range before returning ptr */ + chunk = cheri_bounds_set_exact(chunk, rounded_len); + + /* update bytes allocated count */ + count = new_count; + } + + return chunk; +} \ No newline at end of file diff --git a/allocator/bump_alloc/bump_alloc.h b/allocator/bump_alloc/bump_alloc.h new file mode 100644 index 0000000..5dce22d --- /dev/null +++ b/allocator/bump_alloc/bump_alloc.h @@ -0,0 +1,18 @@ +/********************************** + * bump_alloc.h + * Jeremy.Singer@glasgow.ac.uk + * + * This is a simple bump-pointer allocator. + * It mmaps a large buffer of SIZE bytes, + * then allocates this space in word-sized + * chunks to client code (in main fn). + * + * Initial simple memory allocator test. + * Explore capability narrowing operations + * and intrinsics for bound reporting. + */ + +void init_alloc(int size_in_bytes); + +char *bump_alloc(int bytes); /* the simplest malloc */ + /* oh, and there's no free() ! */ \ No newline at end of file diff --git a/allocator/freelist/binary_trees.c b/allocator/freelist/binary_trees.c new file mode 100644 index 0000000..2f8a27a --- /dev/null +++ b/allocator/freelist/binary_trees.c @@ -0,0 +1,119 @@ +/* The Computer Language Benchmarks Game + * https://salsa.debian.org/benchmarksgame-team/benchmarksgame/ + + contributed by Kevin Carson + compilation: + gcc -O3 -fomit-frame-pointer -funroll-loops -static binary-trees.c -lm + icc -O3 -ip -unroll -static binary-trees.c -lm + + *reset* +*/ + +/* modified by @jsinger for CHERI example allocators */ +#include "freelist_allocator.h" +#include +#include +#include + +typedef struct tn +{ + struct tn *left; + struct tn *right; +} treeNode; + +treeNode *NewTreeNode(treeNode *left, treeNode *right) +{ + treeNode *new; + + new = (treeNode *) alloc(sizeof(treeNode)); + + new->left = left; + new->right = right; + + return new; +} /* NewTreeNode() */ + +long ItemCheck(treeNode *tree) +{ + if (tree->left == NULL) + return 1; + else + return 1 + ItemCheck(tree->left) + ItemCheck(tree->right); +} /* ItemCheck() */ + +treeNode *BottomUpTree(unsigned depth) +{ + if (depth > 0) + return NewTreeNode(BottomUpTree(depth - 1), BottomUpTree(depth - 1)); + else + return NewTreeNode(NULL, NULL); +} /* BottomUpTree() */ + +void DeleteTree(treeNode *tree) +{ + if (tree->left != NULL) + { + DeleteTree(tree->left); + DeleteTree(tree->right); + } + + dealloc(tree); +} /* DeleteTree() */ + +int main(int argc, char *argv[]) +{ + unsigned N, depth, minDepth, maxDepth, stretchDepth; + treeNode *stretchTree, *longLivedTree, *tempTree; + unsigned pages; /* mem required */ + + N = (argc > 1) ? atol(argv[1]) : 0; + + minDepth = 4; + + if ((minDepth + 2) > N) + maxDepth = minDepth + 2; + else + maxDepth = N; + + stretchDepth = maxDepth + 1; + + /* calculate mem requirements, with allocator-specific + * size-class assumptions + */ + pages = ((2 << (stretchDepth + 3)) * sizeof(treeNode)) / BYTES_IN_PAGE; + printf("treenode size is %u bytes\n", (unsigned int) sizeof(treeNode)); + printf("we need %u pages\n", pages); + + /* allocate memory pool */ + initialize(pages); + + /* start creating tree data structures */ + stretchTree = BottomUpTree(stretchDepth); + printf("stretch tree of depth %u\t check: %li\n", stretchDepth, ItemCheck(stretchTree)); + + DeleteTree(stretchTree); + + longLivedTree = BottomUpTree(maxDepth); + + for (depth = minDepth; depth <= maxDepth; depth += 2) + { + long i, iterations, check; + + iterations = pow(2, maxDepth - depth + minDepth); + + check = 0; + + for (i = 1; i <= iterations; i++) + { + tempTree = BottomUpTree(depth); + check += ItemCheck(tempTree); + DeleteTree(tempTree); + } /* for(i = 1...) */ + + printf("%li\t trees of depth %u\t check: %li\n", iterations, depth, check); + } /* for(depth = minDepth...) */ + + printf("long lived tree of depth %u\t check: %li\n", maxDepth, ItemCheck(longLivedTree)); + + return 0; +} /* main() */ \ No newline at end of file diff --git a/allocator/freelist/freelist.c b/allocator/freelist/freelist.c new file mode 100644 index 0000000..4ed5029 --- /dev/null +++ b/allocator/freelist/freelist.c @@ -0,0 +1,166 @@ +#include "freelist_allocator.h" +#include +#include +#include +#include +#include + +char *small_freelist = NULL; + +char *medium_freelist = NULL; + +char *large_freelist = NULL; + +void initialize(unsigned int size_in_pages) +{ + /* request memory for our allocation buffer + * NB mmap min bounds for capability is 1 page (4K) + */ + size_t bytes_to_allocate = size_in_pages * BYTES_IN_PAGE; + char *res = + mmap(NULL, bytes_to_allocate, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + + if (res == MAP_FAILED) + { + perror("error in initial mem allocation"); + exit(-1); + } + + // put in linked list pointers and + // stick into the large freelist + // give this space to the large freelist ... + large_freelist = insert_linked_list_pointers(LARGE, bytes_to_allocate, res, large_freelist); + return; +} + +char *insert_linked_list_pointers(size_t cell_size, size_t limit, char *start, char *freelist) +{ + char *curr = start; + char *next = curr + cell_size; + char *max = start + limit; + // ensure next ptr will fit into cell + assert(sizeof(void *) <= cell_size); + + while (next < max) + { + ((char **) curr)[0] = next; + curr = next; + next = curr + cell_size; + } + // at the end, concatenate this newly formed + // list with existing freelist + ((char **) curr)[0] = freelist; + + return start; +} + +char *alloc(size_t bytes) +{ + + size_t size; + char *freelist_to_use = NULL; + char *ret = NULL; // ptr to return + + // work out which freelist to use + if (bytes <= SMALL) + { + size = SMALL; + freelist_to_use = small_freelist; + } + else if (bytes <= MEDIUM) + { + size = MEDIUM; + freelist_to_use = medium_freelist; + } + else + { + size = LARGE; + freelist_to_use = large_freelist; + } + + if (freelist_to_use == NULL) + { + // fixup freelist (if no available mem there) + char *new_space = NULL; + switch (size) + { + case SMALL: + new_space = alloc(MEDIUM); + if (new_space != NULL) + { + small_freelist = + insert_linked_list_pointers(SMALL, MEDIUM, new_space, small_freelist); + freelist_to_use = small_freelist; + // now we have replenished space... + } + break; + case MEDIUM: + new_space = alloc(LARGE); + if (new_space != NULL) + { + medium_freelist = + insert_linked_list_pointers(MEDIUM, LARGE, new_space, medium_freelist); + freelist_to_use = medium_freelist; + } + break; + default: + // stuck! no more mem! + // we will return NULL + break; + } + } + + // pop from head of freelist (if there's anything there) + if (freelist_to_use != NULL) + { + char *head = freelist_to_use; + char *tail = ((char **) head)[0]; + switch (size) + { + case SMALL: + small_freelist = tail; + break; + case MEDIUM: + medium_freelist = tail; + break; + default: + large_freelist = tail; + break; + } + ret = head; + SET_SIZE(ret, size); + } + return ret; +} + +void dealloc(void *buffer) +{ + + // work out the size of the buffer + size_t size; + char *freelist; + + size = GET_SIZE(buffer); + + // then prepend it to the appropriate freelist + switch (size) + { + case SMALL: + small_freelist = cons_onto_freelist(buffer, small_freelist); + break; + case MEDIUM: + medium_freelist = cons_onto_freelist(buffer, medium_freelist); + break; + default: + large_freelist = cons_onto_freelist(buffer, large_freelist); + break; + } + + return; +} + +char *cons_onto_freelist(char *cell, char *freelist) +{ + ((char **) cell)[0] = freelist; + return cell; +} \ No newline at end of file diff --git a/allocator/freelist/freelist_allocator.h b/allocator/freelist/freelist_allocator.h new file mode 100644 index 0000000..e52c8dd --- /dev/null +++ b/allocator/freelist/freelist_allocator.h @@ -0,0 +1,53 @@ +/********************************** + * freelist_allocator.h + * Jeremy.Singer@glasgow.ac.uk + * + * This is a simple segregated freelist allocator. + * It mmaps a large buffer of num_pages pages, + * then constructs a linked list of LARGE-sized cells. + * + * When an alloc request occurs, we have three size + * classes we can use - SMALL, MEDIUM, and LARGE. + * If there is a empty cell available in the appropriate + * freelist, we return this cell. + * If there are no cells available, we try to + * grab a cell from a larger freelist to replenish + * our freelist, and return one of these cells. + * If there is no memory available, alloc returns NULL. + * + * When a dealloc request occurs, we know the size + * of the cell so we can prepend the cell onto the + * appropriate freelist. + * + * NB Allocated cells have their sizes encoded in the + * corresponding cell capability - this means we + * naively assume that allocator client code + * does _not_ interfere with the capability + * metadata. + */ + +#include +#include + +/* possible sizes for cells */ +#define SMALL 16 +#define MEDIUM 256 +#define LARGE 4096 + +/* we assume 4K pages */ +#define BYTES_IN_PAGE LARGE + +/* cell sizes encoded in CHERI bounds metadata */ +#define SET_SIZE(cell, size) cell = cheri_bounds_set_exact(cell, size) +#define GET_SIZE(cell) cheri_length_get(cell) + +/* allocator init routine */ +void initialize(unsigned int num_pages); + +/* malloc and free */ +char *alloc(size_t bytes); +void dealloc(void *buffer); + +/* freelist management routines */ +char *insert_linked_list_pointers(size_t cell_size, size_t limit, char *start, char *freelist); +char *cons_onto_freelist(char *cell, char *freelist); \ No newline at end of file diff --git a/allocator/readme.md b/allocator/readme.md new file mode 100644 index 0000000..3f03662 --- /dev/null +++ b/allocator/readme.md @@ -0,0 +1,46 @@ +# Convention for benchmarking various allocators +We demonstrate the standard emitters each C memory allocator should follow to standardize the code being benchmarked. + +There are 3 factors on the design for benchmarking these C programs: +- Standard interface to test various memory allocators. +- Automating the extracting of various performance counters. +- Parsing and analyzing the various benchmark metrics extracted. + +## Standard interface to test various memory allocators +The interface goes as the following: +- ```Malloc```, ```Free``` and ``ìnit_alloc``. +- The folder structure is as follows: +``` +- + - HugePages + - Original + - README (Explaining the Allocator design with the source of the allocator) +``` +- The linkage of the C program should consist either of a shared object file +which is preferred. Or with a header file which can compile the appropriate +file at compile. + - [ ] To write a script to compile and link shared object files. + - [ ] Automate generating header files. + +## Automating the extracting of various performance counters. +The extraction library to generate the decided performance counters is implemented. +ARM unclear documentation from the A profile manual gives a unclear picture of +exactly what the performance counters do. The script to extract it and to generate graphs +is completely isolated. This makes process from running to generating the end graphs +pretty tedious. + +### Steps to resolve this: +- [ ] To build runners that runs with different memory allocators and the wall clock +and metrics in semantically comparable file which is followed as a basic standard. +This means. +``` +Ex: + - performance-benchmark.stat + - performance-huge-benchmark.stat +``` +- [ ] Extract results to a certain folder and then immediate run python program to generate the graphs. +- [ ] Numeric values represented as a table. +- [ ] Generate graphs in semantically readable folder structure. +- [ ] Save generated data which can be loaded as graphs. + +