Skip to content

Commit 19c6098

Browse files
author
Siva Chandra Reddy
committed
[libc] Add a resizable container with constexpr constructor and destructor.
The new container is used to store atexit callbacks. This way, we avoid the possibility of the destructor of the container itself getting added as an at exit callback. Reviewed By: abrachet Differential Revision: https://reviews.llvm.org/D121350
1 parent 15ef06f commit 19c6098

File tree

7 files changed

+257
-13
lines changed

7 files changed

+257
-13
lines changed

libc/src/__support/CPP/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,9 @@ add_header_library(
6767
HDRS
6868
atomic.h
6969
)
70+
71+
add_header_library(
72+
blockstore
73+
HDRS
74+
blockstore.h
75+
)
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//===-- A data structure which stores data in blocks -----------*- C++ -*-===//
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_SUPPORT_CPP_BLOCKSTORE_H
10+
#define LLVM_LIBC_SUPPORT_CPP_BLOCKSTORE_H
11+
12+
#include <stddef.h>
13+
#include <stdint.h>
14+
#include <stdlib.h>
15+
16+
namespace __llvm_libc {
17+
namespace cpp {
18+
19+
// The difference between BlockStore a traditional vector types is that,
20+
// when more capacity is desired, a new block is added instead of allocating
21+
// a larger sized array and copying over existing items to the new allocation.
22+
// Also, the initial block does not need heap allocation. Hence, a BlockStore is
23+
// suitable for global objects as it does not require explicit construction.
24+
// Also, the destructor of this class does nothing, which eliminates the need
25+
// for an atexit global object destruction. But, it also means that the global
26+
// object should be explicitly cleaned up at the appropriate time.
27+
//
28+
// If REVERSE_ORDER is true, the iteration of elements will in the reverse
29+
// order. Also, since REVERSE_ORDER is a constexpr, conditionals branching
30+
// on its value will be optimized out in the code below.
31+
template <typename T, size_t BLOCK_SIZE, bool REVERSE_ORDER = false>
32+
class BlockStore {
33+
protected:
34+
struct Block {
35+
alignas(T) uint8_t data[BLOCK_SIZE * sizeof(T)] = {0};
36+
Block *next = nullptr;
37+
};
38+
39+
Block first;
40+
Block *current = &first;
41+
size_t fill_count = 0;
42+
43+
public:
44+
constexpr BlockStore() = default;
45+
~BlockStore() = default;
46+
47+
class iterator {
48+
Block *block;
49+
size_t index;
50+
51+
public:
52+
constexpr iterator(Block *b, size_t i) : block(b), index(i) {}
53+
54+
iterator &operator++() {
55+
if (REVERSE_ORDER) {
56+
if (index == 0)
57+
return *this;
58+
59+
--index;
60+
if (index == 0 && block->next != nullptr) {
61+
index = BLOCK_SIZE;
62+
block = block->next;
63+
}
64+
} else {
65+
if (index == BLOCK_SIZE)
66+
return *this;
67+
68+
++index;
69+
if (index == BLOCK_SIZE && block->next != nullptr) {
70+
index = 0;
71+
block = block->next;
72+
}
73+
}
74+
75+
return *this;
76+
}
77+
78+
T &operator*() {
79+
size_t true_index = REVERSE_ORDER ? index - 1 : index;
80+
return *reinterpret_cast<T *>(block->data + sizeof(T) * true_index);
81+
}
82+
83+
bool operator==(const iterator &rhs) const {
84+
return block == rhs.block && index == rhs.index;
85+
}
86+
87+
bool operator!=(const iterator &rhs) const {
88+
return block != rhs.block || index != rhs.index;
89+
}
90+
};
91+
92+
static void destroy(BlockStore<T, BLOCK_SIZE, REVERSE_ORDER> *block_store);
93+
94+
T *new_obj() {
95+
if (fill_count == BLOCK_SIZE) {
96+
auto new_block = reinterpret_cast<Block *>(::malloc(sizeof(Block)));
97+
if (REVERSE_ORDER) {
98+
new_block->next = current;
99+
} else {
100+
new_block->next = nullptr;
101+
current->next = new_block;
102+
}
103+
current = new_block;
104+
fill_count = 0;
105+
}
106+
T *obj = reinterpret_cast<T *>(current->data + fill_count * sizeof(T));
107+
++fill_count;
108+
return obj;
109+
}
110+
111+
void push_back(const T &value) {
112+
T *ptr = new_obj();
113+
*ptr = value;
114+
}
115+
116+
iterator begin() {
117+
if (REVERSE_ORDER)
118+
return iterator(current, fill_count);
119+
else
120+
return iterator(&first, 0);
121+
}
122+
123+
iterator end() {
124+
if (REVERSE_ORDER)
125+
return iterator(&first, 0);
126+
else
127+
return iterator(current, fill_count);
128+
}
129+
};
130+
131+
template <typename T, size_t BLOCK_SIZE, bool REVERSE_ORDER>
132+
void BlockStore<T, BLOCK_SIZE, REVERSE_ORDER>::destroy(
133+
BlockStore<T, BLOCK_SIZE, REVERSE_ORDER> *block_store) {
134+
if (REVERSE_ORDER) {
135+
auto current = block_store->current;
136+
while (current->next != nullptr) {
137+
auto temp = current;
138+
current = current->next;
139+
free(temp);
140+
}
141+
} else {
142+
auto current = block_store->first.next;
143+
while (current != nullptr) {
144+
auto temp = current;
145+
current = current->next;
146+
free(temp);
147+
}
148+
}
149+
block_store->current = nullptr;
150+
block_store->fill_count = 0;
151+
}
152+
153+
// A convenience type for reverse order block stores.
154+
template <typename T, size_t BLOCK_SIZE>
155+
using ReverseOrderBlockStore = BlockStore<T, BLOCK_SIZE, true>;
156+
157+
} // namespace cpp
158+
} // namespace __llvm_libc
159+
160+
#endif // LLVM_LIBC_SUPPORT_CPP_BLOCKSTORE_H

libc/src/stdlib/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,10 @@ add_entrypoint_object(
276276
atexit.cpp
277277
HDRS
278278
atexit.h
279+
CXX_STANDARD
280+
20 # For constinit of the atexit callback list.
279281
DEPENDS
280-
libc.src.__support.CPP.vector
282+
libc.src.__support.CPP.blockstore
281283
libc.src.__support.threads.thread
282284
)
283285

libc/src/stdlib/atexit.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "src/stdlib/atexit.h"
10-
#include "src/__support/CPP/vector.h"
10+
#include "src/__support/CPP/blockstore.h"
1111
#include "src/__support/common.h"
1212
#include "src/__support/threads/mutex.h"
1313

@@ -17,29 +17,29 @@ namespace {
1717

1818
Mutex handler_list_mtx(false, false, false);
1919

20-
// TOOD should we make cpp::vector like llvm::SmallVector<T, N> where it will
21-
// allocate at least N before needing dynamic allocation?
22-
static cpp::vector<void (*)(void)> handlers;
20+
using AtExitCallback = void(void);
21+
using ExitCallbackList = cpp::ReverseOrderBlockStore<AtExitCallback *, 32>;
22+
constinit ExitCallbackList exit_callbacks;
2323

2424
} // namespace
2525

2626
namespace internal {
2727

28-
void call_exit_handlers() {
28+
void call_exit_callbacks() {
2929
handler_list_mtx.lock();
30-
// TODO: implement rbegin() + rend() for cpp::vector
31-
for (int i = handlers.size() - 1; i >= 0; i--) {
30+
for (auto callback : exit_callbacks) {
3231
handler_list_mtx.unlock();
33-
handlers[i]();
32+
callback();
3433
handler_list_mtx.lock();
3534
}
35+
ExitCallbackList::destroy(&exit_callbacks);
3636
}
3737

3838
} // namespace internal
3939

40-
LLVM_LIBC_FUNCTION(int, atexit, (void (*function)())) {
40+
LLVM_LIBC_FUNCTION(int, atexit, (AtExitCallback * callback)) {
4141
handler_list_mtx.lock();
42-
handlers.push_back(function);
42+
exit_callbacks.push_back(callback);
4343
handler_list_mtx.unlock();
4444
return 0;
4545
}

libc/src/stdlib/exit.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
namespace __llvm_libc {
1414

1515
namespace internal {
16-
void call_exit_handlers();
16+
void call_exit_callbacks();
1717
}
1818

1919
LLVM_LIBC_FUNCTION(void, exit, (int status)) {
20-
internal::call_exit_handlers();
20+
internal::call_exit_callbacks();
2121
_Exit(status);
2222
}
2323

libc/test/src/__support/CPP/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,13 @@ add_libc_unittest(
6969
DEPENDS
7070
libc.src.__support.CPP.atomic
7171
)
72+
73+
add_libc_unittest(
74+
blockstore_test
75+
SUITE
76+
libc_cpp_utils_unittests
77+
SRCS
78+
blockstore_test.cpp
79+
DEPENDS
80+
libc.src.__support.CPP.blockstore
81+
)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//===-- Unittests for BlockStore ------------------------------------------===//
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+
#include "src/__support/CPP/blockstore.h"
10+
#include "utils/UnitTest/Test.h"
11+
12+
struct Element {
13+
int a;
14+
long b;
15+
unsigned c;
16+
};
17+
18+
class LlvmLibcBlockStoreTest : public __llvm_libc::testing::Test {
19+
public:
20+
template <size_t BLOCK_SIZE, size_t ELEMENT_COUNT, bool REVERSE>
21+
void populate_and_iterate() {
22+
__llvm_libc::cpp::BlockStore<Element, BLOCK_SIZE, REVERSE> block_store;
23+
for (int i = 0; i < int(ELEMENT_COUNT); ++i)
24+
block_store.push_back({i, 2 * i, 3 * unsigned(i)});
25+
auto end = block_store.end();
26+
int i = 0;
27+
for (auto iter = block_store.begin(); iter != end; ++iter, ++i) {
28+
Element &e = *iter;
29+
if (REVERSE) {
30+
int j = ELEMENT_COUNT - 1 - i;
31+
ASSERT_EQ(e.a, j);
32+
ASSERT_EQ(e.b, long(j * 2));
33+
ASSERT_EQ(e.c, unsigned(j * 3));
34+
} else {
35+
ASSERT_EQ(e.a, i);
36+
ASSERT_EQ(e.b, long(i * 2));
37+
ASSERT_EQ(e.c, unsigned(i * 3));
38+
}
39+
}
40+
ASSERT_EQ(i, int(ELEMENT_COUNT));
41+
}
42+
};
43+
44+
TEST_F(LlvmLibcBlockStoreTest, PopulateAndIterate4) {
45+
populate_and_iterate<4, 4, false>();
46+
}
47+
48+
TEST_F(LlvmLibcBlockStoreTest, PopulateAndIterate8) {
49+
populate_and_iterate<4, 8, false>();
50+
}
51+
52+
TEST_F(LlvmLibcBlockStoreTest, PopulateAndIterate10) {
53+
populate_and_iterate<4, 10, false>();
54+
}
55+
56+
TEST_F(LlvmLibcBlockStoreTest, PopulateAndIterateReverse4) {
57+
populate_and_iterate<4, 4, true>();
58+
}
59+
60+
TEST_F(LlvmLibcBlockStoreTest, PopulateAndIterateReverse8) {
61+
populate_and_iterate<4, 8, true>();
62+
}
63+
64+
TEST_F(LlvmLibcBlockStoreTest, PopulateAndIterateReverse10) {
65+
populate_and_iterate<4, 10, true>();
66+
}

0 commit comments

Comments
 (0)