xref: /llvm-project/libc/src/__support/freelist_heap.h (revision 60de7dc886b9d83b0e2b6c9d7b73173d5d870388)
1 //===-- Interface for freelist_heap ---------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef LLVM_LIBC_SRC___SUPPORT_FREELIST_HEAP_H
10 #define LLVM_LIBC_SRC___SUPPORT_FREELIST_HEAP_H
11 
12 #include <stddef.h>
13 
14 #include "block.h"
15 #include "freestore.h"
16 #include "src/__support/CPP/optional.h"
17 #include "src/__support/CPP/span.h"
18 #include "src/__support/libc_assert.h"
19 #include "src/__support/macros/config.h"
20 #include "src/__support/math_extras.h"
21 #include "src/string/memory_utils/inline_memcpy.h"
22 #include "src/string/memory_utils/inline_memset.h"
23 
24 namespace LIBC_NAMESPACE_DECL {
25 
26 extern "C" cpp::byte _end;
27 extern "C" cpp::byte __llvm_libc_heap_limit;
28 
29 using cpp::optional;
30 using cpp::span;
31 
32 LIBC_INLINE constexpr bool IsPow2(size_t x) { return x && (x & (x - 1)) == 0; }
33 
34 class FreeListHeap {
35 public:
36   constexpr FreeListHeap() : begin(&_end), end(&__llvm_libc_heap_limit) {}
37 
38   constexpr FreeListHeap(span<cpp::byte> region)
39       : begin(region.begin()), end(region.end()) {}
40 
41   void *allocate(size_t size);
42   void *aligned_allocate(size_t alignment, size_t size);
43   // NOTE: All pointers passed to free must come from one of the other
44   // allocation functions: `allocate`, `aligned_allocate`, `realloc`, `calloc`.
45   void free(void *ptr);
46   void *realloc(void *ptr, size_t size);
47   void *calloc(size_t num, size_t size);
48 
49   cpp::span<cpp::byte> region() const { return {begin, end}; }
50 
51 private:
52   void init();
53 
54   void *allocate_impl(size_t alignment, size_t size);
55 
56   span<cpp::byte> block_to_span(Block *block) {
57     return span<cpp::byte>(block->usable_space(), block->inner_size());
58   }
59 
60   bool is_valid_ptr(void *ptr) { return ptr >= begin && ptr < end; }
61 
62   cpp::byte *begin;
63   cpp::byte *end;
64   bool is_initialized = false;
65   FreeStore free_store;
66 };
67 
68 template <size_t BUFF_SIZE> class FreeListHeapBuffer : public FreeListHeap {
69 public:
70   constexpr FreeListHeapBuffer() : FreeListHeap{buffer}, buffer{} {}
71 
72 private:
73   cpp::byte buffer[BUFF_SIZE];
74 };
75 
76 LIBC_INLINE void FreeListHeap::init() {
77   LIBC_ASSERT(!is_initialized && "duplicate initialization");
78   auto result = Block::init(region());
79   Block *block = *result;
80   free_store.set_range({0, cpp::bit_ceil(block->inner_size())});
81   free_store.insert(block);
82   is_initialized = true;
83 }
84 
85 LIBC_INLINE void *FreeListHeap::allocate_impl(size_t alignment, size_t size) {
86   if (size == 0)
87     return nullptr;
88 
89   if (!is_initialized)
90     init();
91 
92   size_t request_size = Block::min_size_for_allocation(alignment, size);
93   if (!request_size)
94     return nullptr;
95 
96   Block *block = free_store.remove_best_fit(request_size);
97   if (!block)
98     return nullptr;
99 
100   auto block_info = Block::allocate(block, alignment, size);
101   if (block_info.next)
102     free_store.insert(block_info.next);
103   if (block_info.prev)
104     free_store.insert(block_info.prev);
105 
106   block_info.block->mark_used();
107   return block_info.block->usable_space();
108 }
109 
110 LIBC_INLINE void *FreeListHeap::allocate(size_t size) {
111   return allocate_impl(alignof(max_align_t), size);
112 }
113 
114 LIBC_INLINE void *FreeListHeap::aligned_allocate(size_t alignment,
115                                                  size_t size) {
116   // The alignment must be an integral power of two.
117   if (!IsPow2(alignment))
118     return nullptr;
119 
120   // The size parameter must be an integral multiple of alignment.
121   if (size % alignment != 0)
122     return nullptr;
123 
124   // The minimum alignment supported by Block is max_align_t.
125   alignment = cpp::max(alignment, alignof(max_align_t));
126 
127   return allocate_impl(alignment, size);
128 }
129 
130 LIBC_INLINE void FreeListHeap::free(void *ptr) {
131   cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
132 
133   LIBC_ASSERT(is_valid_ptr(bytes) && "Invalid pointer");
134 
135   Block *block = Block::from_usable_space(bytes);
136   LIBC_ASSERT(block->next() && "sentinel last block cannot be freed");
137   LIBC_ASSERT(block->used() && "double free");
138   block->mark_free();
139 
140   // Can we combine with the left or right blocks?
141   Block *prev_free = block->prev_free();
142   Block *next = block->next();
143 
144   if (prev_free != nullptr) {
145     // Remove from free store and merge.
146     free_store.remove(prev_free);
147     block = prev_free;
148     block->merge_next();
149   }
150   if (!next->used()) {
151     free_store.remove(next);
152     block->merge_next();
153   }
154   // Add back to the freelist
155   free_store.insert(block);
156 }
157 
158 // Follows constract of the C standard realloc() function
159 // If ptr is free'd, will return nullptr.
160 LIBC_INLINE void *FreeListHeap::realloc(void *ptr, size_t size) {
161   if (size == 0) {
162     free(ptr);
163     return nullptr;
164   }
165 
166   // If the pointer is nullptr, allocate a new memory.
167   if (ptr == nullptr)
168     return allocate(size);
169 
170   cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
171 
172   if (!is_valid_ptr(bytes))
173     return nullptr;
174 
175   Block *block = Block::from_usable_space(bytes);
176   if (!block->used())
177     return nullptr;
178   size_t old_size = block->inner_size();
179 
180   // Do nothing and return ptr if the required memory size is smaller than
181   // the current size.
182   if (old_size >= size)
183     return ptr;
184 
185   void *new_ptr = allocate(size);
186   // Don't invalidate ptr if allocate(size) fails to initilize the memory.
187   if (new_ptr == nullptr)
188     return nullptr;
189   LIBC_NAMESPACE::inline_memcpy(new_ptr, ptr, old_size);
190 
191   free(ptr);
192   return new_ptr;
193 }
194 
195 LIBC_INLINE void *FreeListHeap::calloc(size_t num, size_t size) {
196   size_t bytes;
197   if (__builtin_mul_overflow(num, size, &bytes))
198     return nullptr;
199   void *ptr = allocate(bytes);
200   if (ptr != nullptr)
201     LIBC_NAMESPACE::inline_memset(ptr, 0, bytes);
202   return ptr;
203 }
204 
205 extern FreeListHeap *freelist_heap;
206 
207 } // namespace LIBC_NAMESPACE_DECL
208 
209 #endif // LLVM_LIBC_SRC___SUPPORT_FREELIST_HEAP_H
210