Skip to content

Commit 3bcd80a

Browse files
authored
[libc][stdlib] Add the FreelistHeap (#95066)
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 f33310e commit 3bcd80a

File tree

4 files changed

+449
-0
lines changed

4 files changed

+449
-0
lines changed

libc/src/stdlib/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,21 @@ 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.__support.libc_assert
417+
libc.src.string.memory_utils.inline_memcpy
418+
libc.src.string.memory_utils.inline_memset
419+
)
405420
add_entrypoint_external(
406421
malloc
407422
)

libc/src/stdlib/freelist_heap.h

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