Browse Source

Merge incomplete branch 'tree_alloc'

Cameron Weinfurt 5 months ago
parent
commit
a0155d66dd
8 changed files with 994 additions and 38 deletions
  1. 7
    0
      .gitignore
  2. 6
    5
      Makefile
  3. 13
    0
      alloc_api.h
  4. 36
    5
      allocator_internal.h
  5. 131
    1
      main.c
  6. 122
    0
      paper.tex
  7. 649
    24
      tree_alloc.c
  8. 30
    3
      util.c

+ 7
- 0
.gitignore View File

@@ -1 +1,8 @@
1 1
 *.o
2
+the_alloc
3
+vgcore*
4
+debug_log.txt
5
+paper.log
6
+paper.aux
7
+paper.pdf
8
+

+ 6
- 5
Makefile View File

@@ -1,12 +1,13 @@
1
-objs = main.o tree_alloc.o
1
+objs = main.o tree_alloc.o util.o
2 2
 
3 3
 CC = clang
4
-COMMON_FLAGS = -std=c11
4
+COMMON_FLAGS = -std=gnu11
5 5
 OUT_NAME = the_alloc
6 6
 
7
-dev: CFLAGS= $(COMMON_FLAGS) -g -D UNIT_TESTS
7
+dev: CFLAGS= $(COMMON_FLAGS) -g -D UNIT_TESTS -D DEBUG=1
8 8
 dev: $(OUT_NAME)
9
-	valgrind ./$(OUT_NAME)
9
+	rm -f vgcore.*
10
+	valgrind ./$(OUT_NAME) 
10 11
 
11 12
 release: CFLAGS= $(COMMON_FLAGS) -O2
12 13
 release: clean $(OUT_NAME)
@@ -18,7 +19,7 @@ $(OUT_NAME): $(objs)
18 19
 clean:
19 20
 	rm -f *.o $(OUT_NAME)
20 21
 
21
-main.o: main.c
22
+main.o: main.c alloc_api.h
22 23
 	$(CC) $(CFLAGS) -c main.c -o main.o
23 24
 
24 25
 tree_alloc.o: tree_alloc.c allocator_internal.h

+ 13
- 0
alloc_api.h View File

@@ -0,0 +1,13 @@
1
+
2
+#include <stdint.h>
3
+
4
+struct Arena {
5
+  void *root_freespace;
6
+  void *root_treealloc;
7
+  void *(*get_new_region)(uintptr_t);
8
+  void (*error)(char*);
9
+};
10
+
11
+void unalloc(struct Arena *arena, void *addr);
12
+void *alloc(struct Arena *arena, uintptr_t size, uintptr_t align);
13
+

+ 36
- 5
allocator_internal.h View File

@@ -8,23 +8,40 @@ const char RT_FREESPACE = 0;
8 8
 const char RT_TREE_NODE = 1;
9 9
 const char RT_WATERMARK = 2;
10 10
 
11
-struct TopLevel {
11
+const char COLOR_RED = 0;
12
+const char COLOR_BLACK = 1;
13
+
14
+const uintptr_t MIN_NEW_MEM_SIZE = 4096;
15
+
16
+typedef struct Arena {
12 17
   struct FreeSpace *root_freespace;
13 18
   struct TreeAlloc *root_treealloc;
14
-};
19
+  void *(*get_new_region)(uintptr_t);
20
+  void (*error)(char*);
21
+} Arena;
15 22
 
16 23
 // All three of these types should be memory-compatible with each other
17 24
 
25
+// Before I forget to write it down: It is assumed that the beginning of every allocation region (or
26
+// free space region) is aligned to the alignment of all three of these types (or WatermarkAlloc since
27
+// it has at least all the fields of the other one). This allows for the allocation record to appear
28
+// at the beginning of the region and still be aligned. As a consequence, all sizes are a multiple of
29
+// the alignment.
30
+
18 31
 typedef struct TreeAlloc {
19 32
   char type;  // Should be RT_TREE_NODE
33
+  char color;
20 34
   struct TreeAlloc *parent;
21 35
   struct TreeAlloc *left;
22 36
   struct TreeAlloc *right;
23 37
   uintptr_t size;
38
+  struct TreeAlloc *before;
39
+  struct TreeAlloc *after;
24 40
 } TreeAlloc;
25 41
 
26 42
 typedef struct FreeSpace {
27 43
   char type;  // Should be RT_FREESPACE
44
+  char color;
28 45
   struct FreeSpace *parent;
29 46
   struct FreeSpace *left;
30 47
   struct FreeSpace *right;
@@ -33,17 +50,31 @@ typedef struct FreeSpace {
33 50
 
34 51
 typedef struct WatermarkAlloc {
35 52
   char type;  // Should be RT_WATERMARK
53
+  char color;
36 54
   struct TreeAlloc *parent;
37 55
   struct TreeAlloc *left;
38 56
   struct TreeAlloc *right;
39 57
   uintptr_t size;
58
+  struct TreeAlloc *before;
59
+  struct TreeAlloc *after;
40 60
   int num_allocs;
41 61
   void *next_alloc;
42 62
 } WatermarkAlloc;
43 63
 
44
-void* align_after(void* address, int align);
45
-TreeAlloc *insert_node_at(void *address, int padding, int align, int size);
64
+void* align_after(void* address, uintptr_t align);
65
+uintptr_t lcm(uintptr_t a, uintptr_t b);
66
+uintptr_t gcd(uintptr_t a, uintptr_t b);
46 67
 TreeAlloc *search_by_address(TreeAlloc *root, void *address);
47
-TreeAlloc *search_by_size(TreeAlloc *root, int padding, int align, int size);
68
+TreeAlloc *search_by_size(TreeAlloc *root, uintptr_t padding, uintptr_t align, uintptr_t size);
69
+TreeAlloc *get_sibling(TreeAlloc *p, TreeAlloc *ta);
70
+void rotate_left(TreeAlloc **root_ptr, TreeAlloc *ta);
71
+void rotate_right(TreeAlloc **root_ptr, TreeAlloc *ta);
72
+void repair_tree_after_insert(TreeAlloc **root_ptr, TreeAlloc *ta);
73
+void remove_node(TreeAlloc** root_ptr, TreeAlloc* node);
74
+void insert_singleton(TreeAlloc **root_ptr, TreeAlloc *to_insert);
75
+void insert_by_size(TreeAlloc** root_ptr, TreeAlloc* to_insert);
76
+void insert_by_addr(TreeAlloc** root_ptr, TreeAlloc* to_insert);
77
+void unalloc(Arena *arena, void *addr);
78
+void *alloc(Arena *arena, uintptr_t size, uintptr_t align);
48 79
 
49 80
 #endif

+ 131
- 1
main.c View File

@@ -1,6 +1,136 @@
1
+#include <stdlib.h>
1 2
 #include <stdio.h>
3
+#include <stdint.h>
4
+#include <stddef.h>
5
+#include <unistd.h>
6
+#include <sys/mman.h>
7
+#include <string.h>
8
+
9
+#include "alloc_api.h"
10
+
11
+void *main_get_new_region(uintptr_t size) {
12
+  void *m = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
13
+  if (m == MAP_FAILED) {
14
+    return NULL;
15
+  }
16
+  return m;
17
+}
18
+
19
+void error(char *msg) {
20
+  printf("=== ALLOCATOR ERROR ===\n%s\n====== END ERROR ======\n", msg);
21
+}
22
+
23
+struct AllocationRecord {
24
+  uintptr_t size;
25
+  void *allocation;
26
+  void *reference;
27
+  int id;
28
+  struct AllocationRecord *next;
29
+  struct AllocationRecord *prev;
30
+};
31
+
32
+void insert_record(struct AllocationRecord *dummy, void *allocation, uintptr_t size) {
33
+  struct AllocationRecord *record = malloc(sizeof(struct AllocationRecord));
34
+  record->size = size;
35
+  record->allocation = allocation;
36
+  record->reference = malloc(size);
37
+  record->next = dummy;
38
+  record->prev = dummy->prev;
39
+  record->id = dummy->id;
40
+  dummy->prev->next = record;
41
+  dummy->prev = record;
42
+  dummy->size += 1;
43
+  dummy->id += 1;
44
+  memcpy(record->reference, allocation, size);
45
+}
46
+
47
+void delete_record(struct AllocationRecord *dummy, struct AllocationRecord *delete) {
48
+  free(delete->reference);
49
+  delete->prev->next = delete->next;
50
+  delete->next->prev = delete->prev;
51
+  free(delete);
52
+  dummy->size -= 1;
53
+}
54
+
55
+struct AllocationRecord *get_record(struct AllocationRecord *dummy, int which) {
56
+  struct AllocationRecord *head = dummy->next;
57
+  while (which--) {
58
+    head = head->next;
59
+  }
60
+  return head;
61
+}
62
+
63
+int validate_record(struct AllocationRecord *record) {
64
+  if (memcmp(record->allocation, record->reference, record->size) != 0) {
65
+    return 0;
66
+  }
67
+  return 1;
68
+}
69
+
70
+void new_region(struct AllocationRecord *dummy, struct Arena *arena) {
71
+  uintptr_t size = 1 + (rand() % 8192);
72
+  uintptr_t align = 1 << (rand() % 4);
73
+  printf("alloc'ing a region of size %lu with alignment %lu, id %i\n", size, align, dummy->id);
74
+  void *region = alloc(arena, size, align);
75
+  if (region == NULL) {
76
+    printf("memory allocator broke, we have 1L though\n");
77
+    return;
78
+  }
79
+  for (int ii = 0; ii < size; ii++) {
80
+    ((char*) region)[ii] = (char) rand();
81
+  }
82
+  insert_record(dummy, region, size);
83
+}
84
+
85
+void delete_region(struct AllocationRecord *dummy, struct Arena *arena) {
86
+  if (dummy->size == 0) {
87
+    return;
88
+  }
89
+  uintptr_t which = rand() % dummy->size;
90
+  struct AllocationRecord *zap = get_record(dummy, which);
91
+  printf("dealloc'ing a region of size %lu, id %i\n", zap->size, zap->id);
92
+  unalloc(arena, zap->allocation);
93
+  delete_record(dummy, zap);
94
+}
95
+
96
+int act(struct AllocationRecord *dummy, struct Arena *arena) {
97
+  if (rand() & 1) {
98
+    new_region(dummy, arena);
99
+  } else {
100
+    delete_region(dummy, arena);
101
+  }
102
+  int die = 0;
103
+  struct AllocationRecord *head = dummy->next;
104
+  while (head != dummy) {
105
+    if (!validate_record(head)) {
106
+      //printf("validation failed at id %i\n", head->id);
107
+      die = 1;
108
+    }
109
+    head = head->next;
110
+  }
111
+  return die;
112
+}
2 113
 
3 114
 int main() {
4
-	printf("Hello, World!\n");
115
+  srand(1);
116
+  struct Arena arena = {
117
+    NULL,
118
+    NULL,
119
+    main_get_new_region,
120
+    error,
121
+  };
122
+  void *reg = alloc(&arena, 20, 4);
123
+  unalloc(&arena, reg);
124
+  struct AllocationRecord dummy = {
125
+    .size = 0,
126
+    .allocation = NULL,
127
+    .reference = NULL,
128
+    .id = 0,
129
+  };
130
+  dummy.next = &dummy;
131
+  dummy.prev = &dummy;
132
+  for (int ii = 0; ii < 100; ii++) {
133
+    act(&dummy, &arena);
134
+  }
5 135
 	return 0;
6 136
 }

+ 122
- 0
paper.tex View File

@@ -0,0 +1,122 @@
1
+\documentclass{article}
2
+
3
+\begin{document}
4
+\title{A Memory Allocator}
5
+\author{Cameron Weinfurt, Thomas Johnson}
6
+
7
+\maketitle
8
+
9
+\section{Introduction}
10
+
11
+When implementing the functionality of abstract data structures, it becomes necessary to have an array
12
+whose size is not known at compile time. This is problematic in languages like C and C++ where the
13
+size of an array must be known when compiling in order to calculate how bih the stack frame must be.
14
+The solution is to forgo having the array existing on the stack and not even dedicate space at compile
15
+time for the array. Instead, the array is created during runtime once the size of the array can be
16
+determined. This is known as dynamic memory allocation. A special region in a process's memory is
17
+dedicated for these kinds of allocations, called the heap, while a memory allocator keeps track of
18
+this space's usage. How the allocator handles memory that is no longer in use and situations in which
19
+a region must be resized depends on how it is implemented. In addition, the memory allocator has to
20
+balance how much memory that it uses for record keeping while also minimizing the amount of CPU cycles
21
+required to manage the memory region it has been given.
22
+
23
+In ANSI C, the standard library provides a memory allocator for general purpose use in programs.
24
+Dynamic memory allocation is performed through the {\tt malloc()} library call in which the caller
25
+passes the desired size of the allocation and it returns a pointer to the allocated memory region. It
26
+must be noted that malloc does not initalize the returned memory region with any value, so the caller
27
+must initalize the array itself. However, the standard library also provides the {\tt calloc()}
28
+library call in which the allocated memory is initalized with zero in every byte. Resizing of an
29
+allocation is performed using the {\tt realloc()} and {\tt reallocarray()} library calls, which will
30
+either grow the allocation in place or move the allocation to a space in which the new size can fit.
31
+The program must signify to the allocator that an memory region is to be marked free through the {\tt
32
+  free()} libary call. Should it fail to notify the allocator that an allocation is no longer in use
33
+  before all of its references go out of scope, it will become impossible to access the underlying
34
+  data or use the space it took up; a
35
+memory leak.
36
+
37
+Other languages' standard libraries can provide memory allocators or additional data structures for
38
+dynamic allocation using other techniques. Rather returning a pointer as a reference to the
39
+allocation, languages like C++ and Rust provide smart references that determines when the underlying
40
+memory allocation can be freed by detecting when all of its references have gone out of scope. Runtime
41
+languages like those running on the Java Virtual Machine or Microsoft's Common Language Interface also
42
+provide smart pointers, but wait to free the unused memory regions until a scheduled batch process
43
+occurs known as a garbage collection. Higher order languages like Python and Lua completely abstract
44
+away that dynamic allocation is occuring by only allowing abstract data structures to be created by
45
+the programmer, allowing for their implementation to handle the underlying allocations needed
46
+transparently. 
47
+
48
+In practice, abstract data structures do not allocate in the same way. It is possible to categorize
49
+them into two groups based on how they allocate. For data structures like vectors and smart strings, a
50
+single allocation is made at its creation and then resized as data is either added or removed. This is
51
+the first group. The second group consists of data structures like linked lists and trees, where many
52
+allocations and frees are requested, but each allocation is fixed in size. Rather than handling
53
+dynamic allocation request using one method, abstract data structures could instead give hints to the
54
+allocator as to what kind of allocations it should be making. Such an allocator could then be
55
+constructed to take advantage of these hints and optimize accordingly. This is what this paper aims to
56
+demonstrate. 
57
+
58
+\section{Implementation}
59
+
60
+\subsection{The Tree Allocator}
61
+
62
+Internally, the allocator is made of two self-balancing binary search trees in which one keeps records
63
+of free space available to the allocator while the other keeps records of allocated memory. Both trees
64
+are sorted using the size of their respective regions, though they can be search based on location in
65
+memory as well. Each node can be one of three types depending on their purpose. The type is used to
66
+determine which struct to represent the node with; the similar footprint permitting pointer
67
+polymorphism. A red-black tree was chosen to perform the self-balancing due to the minimal cost of
68
+adding a color field to each node. 
69
+
70
+To perform an allocation, the allocator first searches for within the free space tree for a memory
71
+block of suitable size. If it cannot find one, it requests the operating system for additional memory
72
+to mapped into the process before pushing the new space onto the tree and searching again. Once a node
73
+representing a memory region of sufficent size is found, it is removed from the free-space tree. The
74
+underlying space is then split to fit the new allocation and leave the excess space unallocated. The
75
+allocated space as a new node is pushed onto the allocations tree while the excess space also in new
76
+nodes are pushed back ono the free-space tree. In particular, the allocator attempts to place the new
77
+allocation in the center of the free space in order to minimize the chances of resizing causing a
78
+move. Deallocations are handled in a similar manner. When an address is requested to be freed, the
79
+allocator searches for the corresponding node in the allocations tree. This node is then popped off
80
+the allocations tree and pushed onto the free-space tree. In addition, if it is found that this node
81
+is surrounded by unallocated memory after being pushed onto the free-space tree, it will merge the
82
+nodes together. This keeps fragmentation at a minimum and speeds up subsequent allocations and
83
+deallocation. 
84
+
85
+\subsection{The Watermark Allocator}
86
+
87
+On its own, the watermark allocator present many problems that make is unfeasible to use as an
88
+allocator. This model of allocator is simply a stack that cannot have elements popped off of it while
89
+also holding a reference counter. The obvious problem with this is that frees ultimately are leaks
90
+under a watermark allocator. Unrestricted, a watermark allocator will eventually run out of memory
91
+even though free space may exist behind its stack pointer. This simplistic model does not come without
92
+its benefits; however, being that an allocation requires very little overhead and metadata to handle.
93
+The solution that was derived to take advantage of this property was to use the tree allocator to
94
+manage a series of finite sized watermark allocators. The implementation does not create an instance
95
+of a watermark allocator until a request for a fixed sized allocation is made. It is limited to a
96
+space of 4096 bytes, enforcing that the allocator be used for small, fixed size allocations. Larger
97
+allocations will either fail or be allocated using the tree allocator instead. Should an allocator run
98
+out of space, a new one is created and the allocation is performed on that new allocator. In addition,
99
+    it is stored as a node within the tree allocator, meaning the last reference to the memory region
100
+    will be the global allocator itself, which will free the space through the tree allocator
101
+    automatically when the reference count on the space goes to zero. 
102
+
103
+\section{Results}
104
+
105
+% TODO: Add figures the LaTeX way
106
+To evaluate the effectiveness of the allocator, glibc's malloc was used as a benchmark.
107
+
108
+Tree alloc vs. Watermark alloc vs. Glibc Malloc with repeated allocs of size 20:
109
+
110
+Tree alloc vs. Glibc Malloc with repeated allocs of size 8000:
111
+
112
+%## Tree alloc vs. Watermark alloc vs Glibc Malloc with repeated frees of size 20:
113
+
114
+%## Tree alloc vs. Glibc Malloc with repeated frees of size 8000:
115
+
116
+%## Tree alloc vs. Glibc Realloc with repeatedly resizing allocations.
117
+
118
+A set of 3 allocations were made of size 20. They were doubled in size repeatedly in sequence of their allocation.
119
+
120
+\section{Conclusion}
121
+
122
+\end{document}

+ 649
- 24
tree_alloc.c View File

@@ -1,44 +1,669 @@
1 1
 
2 2
 #include <stddef.h> 
3
+#include <stdalign.h>
3 4
 
4 5
 #include "allocator_internal.h"
5 6
 
6
-TreeAlloc *insert_node_at(void *address, int padding, int align, int size) {
7
+#define IS_BLACK_NODE(n) (n == NULL || n->color == COLOR_BLACK)
8
+#define IS_RED_NODE(n) (n != NULL && n->color == COLOR_RED)
9
+
10
+#ifdef DEBUG
11
+#include <stdio.h>
12
+
13
+int debug_tree_black_height(TreeAlloc *node) {
14
+	if (node == NULL) {
15
+		return 1;
16
+	}
17
+	return (IS_BLACK_NODE(node) ? 1 : 0) + debug_tree_black_height(node->left);
18
+}
19
+
20
+void debug_print_node(int indent, TreeAlloc *node, int bad) {
21
+	for (int ii = 0; ii < indent; ii++) 
22
+		printf("  ");
23
+
24
+	if (IS_RED_NODE(node)) 
25
+		printf("\e[31m");
26
+	else if (bad) 
27
+		printf("\e[30m]");
28
+
29
+	if (bad)
30
+		printf("\e[43m");
31
+
32
+	if (node)
33
+		printf("%p %lu\n", node, node->size);
34
+	else
35
+		printf("(nil) 0\n");
36
+	printf("\e[37m");
37
+
38
+	if (bad) 
39
+		printf("\e[40m");
40
+
41
+}
42
+
43
+void debug_print_tree(int indent, void *p, int safe) {
44
+	TreeAlloc *node = (TreeAlloc*) p;
45
+	if (node != NULL) {
46
+		int bad = debug_tree_black_height(node->left) != debug_tree_black_height(node->right);
47
+		bad |= IS_RED_NODE(node) && (
48
+				IS_RED_NODE(node->left) || 
49
+				IS_RED_NODE(node->right) || 
50
+				IS_RED_NODE(node->parent) );
51
+		bad &= !safe;
52
+		debug_print_tree(indent + 1, node->left, safe);
53
+		debug_print_node(indent, node, bad);
54
+		debug_print_tree(indent + 1, node->right, safe);
55
+	}
56
+}
57
+
58
+#endif
59
+
60
+TreeAlloc *insert_node_at(void *address, uintptr_t padding, uintptr_t align, uintptr_t size) {
7 61
 	return NULL;
8 62
 }
9 63
 
64
+/*
65
+ * Search for the node whose allocated region contains an address.
66
+ */
10 67
 TreeAlloc *search_by_address(TreeAlloc *root, void *address) {
68
+	TreeAlloc *head = root;
11 69
 	while (1) {
12
-		if (root < address) {
13
-			if (root->left)
14
-				root = root->left;
15
-			else
16
-				return root;
17
-		} else if (root > address) {
18
-			if (root->right)
19
-				root = root->right;
20
-			else
21
-				return root;
70
+		void *region_start = head;
71
+		void *region_end = head + head->size;
72
+		if (address < region_start) {
73
+			// The requested address is before this region's start.
74
+			if (head->left) {
75
+				// There is another region that comes before this one
76
+				// in memory.
77
+				head = head->left;
78
+			} else {
79
+				// This address is before any of the allocated regions.
80
+				return NULL;
81
+			}
82
+		} else if (address <= region_end) {
83
+			// The requested address is within the range of this region.
84
+			return head;
22 85
 		} else {
23
-			return root;
86
+			// The requested address is after this region's end.
87
+			if (head->right) {
88
+				// There is another region that comes after this one
89
+				// in memory.
90
+				head = head->right;
91
+			} else {
92
+				// This address is after any of the allocated regions.
93
+				return NULL;
94
+			}
24 95
 		}
25 96
 	}
26 97
 }
27 98
 
28
-TreeAlloc *search_by_size(TreeAlloc *root, int padding, int align, int size) {
99
+static uintptr_t effective_size(TreeAlloc *head, uintptr_t padding, uintptr_t align) {
100
+	return head->size - (align_after(head + padding, align) - (void*) head);
101
+}
102
+
103
+/* 
104
+ * This is the most optimistic estimate of size that we can use which also preserves the ordering over
105
+ * the tree. I had planned to use effective_size before I realized that it would break the tree
106
+ * ordering.
107
+ */
108
+static uintptr_t pessimistic_size(TreeAlloc *head, uintptr_t padding, uintptr_t align) {
109
+	return head->size - padding - align + 1;
110
+}
111
+
112
+TreeAlloc *search_by_size(TreeAlloc *root, uintptr_t padding, uintptr_t align, uintptr_t size) {
113
+	TreeAlloc *head = root;
29 114
 	while (1) {
30
-		if (root->size < size) {
31
-			if (root->left)
32
-				root = root->left;
33
-			else
34
-				return root;
35
-		} else if (root->size > address) {
36
-			if (root->right)
37
-				root = root->right;
38
-			else
39
-				return root;
115
+		uintptr_t esize = pessimistic_size(head, padding, align);
116
+		if (esize <= size) {
117
+			if (head->right == NULL) {
118
+				return NULL;
119
+			} else {
120
+				head = head->right;
121
+			}
122
+		} else {
123
+			if (head->left == NULL || pessimistic_size(head->left, padding, align) < size) {
124
+				return head;
125
+			} else {
126
+				head = head->left;
127
+			}
128
+		}
129
+	}
130
+}
131
+
132
+TreeAlloc *succ(TreeAlloc *el) {
133
+	if (el->right != NULL) {
134
+		el = el->right;
135
+		while (el->left != NULL) {
136
+			el = el->left;
137
+		}
138
+		return el;
139
+	}
140
+	while (el->parent != NULL && el == el->parent->right) {
141
+		el = el->parent;
142
+	}
143
+	return el->parent;
144
+}
145
+
146
+TreeAlloc *pred(TreeAlloc *el) {
147
+	if (el->left != NULL) {
148
+		el = el->left;
149
+		while (el->right != NULL) {
150
+			el = el->right;
151
+		}
152
+		return el;
153
+	}
154
+	while (el->parent != NULL && el == el->parent->left) {
155
+		el = el->parent;
156
+	}
157
+	return el->parent;
158
+}
159
+
160
+TreeAlloc *get_sibling(TreeAlloc *p, TreeAlloc *ta) {
161
+	if (!p)
162
+		return NULL;
163
+	else if (p->left == ta)
164
+		return p->right;
165
+	else
166
+		return p->left;
167
+}
168
+
169
+void rotate_left(TreeAlloc **root_ptr, TreeAlloc *ta) {
170
+	TreeAlloc *parent, *tmp;
171
+
172
+	tmp = ta->right;
173
+	parent = ta->parent;
174
+
175
+	if (tmp) {
176
+		ta->right = tmp->left;
177
+		tmp->left = ta;
178
+		tmp->parent = parent;
179
+	}
180
+
181
+	if (ta->right) ta->right->parent = ta;
182
+	ta->parent = tmp;
183
+
184
+	if (!parent) {
185
+		*root_ptr = tmp;
186
+	} else if (ta == parent->left) {
187
+		parent->left = tmp;
188
+	} else {
189
+		parent->right = tmp;
190
+	}
191
+}
192
+
193
+void rotate_right(TreeAlloc **root_ptr, TreeAlloc *ta) {
194
+	TreeAlloc *parent, *tmp;
195
+
196
+	tmp = ta->left;
197
+	parent = ta->parent;
198
+
199
+	if (tmp) {
200
+		ta->left = tmp->right;
201
+		tmp->right = ta;
202
+		tmp->parent = parent;
203
+	}
204
+
205
+	if (ta->left) ta->left->parent = ta;
206
+	ta->parent = tmp;
207
+
208
+	if (!parent) {
209
+		*root_ptr = tmp;
210
+	} else if (ta == parent->left) {
211
+		parent->left = tmp;
212
+	} else {
213
+		parent->right = tmp;
214
+	}
215
+}
216
+
217
+void repair_tree_after_insert(TreeAlloc **root_ptr, TreeAlloc *ta) {
218
+	TreeAlloc *parent = ta->parent;
219
+	if (ta == *root_ptr) {
220
+		ta->color = COLOR_BLACK;
221
+	} else if (IS_BLACK_NODE(parent)) {
222
+		return;
223
+	} else {
224
+		TreeAlloc *uncle = get_sibling(parent->parent, parent);
225
+		TreeAlloc *grandparent = parent->parent;
226
+
227
+		if (IS_RED_NODE(uncle)) {
228
+			parent->color = COLOR_BLACK;
229
+			uncle->color = COLOR_BLACK;
230
+			grandparent->color = COLOR_RED; 
231
+			repair_tree_after_insert(root_ptr, grandparent);
40 232
 		} else {
41
-			return root;
233
+			if (parent->left == ta) {
234
+				if (grandparent->left == parent) {
235
+					rotate_right(root_ptr, grandparent);
236
+					grandparent->color = COLOR_RED;
237
+					parent->color = COLOR_BLACK;
238
+				} else {
239
+					rotate_right(root_ptr, parent);
240
+					rotate_left(root_ptr, grandparent);
241
+					grandparent->color = COLOR_RED;
242
+					ta->color = COLOR_BLACK;
243
+				}
244
+			} else {
245
+				if (grandparent->left == parent) {
246
+					rotate_left(root_ptr, parent);
247
+					rotate_right(root_ptr, grandparent);
248
+					grandparent->color = COLOR_RED;
249
+					ta->color = COLOR_BLACK;
250
+				} else {
251
+					rotate_left(root_ptr, grandparent);
252
+					grandparent->color = COLOR_RED;
253
+					parent->color = COLOR_BLACK;
254
+				}
255
+			}
42 256
 		}
43 257
 	}
258
+
44 259
 }
260
+// Inserts a node into an empty tree.
261
+void insert_singleton(TreeAlloc **root_ptr, TreeAlloc *to_insert) {
262
+#ifdef DEBUG
263
+	printf("= PRE-INSERT-SINGLETON =\n");
264
+	printf("===== CURRENT TREE =====\n");
265
+	debug_print_tree(0, *root_ptr, 0);
266
+	printf("===== END OF TREES =====\n");
267
+#endif
268
+	*root_ptr = to_insert;
269
+	to_insert->parent = NULL;
270
+	to_insert->color = COLOR_BLACK;
271
+#ifdef DEBUG
272
+	printf("= POST-INSERT-SINGLETON =\n");
273
+	printf("===== CURRENT TREE =====\n");
274
+	debug_print_tree(0, *root_ptr, 0);
275
+	printf("===== END OF TREES =====\n");
276
+#endif
277
+}
278
+
279
+void insert_by_size(TreeAlloc** root_ptr, TreeAlloc* to_insert) {
280
+#ifdef DEBUG
281
+	printf("=== PRE-INSERT-BY-SIZE ===\n");
282
+	printf("===== INSERTING =====\n");
283
+	debug_print_node(0, to_insert, 0);
284
+	printf("===== CURRENT TREE =====\n");
285
+	debug_print_tree(0, *root_ptr, 0);
286
+	printf("===== END OF TREES =====\n");
287
+#endif
288
+	TreeAlloc *tree_ptr = *root_ptr;
289
+	while (1) {
290
+		if (to_insert->size < tree_ptr->size) {
291
+			if (tree_ptr->left) {
292
+				tree_ptr = tree_ptr->left;
293
+			} else {
294
+				tree_ptr->left = to_insert;
295
+				to_insert->parent = tree_ptr;
296
+				break;
297
+			}
298
+		} else {
299
+			if (tree_ptr->right) {
300
+				tree_ptr = tree_ptr->right;
301
+			} else {
302
+				tree_ptr->right = to_insert;
303
+				to_insert->parent = tree_ptr;
304
+				break;
305
+			}
306
+		}
307
+	}
308
+	to_insert->color = COLOR_RED;
309
+	repair_tree_after_insert(root_ptr, to_insert);
310
+#ifdef DEBUG
311
+	printf("== POST-INSERT-FIXUP ===\n");
312
+	printf("===== CURRENT TREE =====\n");
313
+	debug_print_tree(0, *root_ptr, 0);
314
+	printf("===== END OF TREES =====\n");
315
+#endif
316
+}
317
+
318
+void insert_by_addr(TreeAlloc** root_ptr, TreeAlloc* to_insert) {
319
+#ifdef DEBUG
320
+	printf("=== PRE-INSERT-BY-ADDR ===\n");
321
+	printf("===== INSERTING =====\n");
322
+	debug_print_tree(0, to_insert, 0);
323
+	printf("===== CURRENT TREE =====\n");
324
+	debug_print_tree(0, *root_ptr, 0);
325
+	printf("===== END OF TREES =====\n");
326
+#endif
327
+	TreeAlloc *tree_ptr = *root_ptr;
328
+	while (1) {
329
+		if (to_insert < tree_ptr) {
330
+			if (tree_ptr->left) {
331
+				tree_ptr = tree_ptr->left;
332
+			} else {
333
+				tree_ptr->left = to_insert;
334
+				to_insert->parent = tree_ptr;
335
+				break;
336
+			}
337
+		} else {
338
+			if (tree_ptr->right) {
339
+				tree_ptr = tree_ptr->right;
340
+			} else {
341
+				tree_ptr->right = to_insert;
342
+				to_insert->parent = tree_ptr;
343
+				break;
344
+			}
345
+		}
346
+	}
347
+	to_insert->color = COLOR_RED;
348
+	repair_tree_after_insert(root_ptr, to_insert);
349
+#ifdef DEBUG
350
+	printf("== POST-INSERT-FIXUP ===\n");
351
+	printf("===== CURRENT TREE =====\n");
352
+	debug_print_tree(0, *root_ptr, 0);
353
+	printf("===== END OF TREES =====\n");
354
+#endif
355
+}
356
+
357
+void replace_node(TreeAlloc **root_ptr, TreeAlloc *node, TreeAlloc *replace) {
358
+	if (!node->parent) {
359
+		*root_ptr = replace;
360
+	} else {
361
+		if (node == node->parent->left)
362
+			node->parent->left = replace;
363
+		else 
364
+			node->parent->right = replace;
365
+	}
366
+	if (replace) replace->parent = node->parent;
367
+}
368
+
369
+void repair_after_remove(TreeAlloc **root_ptr, TreeAlloc *parent, TreeAlloc *node) {
370
+#ifdef DEBUG
371
+	printf("delete fixup at %p -> %p\n", parent, node);
372
+#endif
373
+	// In theory, the last two conditions should be the same ...
374
+	if (IS_RED_NODE(node) || (node != NULL && node == *root_ptr)) {
375
+		node->color = COLOR_BLACK;
376
+	} else {
377
+		TreeAlloc *sibling = get_sibling(parent, node);
378
+		if (IS_RED_NODE(sibling)) {
379
+			if (parent->left == node) {
380
+				rotate_left(root_ptr, parent);
381
+			} else {
382
+				rotate_right(root_ptr, parent);
383
+			}
384
+			// The rotate shouldn't touch the parent relationship of `node`
385
+			parent->parent->color = COLOR_BLACK;
386
+			parent->color = COLOR_RED;
387
+			sibling = get_sibling(parent, node);
388
+		} 
389
+
390
+		if (IS_BLACK_NODE(sibling->left) && IS_BLACK_NODE(sibling->right)) {
391
+			if (node != NULL)
392
+				node->color = COLOR_BLACK;
393
+			sibling->color = COLOR_RED;
394
+			repair_after_remove(root_ptr, parent->parent, parent);
395
+		} else {
396
+			if (parent->left == node && IS_BLACK_NODE(sibling->right)) {
397
+				rotate_right(root_ptr, sibling);
398
+				sibling->color = COLOR_RED;
399
+				sibling = parent->right;
400
+				sibling->color = COLOR_BLACK;
401
+			}
402
+			if (parent->right == node && IS_BLACK_NODE(sibling->left)) {
403
+				rotate_left(root_ptr, sibling);
404
+				sibling->color = COLOR_RED;
405
+				sibling = parent->left;
406
+				sibling->color = COLOR_BLACK;
407
+			}
408
+			if (parent->left == node) {
409
+				rotate_left(root_ptr, parent);
410
+			} else {
411
+				rotate_right(root_ptr, parent);
412
+			}
413
+			if (node != NULL)
414
+				node->color = COLOR_BLACK;
415
+			TreeAlloc *uncle = get_sibling(parent->parent, parent);
416
+			if (uncle != NULL)
417
+				uncle->color = COLOR_BLACK;
418
+			char swap = parent->color;
419
+			parent->color = parent->parent->color;
420
+			parent->parent->color = swap;
421
+		}
422
+	}
423
+}
424
+
425
+void remove_node(TreeAlloc **root_ptr, TreeAlloc *to_remove) {
426
+	char do_repair = 0;
427
+	char old_color;
428
+#ifdef DEBUG
429
+	printf("====== PRE-REMOVE ======\n");
430
+	printf("======= REMOVING =======\n");
431
+	debug_print_node(0, to_remove, 0);
432
+	printf("===== CURRENT TREE =====\n");
433
+	debug_print_tree(0, *root_ptr, 0);
434
+	printf("===== END OF TREES =====\n");
435
+#endif
436
+	TreeAlloc *replace, *parent_of_replace;
437
+	TreeAlloc *parent = to_remove->parent;
438
+	if (!to_remove->left) {
439
+#ifdef DEBUG
440
+		printf("code path 1l\n");
441
+#endif
442
+		replace = to_remove->right;
443
+		parent_of_replace = to_remove->parent;
444
+		do_repair = to_remove->color == COLOR_BLACK;
445
+		replace_node(root_ptr, to_remove, replace);
446
+	} else if (!to_remove->right) {
447
+#ifdef DEBUG
448
+		printf("code path 1r\n");
449
+#endif
450
+		replace = to_remove->left;
451
+		parent_of_replace = to_remove->parent;
452
+		do_repair = to_remove->color == COLOR_BLACK;
453
+		replace_node(root_ptr, to_remove, replace);
454
+	} else {
455
+#ifdef DEBUG
456
+		printf("code path 2\n");
457
+#endif
458
+		TreeAlloc *tmp = succ(to_remove);
459
+		replace = tmp->right;
460
+		do_repair = tmp->color == COLOR_BLACK;
461
+		if (tmp != to_remove->right) {
462
+			replace_node(root_ptr, tmp, replace);
463
+			tmp->right = to_remove->right;
464
+			to_remove->right->parent = tmp;
465
+			parent_of_replace = tmp->parent;
466
+		} else {
467
+			parent_of_replace = tmp;
468
+		}
469
+		replace_node(root_ptr, to_remove, tmp);
470
+		tmp->color = to_remove->color;
471
+		tmp->left = to_remove->left;
472
+		to_remove->left->parent = tmp;
473
+	}
474
+
475
+	// Make sure that it doesn't have any tree pointers it shouldn't have.
476
+	to_remove->parent = to_remove->left = to_remove->right = NULL;
477
+
478
+#ifdef DEBUG
479
+	printf("==== PRE-REMOVE-FIXUP ===\n");
480
+	printf("===== CURRENT TREE =====\n");
481
+	debug_print_tree(0, *root_ptr, 1);
482
+	printf("===== END OF TREES =====\n");
483
+	printf("considering fixing up %p -> %p\n", parent_of_replace, replace);
484
+#endif
485
+	if (replace && parent_of_replace == NULL) {
486
+		replace->color = COLOR_BLACK;
487
+	} else if (parent_of_replace != NULL && do_repair) {
488
+		repair_after_remove(root_ptr, parent_of_replace, replace);
489
+	}
490
+#ifdef DEBUG
491
+	printf("=== POST-REMOVE ===\n");
492
+	printf("===== CURRENT TREE =====\n");
493
+	debug_print_tree(0, *root_ptr, 0);
494
+	printf("===== END OF TREES =====\n");
495
+#endif
496
+}
497
+
498
+TreeAlloc *get_new_region(Arena *arena, uintptr_t size, uintptr_t padding, uintptr_t align) {
499
+	uintptr_t realsize = size + align + alignof(WatermarkAlloc) + padding - 1;
500
+#ifdef DEBUG
501
+	printf("Attemping request of size %ld\n", realsize);
502
+#endif 
503
+	if (realsize < MIN_NEW_MEM_SIZE) {
504
+		realsize = MIN_NEW_MEM_SIZE;
505
+	}
506
+	TreeAlloc *reg = (TreeAlloc *) arena->get_new_region(realsize);
507
+	if (reg == NULL) {
508
+		arena->error("can't allocate a new memory region!");
509
+	} else {
510
+		reg->parent = NULL;
511
+		reg->left = NULL;
512
+		reg->right = NULL;
513
+		reg->before = NULL;
514
+		reg->after = NULL;
515
+		reg->size = realsize;
516
+	}
517
+	return reg;
518
+}
519
+
520
+void unalloc(Arena *arena, void *addr) {
521
+#ifdef DEBUG
522
+	printf("==== UNALLOCATING ====\n");
523
+	printf("=== FREESPACE TREE ===\n");
524
+	debug_print_tree(0, arena->root_freespace, 0);
525
+	printf("=== TREEALLOC TREE ===\n");
526
+	debug_print_tree(0, arena->root_treealloc, 0);
527
+	printf("==== END OF TREES ====\n");
528
+#endif
529
+	if (arena->root_treealloc == NULL) {
530
+		arena->error("attempt to unallocate when there are no allocations!");
531
+		return;
532
+	}
533
+	// Find the node this address belongs to
534
+	TreeAlloc *node = search_by_address(arena->root_treealloc, addr);
535
+	if (node == NULL) {
536
+		arena->error("attempt to free memory outside any allocations!");
537
+		return;
538
+	}
539
+	// Handle the watermark allocator in this region
540
+	if (node->type == RT_WATERMARK) {
541
+		// TODO: handle watermark deallocation
542
+		return;
543
+	}
544
+	// Get rid of it
545
+	remove_node(&arena->root_treealloc, node);
546
+	// If there's free space on either side of it, merge it with the free space into a bigger chunk of
547
+	// free space.
548
+	uintptr_t size = node->size;
549
+	FreeSpace *start = (FreeSpace*) node;
550
+	if (node->before != NULL && node->before->type == RT_FREESPACE) {
551
+		start = (FreeSpace*) node->before;
552
+		size += node->before->size;
553
+		remove_node((TreeAlloc**) &arena->root_freespace, node->before);
554
+	}
555
+	if (node->after != NULL && node->after->type == RT_FREESPACE) {
556
+		size += node->after->size;
557
+		remove_node((TreeAlloc**) &arena->root_freespace, node->after);
558
+	}
559
+	start->type = RT_FREESPACE;
560
+	start->size = size;
561
+	// And finally, insert the resulting free space.
562
+	if (arena->root_freespace == NULL) {
563
+		insert_singleton((TreeAlloc**) &arena->root_freespace, (TreeAlloc*) start);
564
+	} else {
565
+		TreeAlloc *insert_point = search_by_size((TreeAlloc*) arena->root_freespace, 0, 1, size);
566
+		if (insert_point == NULL) {
567
+			insert_by_size((TreeAlloc**) &arena->root_freespace, (TreeAlloc*) start);
568
+		} else {
569
+			insert_by_size((TreeAlloc**) &arena->root_freespace, (TreeAlloc*) start);
570
+		}
571
+	}
572
+}
573
+
574
+void *alloc(Arena *arena, uintptr_t size, uintptr_t align) {
575
+	uintptr_t actual_align = lcm(alignof(struct WatermarkAlloc), align);
576
+
577
+#ifdef DEBUG
578
+	printf("==== ALLOCATING =====\n");
579
+	printf("=== FREESPACE TREE ===\n");
580
+	debug_print_tree(0, arena->root_freespace, 0);
581
+	printf("=== TREEALLOC TREE ===\n");
582
+	debug_print_tree(0, arena->root_treealloc, 0);
583
+	printf("==== END OF TREES ====\n");
584
+#endif
585
+
586
+	TreeAlloc *region;
587
+	if (arena->root_freespace == NULL) {
588
+		// Handle being out of freespace.
589
+#ifdef DEBUG
590
+		printf("Out of freespace nodes; getting more\n");
591
+#endif
592
+		region = get_new_region(arena, size, sizeof(TreeAlloc), actual_align);
593
+	} else {
594
+		region = search_by_size((TreeAlloc*) arena->root_freespace, sizeof(TreeAlloc), actual_align, size);
595
+		if (region == NULL) {
596
+			// Handle insufficient freespace or fragmentation.
597
+#ifdef DEBUG
598
+			printf("Out of sufficiently large freespace nodes; getting more\n");
599
+#endif
600
+			region = get_new_region(arena, size, sizeof(TreeAlloc), actual_align);
601
+		} else {
602
+			remove_node((TreeAlloc**) &arena->root_freespace, region);
603
+		}
604
+	}
605
+
606
+	void *true_end = align_after(align_after(((void*) region) + sizeof(TreeAlloc), actual_align) + size, alignof(WatermarkAlloc));
607
+	// The size of the new allocation (adjusted for region header and alignment
608
+	uintptr_t new_size = true_end - (void*) region;
609
+	// The size of the free space region following the new allocation
610
+	uintptr_t new_free_size = region->size - new_size;
611
+	region->right = NULL;
612
+	region->left = NULL;
613
+	region->type = RT_TREE_NODE;
614
+	region->size = size;
615
+
616
+#ifdef DEBUG
617
+	printf("start: %p, end: %p, adjusted end: %p\n", region, ((void*) region) + size, true_end);
618
+	printf("size: %lu -> %lu\n", size, new_size);
619
+	printf("new_free_size: %lu\n", new_free_size);
620
+#endif
621
+
622
+	if (arena->root_treealloc == NULL) {
623
+		insert_singleton((TreeAlloc**) &arena->root_treealloc, region);
624
+	} else {
625
+		insert_by_addr(&arena->root_treealloc, region);
626
+	}
627
+	if (region->size >= new_size + sizeof(FreeSpace)) {
628
+		// If there's enough free space after the allocation, use it!
629
+		region->size = new_size;  // Safe because the allocated region tree is not sorted by size.
630
+		FreeSpace *new_free = (FreeSpace*) ((void*) region + new_size);
631
+		new_free->left = NULL;
632
+		new_free->right = NULL;
633
+		new_free->parent = NULL;
634
+		new_free->type = RT_FREESPACE;
635
+		new_free->size = new_free_size;
636
+		if (arena->root_freespace == NULL) {
637
+			insert_singleton((TreeAlloc**) &arena->root_freespace, (TreeAlloc*) new_free);
638
+		} else {
639
+			insert_by_size((TreeAlloc**) &arena->root_freespace, (TreeAlloc*) new_free);
640
+		}
641
+		// Set the region following this one to be the new free space
642
+		region->after = (TreeAlloc*) new_free;
643
+	} else {
644
+		// There isn't a free space after this one, so put the `next` pointer at the next allocated
645
+		// region if there is one.
646
+		region->after = search_by_address((TreeAlloc *) &arena->root_treealloc, region + region->size + 1);
647
+	}
648
+
649
+	// Are there any allocations before this one?
650
+	region->before = search_by_address((TreeAlloc *) &arena->root_treealloc, region - 1);
651
+
652
+#ifdef DEBUG
653
+	printf("region is still at %p\n", region);
654
+	printf("=== POST-ALLOCATION ===\n");
655
+	printf("=== FREESPACE TREE ===\n");
656
+	debug_print_tree(0, arena->root_freespace, 0);
657
+	printf("=== TREEALLOC TREE ===\n");
658
+	debug_print_tree(0, arena->root_treealloc, 0);
659
+	printf("==== END OF TREES ====\n");
660
+#endif
661
+	return align_after((void*) region + sizeof(TreeAlloc), actual_align);
662
+}
663
+
664
+void *alloc_growable(Arena *arena, uintptr_t size, uintptr_t align) {
665
+	// TODO: Basically the same as above, but put the allocated region in the center of the largest free
666
+	// space. Due to alignment and whatnot, the code will be gory.
667
+	return NULL;
668
+}
669
+

+ 30
- 3
util.c View File

@@ -1,13 +1,40 @@
1 1
 
2 2
 #include <stdint.h>
3 3
 
4
+#ifdef DEBUG
5
+#include <stdio.h>
6
+#endif
7
+
4 8
 void* align_after(void* address, int align) {
5 9
   uintptr_t addr = (uintptr_t) address;
6 10
   uintptr_t offset = addr % align;
7
-  if offset == 0 {
8
-    return address;
11
+  void *rv;
12
+  if (offset == 0) {
13
+    rv = address;
9 14
   } else {
10
-    return (void*) (addr + align - align % offset);
15
+    rv = (void*) (addr + align - align % offset);
16
+  }
17
+#ifdef DEBUG
18
+  printf("aligning %p at alignment %x to %p\n", address, align, rv);
19
+#endif
20
+  return rv;
21
+}
22
+
23
+uintptr_t gcd(uintptr_t a, uintptr_t b) {
24
+  if (b > a) {
25
+    return gcd(b, a);
11 26
   }
27
+  if (b == 0) {
28
+    return a;
29
+  }
30
+  return gcd(b, a % b);
31
+}
32
+
33
+uintptr_t lcm(uintptr_t a, uintptr_t b) {
34
+  uintptr_t lcm = (a / gcd(a, b)) * b;
35
+#ifdef DEBUG
36
+  printf("so apparently lcm(%lu, %lu) = %lu\n", a, b, lcm);
37
+#endif
38
+  return lcm;
12 39
 }
13 40
 

Loading…
Cancel
Save