added new memory allocator files and README

This commit is contained in:
2024-11-18 13:04:44 +00:00
parent 515e5a30e2
commit 794f593390
9 changed files with 684 additions and 0 deletions

0
allocator/alloc.c Normal file
View File

View File

@@ -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 <assert.h>
#include <cheriintrin.h>
#include <cheri/cheric.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#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;
}

View File

View File

@@ -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 <cheriintrin.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#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;
}

View File

@@ -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() ! */

View File

@@ -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 <math.h>
#include <stdio.h>
#include <stdlib.h>
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() */

View File

@@ -0,0 +1,166 @@
#include "freelist_allocator.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
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;
}

View File

@@ -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 <cheriintrin.h>
#include <stddef.h>
/* 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);

46
allocator/readme.md Normal file
View File

@@ -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:
```
- <Allocator name>
- 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.