1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_allocator/test_harness.h"
16
17 #include <algorithm>
18 #include <climits>
19 #include <cstdint>
20 #include <type_traits>
21
22 #include "lib/stdcompat/bit.h"
23 #include "pw_assert/check.h"
24
25 namespace pw::allocator::test {
26 namespace {
27
28 // Helper to allow static_assert'ing in constexpr-if branches, e.g. that a
29 // visitor for a std::variant are exhaustive.
30 template <typename T>
not_reached(T &)31 constexpr bool not_reached(T&) {
32 return false;
33 }
34
GenerateAllocationRequest(random::RandomGenerator & prng,size_t max_size)35 AllocationRequest GenerateAllocationRequest(random::RandomGenerator& prng,
36 size_t max_size) {
37 AllocationRequest request;
38 if (max_size != 0) {
39 prng.GetInt(request.size);
40 request.size %= max_size;
41 }
42 uint8_t lshift;
43 prng.GetInt(lshift);
44 request.alignment = AlignmentFromLShift(lshift, request.size);
45 return request;
46 }
47
GenerateDeallocationRequest(random::RandomGenerator & prng)48 DeallocationRequest GenerateDeallocationRequest(random::RandomGenerator& prng) {
49 DeallocationRequest request;
50 prng.GetInt(request.index);
51 return request;
52 }
53
GenerateReallocationRequest(random::RandomGenerator & prng,size_t max_size)54 ReallocationRequest GenerateReallocationRequest(random::RandomGenerator& prng,
55 size_t max_size) {
56 ReallocationRequest request;
57 prng.GetInt(request.index);
58 if (max_size != 0) {
59 prng.GetInt(request.new_size);
60 request.new_size %= max_size;
61 }
62 return request;
63 }
64
65 } // namespace
66
AlignmentFromLShift(size_t lshift,size_t size)67 size_t AlignmentFromLShift(size_t lshift, size_t size) {
68 constexpr size_t max_bits = (sizeof(size) * CHAR_BIT) - 1;
69 size_t num_bits =
70 size == 0 ? 1 : std::min(size_t(cpp20::bit_width(size)), max_bits);
71 return 1U << (lshift % num_bits);
72 }
73
GenerateRequests(random::RandomGenerator & prng,size_t max_size,size_t num_requests)74 void TestHarnessGeneric::GenerateRequests(random::RandomGenerator& prng,
75 size_t max_size,
76 size_t num_requests) {
77 for (size_t i = 0; i < num_requests; ++i) {
78 GenerateRequest(prng, max_size);
79 }
80 Reset();
81 }
82
GenerateRequest(random::RandomGenerator & prng,size_t max_size)83 void TestHarnessGeneric::GenerateRequest(random::RandomGenerator& prng,
84 size_t max_size) {
85 Request request;
86 size_t request_type;
87 prng.GetInt(request_type);
88 switch (request_type % 3) {
89 case 0:
90 request = GenerateAllocationRequest(prng, max_size);
91 break;
92 case 1:
93 request = GenerateDeallocationRequest(prng);
94 break;
95 case 2:
96 request = GenerateReallocationRequest(prng, max_size);
97 break;
98 }
99 HandleRequest(request);
100 }
101
HandleRequests(const Vector<Request> & requests)102 void TestHarnessGeneric::HandleRequests(const Vector<Request>& requests) {
103 for (const auto& request : requests) {
104 HandleRequest(request);
105 }
106 Reset();
107 }
108
HandleRequest(const Request & request)109 void TestHarnessGeneric::HandleRequest(const Request& request) {
110 if (allocator_ == nullptr) {
111 allocator_ = Init();
112 PW_DCHECK_NOTNULL(allocator_);
113 }
114 std::visit(
115 [this](auto&& r) {
116 using T = std::decay_t<decltype(r)>;
117
118 if constexpr (std::is_same_v<T, AllocationRequest>) {
119 if (allocations_.size() < allocations_.max_size()) {
120 Layout layout(r.size, r.alignment);
121 void* ptr = allocator_->Allocate(layout);
122 if (ptr != nullptr) {
123 AddAllocation(ptr, layout);
124 }
125 }
126
127 } else if constexpr (std::is_same_v<T, DeallocationRequest>) {
128 if (!allocations_.empty()) {
129 Allocation old = RemoveAllocation(r.index);
130 allocator_->Deallocate(old.ptr);
131 }
132
133 } else if constexpr (std::is_same_v<T, ReallocationRequest>) {
134 if (!allocations_.empty()) {
135 Allocation old = RemoveAllocation(r.index);
136 Layout new_layout = Layout(r.new_size, old.layout.alignment());
137 void* new_ptr = allocator_->Reallocate(old.ptr, new_layout);
138 if (new_ptr == nullptr) {
139 AddAllocation(old.ptr, old.layout);
140 } else {
141 AddAllocation(new_ptr, new_layout);
142 }
143 }
144 } else {
145 static_assert(not_reached(r), "unsupported request type!");
146 }
147 },
148 request);
149 }
150
Reset()151 void TestHarnessGeneric::Reset() {
152 if (allocator_ == nullptr) {
153 return;
154 }
155 for (const Allocation& old : allocations_) {
156 allocator_->Deallocate(old.ptr);
157 }
158 allocations_.clear();
159 }
160
AddAllocation(void * ptr,Layout layout)161 void TestHarnessGeneric::AddAllocation(void* ptr, Layout layout) {
162 auto* bytes = static_cast<std::byte*>(ptr);
163 size_t left = layout.size();
164
165 // Record the request number in the allocated memory to aid in debugging.
166 ++num_requests_;
167 if (left >= sizeof(num_requests_)) {
168 std::memcpy(bytes, &num_requests_, sizeof(num_requests_));
169 left -= sizeof(num_requests_);
170 }
171
172 // Record the allocation size in the allocated memory to aid in debugging.
173 size_t size = layout.size();
174 if (left >= sizeof(size)) {
175 std::memcpy(bytes, &size, sizeof(size));
176 left -= sizeof(size);
177 }
178
179 // Fill the remaining memory with data.
180 if (left > 0) {
181 std::memset(bytes, 0x5A, left);
182 }
183
184 allocations_.emplace_back(Allocation{ptr, layout});
185 }
186
RemoveAllocation(size_t index)187 TestHarnessGeneric::Allocation TestHarnessGeneric::RemoveAllocation(
188 size_t index) {
189 // Move the target allocation to the back of the list.
190 index %= allocations_.size();
191 std::swap(allocations_.at(index), allocations_.back());
192
193 // Copy and remove the targeted allocation.
194 Allocation old(allocations_.back());
195 allocations_.pop_back();
196 return old;
197 }
198
199 } // namespace pw::allocator::test
200