• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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