Skip to content

Commit cca2520

Browse files
committed
[libc][stdlib] Add the FreelistHeap
This is the actual freelist allocator which utilizes the generic FreeList and the Block classes. We will eventually wrap the malloc interface around this. This is a part of #94270 to land in smaller patches.
1 parent f074500 commit cca2520

File tree

4 files changed

+455
-0
lines changed

4 files changed

+455
-0
lines changed

libc/src/stdlib/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,20 @@ else()
402402
libc.src.__support.CPP.array
403403
libc.src.__support.CPP.span
404404
)
405+
add_header_library(
406+
freelist_heap
407+
HDRS
408+
freelist_heap.h
409+
DEPENDS
410+
.block
411+
.freelist
412+
libc.src.__support.CPP.cstddef
413+
libc.src.__support.CPP.array
414+
libc.src.__support.CPP.optional
415+
libc.src.__support.CPP.span
416+
libc.src.string.memory_utils.inline_memcpy
417+
libc.src.string.memory_utils.inline_memset
418+
)
405419
add_entrypoint_external(
406420
malloc
407421
)

libc/src/stdlib/freelist_heap.h

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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_STDLIB_FREELIST_HEAP_H
10+
#define LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H
11+
12+
#include <stddef.h>
13+
14+
#include "block.h"
15+
#include "freelist.h"
16+
#include "src/__support/CPP/optional.h"
17+
#include "src/__support/CPP/span.h"
18+
#include "src/string/memory_utils/inline_memcpy.h"
19+
#include "src/string/memory_utils/inline_memset.h"
20+
21+
namespace LIBC_NAMESPACE {
22+
23+
using cpp::optional;
24+
using cpp::span;
25+
26+
static constexpr cpp::array<size_t, 6> DEFAULT_BUCKETS{16, 32, 64,
27+
128, 256, 512};
28+
29+
template <size_t NUM_BUCKETS = DEFAULT_BUCKETS.size()> class FreeListHeap {
30+
public:
31+
using BlockType = Block<>;
32+
33+
struct HeapStats {
34+
size_t total_bytes;
35+
size_t bytes_allocated;
36+
size_t cumulative_allocated;
37+
size_t cumulative_freed;
38+
size_t total_allocate_calls;
39+
size_t total_free_calls;
40+
};
41+
FreeListHeap(span<cpp::byte> region);
42+
43+
void *allocate(size_t size);
44+
void free(void *ptr);
45+
void *realloc(void *ptr, size_t size);
46+
void *calloc(size_t num, size_t size);
47+
48+
const HeapStats &heap_stats() const { return heap_stats_; }
49+
50+
private:
51+
span<cpp::byte> block_to_span(BlockType *block) {
52+
return span<cpp::byte>(block->usable_space(), block->inner_size());
53+
}
54+
55+
void InvalidFreeCrash() { __builtin_trap(); }
56+
57+
span<cpp::byte> region_;
58+
FreeList<NUM_BUCKETS> freelist_;
59+
HeapStats heap_stats_;
60+
};
61+
62+
template <size_t NUM_BUCKETS>
63+
FreeListHeap<NUM_BUCKETS>::FreeListHeap(span<cpp::byte> region)
64+
: region_(region), freelist_(DEFAULT_BUCKETS), heap_stats_() {
65+
auto result = BlockType::init(region);
66+
BlockType *block = *result;
67+
68+
freelist_.add_chunk(block_to_span(block));
69+
70+
heap_stats_.total_bytes = region.size();
71+
}
72+
73+
template <size_t NUM_BUCKETS>
74+
void *FreeListHeap<NUM_BUCKETS>::allocate(size_t size) {
75+
// Find a chunk in the freelist. Split it if needed, then return
76+
auto chunk = freelist_.find_chunk(size);
77+
78+
if (chunk.data() == nullptr)
79+
return nullptr;
80+
freelist_.remove_chunk(chunk);
81+
82+
BlockType *chunk_block = BlockType::from_usable_space(chunk.data());
83+
84+
// Split that chunk. If there's a leftover chunk, add it to the freelist
85+
optional<BlockType *> result = BlockType::split(chunk_block, size);
86+
if (result)
87+
freelist_.add_chunk(block_to_span(*result));
88+
89+
chunk_block->mark_used();
90+
91+
heap_stats_.bytes_allocated += size;
92+
heap_stats_.cumulative_allocated += size;
93+
heap_stats_.total_allocate_calls += 1;
94+
95+
return chunk_block->usable_space();
96+
}
97+
98+
template <size_t NUM_BUCKETS> void FreeListHeap<NUM_BUCKETS>::free(void *ptr) {
99+
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
100+
101+
if (bytes < region_.data() || bytes >= region_.data() + region_.size()) {
102+
InvalidFreeCrash();
103+
return;
104+
}
105+
106+
BlockType *chunk_block = BlockType::from_usable_space(bytes);
107+
108+
size_t size_freed = chunk_block->inner_size();
109+
// Ensure that the block is in-use
110+
if (!chunk_block->used()) {
111+
InvalidFreeCrash();
112+
return;
113+
}
114+
chunk_block->mark_free();
115+
// Can we combine with the left or right blocks?
116+
BlockType *prev = chunk_block->prev();
117+
BlockType *next = nullptr;
118+
119+
if (!chunk_block->last())
120+
next = chunk_block->next();
121+
122+
if (prev != nullptr && !prev->used()) {
123+
// Remove from freelist and merge
124+
freelist_.remove_chunk(block_to_span(prev));
125+
chunk_block = chunk_block->prev();
126+
BlockType::merge_next(chunk_block);
127+
}
128+
129+
if (next != nullptr && !next->used()) {
130+
freelist_.remove_chunk(block_to_span(next));
131+
BlockType::merge_next(chunk_block);
132+
}
133+
// Add back to the freelist
134+
freelist_.add_chunk(block_to_span(chunk_block));
135+
136+
heap_stats_.bytes_allocated -= size_freed;
137+
heap_stats_.cumulative_freed += size_freed;
138+
heap_stats_.total_free_calls += 1;
139+
}
140+
141+
// Follows contract of the C standard realloc() function
142+
// If ptr is free'd, will return nullptr.
143+
template <size_t NUM_BUCKETS>
144+
void *FreeListHeap<NUM_BUCKETS>::realloc(void *ptr, size_t size) {
145+
if (size == 0) {
146+
free(ptr);
147+
return nullptr;
148+
}
149+
150+
// If the pointer is nullptr, allocate a new memory.
151+
if (ptr == nullptr)
152+
return allocate(size);
153+
154+
cpp::byte *bytes = static_cast<cpp::byte *>(ptr);
155+
156+
if (bytes < region_.data() || bytes >= region_.data() + region_.size())
157+
return nullptr;
158+
159+
BlockType *chunk_block = BlockType::from_usable_space(bytes);
160+
if (!chunk_block->used())
161+
return nullptr;
162+
size_t old_size = chunk_block->inner_size();
163+
164+
// Do nothing and return ptr if the required memory size is smaller than
165+
// the current size.
166+
if (old_size >= size)
167+
return ptr;
168+
169+
void *new_ptr = allocate(size);
170+
// Don't invalidate ptr if allocate(size) fails to initilize the memory.
171+
if (new_ptr == nullptr)
172+
return nullptr;
173+
LIBC_NAMESPACE::inline_memcpy(new_ptr, ptr, old_size);
174+
175+
free(ptr);
176+
return new_ptr;
177+
}
178+
179+
template <size_t NUM_BUCKETS>
180+
void *FreeListHeap<NUM_BUCKETS>::calloc(size_t num, size_t size) {
181+
void *ptr = allocate(num * size);
182+
if (ptr != nullptr)
183+
LIBC_NAMESPACE::inline_memset(ptr, 0, num * size);
184+
return ptr;
185+
}
186+
187+
} // namespace LIBC_NAMESPACE
188+
189+
#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H

libc/test/src/stdlib/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ add_libc_test(
7979
libc.src.__support.CPP.span
8080
)
8181

82+
add_libc_test(
83+
freelist_heap_test
84+
SUITE
85+
libc-stdlib-tests
86+
SRCS
87+
freelist_heap_test.cpp
88+
DEPENDS
89+
libc.src.__support.CPP.span
90+
libc.src.stdlib.freelist_heap
91+
libc.src.string.memcmp
92+
libc.src.string.memcpy
93+
)
94+
8295
add_fp_unittest(
8396
strtod_test
8497
SUITE

0 commit comments

Comments
 (0)