xref: /aosp_15_r20/external/pigweed/pw_allocator/test_harness.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker // Copyright 2023 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker //
3*61c4878aSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker // use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker // the License at
6*61c4878aSAndroid Build Coastguard Worker //
7*61c4878aSAndroid Build Coastguard Worker //     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker //
9*61c4878aSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker // License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker // the License.
14*61c4878aSAndroid Build Coastguard Worker 
15*61c4878aSAndroid Build Coastguard Worker #include "pw_allocator/test_harness.h"
16*61c4878aSAndroid Build Coastguard Worker 
17*61c4878aSAndroid Build Coastguard Worker #include <algorithm>
18*61c4878aSAndroid Build Coastguard Worker #include <climits>
19*61c4878aSAndroid Build Coastguard Worker #include <cstddef>
20*61c4878aSAndroid Build Coastguard Worker #include <cstdint>
21*61c4878aSAndroid Build Coastguard Worker #include <type_traits>
22*61c4878aSAndroid Build Coastguard Worker 
23*61c4878aSAndroid Build Coastguard Worker #include "lib/stdcompat/bit.h"
24*61c4878aSAndroid Build Coastguard Worker #include "pw_assert/assert.h"
25*61c4878aSAndroid Build Coastguard Worker #include "pw_assert/check.h"
26*61c4878aSAndroid Build Coastguard Worker 
27*61c4878aSAndroid Build Coastguard Worker namespace pw::allocator::test {
28*61c4878aSAndroid Build Coastguard Worker namespace {
29*61c4878aSAndroid Build Coastguard Worker 
30*61c4878aSAndroid Build Coastguard Worker // Helper to allow static_assert'ing in constexpr-if branches, e.g. that a
31*61c4878aSAndroid Build Coastguard Worker // visitor for a std::variant are exhaustive.
32*61c4878aSAndroid Build Coastguard Worker template <typename T>
not_reached(T &)33*61c4878aSAndroid Build Coastguard Worker constexpr bool not_reached(T&) {
34*61c4878aSAndroid Build Coastguard Worker   return false;
35*61c4878aSAndroid Build Coastguard Worker }
36*61c4878aSAndroid Build Coastguard Worker 
37*61c4878aSAndroid Build Coastguard Worker }  // namespace
38*61c4878aSAndroid Build Coastguard Worker 
AlignmentFromLShift(size_t lshift,size_t size)39*61c4878aSAndroid Build Coastguard Worker size_t AlignmentFromLShift(size_t lshift, size_t size) {
40*61c4878aSAndroid Build Coastguard Worker   constexpr size_t max_bits = (sizeof(size) * CHAR_BIT) - 1;
41*61c4878aSAndroid Build Coastguard Worker   size_t num_bits =
42*61c4878aSAndroid Build Coastguard Worker       size == 0 ? 1 : std::min(size_t(cpp20::bit_width(size)), max_bits);
43*61c4878aSAndroid Build Coastguard Worker   size_t alignment = 1U << (lshift % num_bits);
44*61c4878aSAndroid Build Coastguard Worker   return std::max(alignment, alignof(TestHarness::Allocation));
45*61c4878aSAndroid Build Coastguard Worker }
46*61c4878aSAndroid Build Coastguard Worker 
GenerateRequests(size_t max_size,size_t num_requests)47*61c4878aSAndroid Build Coastguard Worker void TestHarness::GenerateRequests(size_t max_size, size_t num_requests) {
48*61c4878aSAndroid Build Coastguard Worker   for (size_t i = 0; i < num_requests; ++i) {
49*61c4878aSAndroid Build Coastguard Worker     GenerateRequest(max_size);
50*61c4878aSAndroid Build Coastguard Worker   }
51*61c4878aSAndroid Build Coastguard Worker   Reset();
52*61c4878aSAndroid Build Coastguard Worker }
53*61c4878aSAndroid Build Coastguard Worker 
GenerateRequest(size_t max_size)54*61c4878aSAndroid Build Coastguard Worker void TestHarness::GenerateRequest(size_t max_size) {
55*61c4878aSAndroid Build Coastguard Worker   Request request;
56*61c4878aSAndroid Build Coastguard Worker   size_t dealloc_threshold = 40;
57*61c4878aSAndroid Build Coastguard Worker   if (available_.has_value()) {
58*61c4878aSAndroid Build Coastguard Worker     // This corresponds to a fixed 10% chance to reallocate and a sliding
59*61c4878aSAndroid Build Coastguard Worker     // scale between:
60*61c4878aSAndroid Build Coastguard Worker     // * when empty, an 80% chance to allocate and a 10% chance to deallocate
61*61c4878aSAndroid Build Coastguard Worker     // * when full, a 30% chance to allocate and a 60% chance to deallocate
62*61c4878aSAndroid Build Coastguard Worker     dealloc_threshold = 80 - (allocated_ * 50) / available_.value();
63*61c4878aSAndroid Build Coastguard Worker   }
64*61c4878aSAndroid Build Coastguard Worker   do {
65*61c4878aSAndroid Build Coastguard Worker     size_t request_type;
66*61c4878aSAndroid Build Coastguard Worker     prng_->GetInt(request_type);
67*61c4878aSAndroid Build Coastguard Worker     request_type %= 100;
68*61c4878aSAndroid Build Coastguard Worker     if (request_type < dealloc_threshold) {
69*61c4878aSAndroid Build Coastguard Worker       request = GenerateAllocationRequest(max_size);
70*61c4878aSAndroid Build Coastguard Worker     } else if (request_type < 90) {
71*61c4878aSAndroid Build Coastguard Worker       request = GenerateDeallocationRequest();
72*61c4878aSAndroid Build Coastguard Worker     } else {
73*61c4878aSAndroid Build Coastguard Worker       request = GenerateReallocationRequest(max_size);
74*61c4878aSAndroid Build Coastguard Worker     }
75*61c4878aSAndroid Build Coastguard Worker   } while (!HandleRequest(request));
76*61c4878aSAndroid Build Coastguard Worker }
77*61c4878aSAndroid Build Coastguard Worker 
GenerateAllocationRequest(size_t max_size)78*61c4878aSAndroid Build Coastguard Worker AllocationRequest TestHarness::GenerateAllocationRequest(size_t max_size) {
79*61c4878aSAndroid Build Coastguard Worker   AllocationRequest request;
80*61c4878aSAndroid Build Coastguard Worker   request.size = GenerateSize(max_size);
81*61c4878aSAndroid Build Coastguard Worker   uint8_t lshift;
82*61c4878aSAndroid Build Coastguard Worker   prng_->GetInt(lshift);
83*61c4878aSAndroid Build Coastguard Worker   request.alignment = AlignmentFromLShift(lshift, request.size);
84*61c4878aSAndroid Build Coastguard Worker   return request;
85*61c4878aSAndroid Build Coastguard Worker }
86*61c4878aSAndroid Build Coastguard Worker 
GenerateDeallocationRequest()87*61c4878aSAndroid Build Coastguard Worker DeallocationRequest TestHarness::GenerateDeallocationRequest() {
88*61c4878aSAndroid Build Coastguard Worker   DeallocationRequest request;
89*61c4878aSAndroid Build Coastguard Worker   prng_->GetInt(request.index);
90*61c4878aSAndroid Build Coastguard Worker   return request;
91*61c4878aSAndroid Build Coastguard Worker }
92*61c4878aSAndroid Build Coastguard Worker 
GenerateReallocationRequest(size_t max_size)93*61c4878aSAndroid Build Coastguard Worker ReallocationRequest TestHarness::GenerateReallocationRequest(size_t max_size) {
94*61c4878aSAndroid Build Coastguard Worker   ReallocationRequest request;
95*61c4878aSAndroid Build Coastguard Worker   prng_->GetInt(request.index);
96*61c4878aSAndroid Build Coastguard Worker   request.new_size = GenerateSize(max_size);
97*61c4878aSAndroid Build Coastguard Worker   return request;
98*61c4878aSAndroid Build Coastguard Worker }
99*61c4878aSAndroid Build Coastguard Worker 
GenerateSize(size_t max_size)100*61c4878aSAndroid Build Coastguard Worker size_t TestHarness::GenerateSize(size_t max_size) {
101*61c4878aSAndroid Build Coastguard Worker   static constexpr size_t kMinSize = sizeof(TestHarness::Allocation);
102*61c4878aSAndroid Build Coastguard Worker   size_t size = 0;
103*61c4878aSAndroid Build Coastguard Worker   if (max_size_.has_value()) {
104*61c4878aSAndroid Build Coastguard Worker     max_size = std::min(max_size, max_size_.value());
105*61c4878aSAndroid Build Coastguard Worker   }
106*61c4878aSAndroid Build Coastguard Worker   if (max_size <= kMinSize) {
107*61c4878aSAndroid Build Coastguard Worker     return kMinSize;
108*61c4878aSAndroid Build Coastguard Worker   }
109*61c4878aSAndroid Build Coastguard Worker   prng_->GetInt(size);
110*61c4878aSAndroid Build Coastguard Worker   size %= max_size - kMinSize;
111*61c4878aSAndroid Build Coastguard Worker   return kMinSize + size;
112*61c4878aSAndroid Build Coastguard Worker }
113*61c4878aSAndroid Build Coastguard Worker 
HandleRequests(const Vector<Request> & requests)114*61c4878aSAndroid Build Coastguard Worker void TestHarness::HandleRequests(const Vector<Request>& requests) {
115*61c4878aSAndroid Build Coastguard Worker   for (const auto& request : requests) {
116*61c4878aSAndroid Build Coastguard Worker     HandleRequest(request);
117*61c4878aSAndroid Build Coastguard Worker   }
118*61c4878aSAndroid Build Coastguard Worker   Reset();
119*61c4878aSAndroid Build Coastguard Worker }
120*61c4878aSAndroid Build Coastguard Worker 
HandleRequest(const Request & request)121*61c4878aSAndroid Build Coastguard Worker bool TestHarness::HandleRequest(const Request& request) {
122*61c4878aSAndroid Build Coastguard Worker   if (allocator_ == nullptr) {
123*61c4878aSAndroid Build Coastguard Worker     allocator_ = Init();
124*61c4878aSAndroid Build Coastguard Worker     PW_DCHECK_NOTNULL(allocator_);
125*61c4878aSAndroid Build Coastguard Worker   }
126*61c4878aSAndroid Build Coastguard Worker   return std::visit(
127*61c4878aSAndroid Build Coastguard Worker       [this](auto&& r) {
128*61c4878aSAndroid Build Coastguard Worker         using T = std::decay_t<decltype(r)>;
129*61c4878aSAndroid Build Coastguard Worker 
130*61c4878aSAndroid Build Coastguard Worker         if constexpr (std::is_same_v<T, AllocationRequest>) {
131*61c4878aSAndroid Build Coastguard Worker           Layout layout(r.size, r.alignment);
132*61c4878aSAndroid Build Coastguard Worker           BeforeAllocate(layout);
133*61c4878aSAndroid Build Coastguard Worker           void* ptr = allocator_->Allocate(layout);
134*61c4878aSAndroid Build Coastguard Worker           AfterAllocate(ptr);
135*61c4878aSAndroid Build Coastguard Worker           if (ptr != nullptr) {
136*61c4878aSAndroid Build Coastguard Worker             AddAllocation(ptr, layout);
137*61c4878aSAndroid Build Coastguard Worker             max_size_.reset();
138*61c4878aSAndroid Build Coastguard Worker           } else {
139*61c4878aSAndroid Build Coastguard Worker             max_size_ = std::max(layout.size() / 2, size_t(1));
140*61c4878aSAndroid Build Coastguard Worker           }
141*61c4878aSAndroid Build Coastguard Worker         } else if constexpr (std::is_same_v<T, DeallocationRequest>) {
142*61c4878aSAndroid Build Coastguard Worker           Allocation* old = RemoveAllocation(r.index);
143*61c4878aSAndroid Build Coastguard Worker           if (old == nullptr) {
144*61c4878aSAndroid Build Coastguard Worker             return false;
145*61c4878aSAndroid Build Coastguard Worker           }
146*61c4878aSAndroid Build Coastguard Worker           BeforeDeallocate(old);
147*61c4878aSAndroid Build Coastguard Worker           allocator_->Deallocate(old);
148*61c4878aSAndroid Build Coastguard Worker           AfterDeallocate();
149*61c4878aSAndroid Build Coastguard Worker 
150*61c4878aSAndroid Build Coastguard Worker         } else if constexpr (std::is_same_v<T, ReallocationRequest>) {
151*61c4878aSAndroid Build Coastguard Worker           Allocation* old = RemoveAllocation(r.index);
152*61c4878aSAndroid Build Coastguard Worker           if (old == nullptr) {
153*61c4878aSAndroid Build Coastguard Worker             return false;
154*61c4878aSAndroid Build Coastguard Worker           }
155*61c4878aSAndroid Build Coastguard Worker           Layout new_layout(r.new_size, old->layout.alignment());
156*61c4878aSAndroid Build Coastguard Worker           BeforeReallocate(new_layout);
157*61c4878aSAndroid Build Coastguard Worker           void* new_ptr = allocator_->Reallocate(old, new_layout);
158*61c4878aSAndroid Build Coastguard Worker           AfterReallocate(new_ptr);
159*61c4878aSAndroid Build Coastguard Worker           if (new_ptr == nullptr) {
160*61c4878aSAndroid Build Coastguard Worker             AddAllocation(old, old->layout);
161*61c4878aSAndroid Build Coastguard Worker           } else {
162*61c4878aSAndroid Build Coastguard Worker             AddAllocation(new_ptr, new_layout);
163*61c4878aSAndroid Build Coastguard Worker           }
164*61c4878aSAndroid Build Coastguard Worker         } else {
165*61c4878aSAndroid Build Coastguard Worker           static_assert(not_reached(r), "unsupported request type!");
166*61c4878aSAndroid Build Coastguard Worker         }
167*61c4878aSAndroid Build Coastguard Worker         return true;
168*61c4878aSAndroid Build Coastguard Worker       },
169*61c4878aSAndroid Build Coastguard Worker       request);
170*61c4878aSAndroid Build Coastguard Worker }
171*61c4878aSAndroid Build Coastguard Worker 
Reset()172*61c4878aSAndroid Build Coastguard Worker void TestHarness::Reset() {
173*61c4878aSAndroid Build Coastguard Worker   if (allocator_ == nullptr) {
174*61c4878aSAndroid Build Coastguard Worker     return;
175*61c4878aSAndroid Build Coastguard Worker   }
176*61c4878aSAndroid Build Coastguard Worker   while (!allocations_.empty()) {
177*61c4878aSAndroid Build Coastguard Worker     allocator_->Deallocate(RemoveAllocation(0));
178*61c4878aSAndroid Build Coastguard Worker   }
179*61c4878aSAndroid Build Coastguard Worker }
180*61c4878aSAndroid Build Coastguard Worker 
AddAllocation(void * ptr,Layout layout)181*61c4878aSAndroid Build Coastguard Worker void TestHarness::AddAllocation(void* ptr, Layout layout) {
182*61c4878aSAndroid Build Coastguard Worker   constexpr Layout min_layout = Layout::Of<Allocation>();
183*61c4878aSAndroid Build Coastguard Worker   if (layout.size() < min_layout.size() ||
184*61c4878aSAndroid Build Coastguard Worker       layout.alignment() < min_layout.alignment()) {
185*61c4878aSAndroid Build Coastguard Worker     // The harness should want to test small sizes and alignments, but it
186*61c4878aSAndroid Build Coastguard Worker     // needs the layout to be at least `Layout::Of<Allocation>` in order
187*61c4878aSAndroid Build Coastguard Worker     // to persist details about it. If either the size or alignment is too
188*61c4878aSAndroid Build Coastguard Worker     // small, deallocate immediately.
189*61c4878aSAndroid Build Coastguard Worker     BeforeDeallocate(ptr);
190*61c4878aSAndroid Build Coastguard Worker     allocator_->Deallocate(ptr);
191*61c4878aSAndroid Build Coastguard Worker     AfterDeallocate();
192*61c4878aSAndroid Build Coastguard Worker     return;
193*61c4878aSAndroid Build Coastguard Worker   }
194*61c4878aSAndroid Build Coastguard Worker   auto* bytes = static_cast<std::byte*>(ptr);
195*61c4878aSAndroid Build Coastguard Worker   auto* allocation = ::new (bytes) Allocation(layout);
196*61c4878aSAndroid Build Coastguard Worker   allocations_.push_back(*allocation);
197*61c4878aSAndroid Build Coastguard Worker   allocated_ += layout.size();
198*61c4878aSAndroid Build Coastguard Worker   ++num_allocations_;
199*61c4878aSAndroid Build Coastguard Worker }
200*61c4878aSAndroid Build Coastguard Worker 
RemoveAllocation(size_t index)201*61c4878aSAndroid Build Coastguard Worker TestHarness::Allocation* TestHarness::RemoveAllocation(size_t index) {
202*61c4878aSAndroid Build Coastguard Worker   // Move the target allocation to the back of the list.
203*61c4878aSAndroid Build Coastguard Worker   if (num_allocations_ == 0) {
204*61c4878aSAndroid Build Coastguard Worker     return nullptr;
205*61c4878aSAndroid Build Coastguard Worker   }
206*61c4878aSAndroid Build Coastguard Worker   index %= num_allocations_;
207*61c4878aSAndroid Build Coastguard Worker   auto prev = allocations_.before_begin();
208*61c4878aSAndroid Build Coastguard Worker   for (auto& allocation : allocations_) {
209*61c4878aSAndroid Build Coastguard Worker     if (index == 0) {
210*61c4878aSAndroid Build Coastguard Worker       PW_ASSERT(allocated_ >= allocation.layout.size());
211*61c4878aSAndroid Build Coastguard Worker       allocated_ -= allocation.layout.size();
212*61c4878aSAndroid Build Coastguard Worker       allocations_.erase_after(prev);
213*61c4878aSAndroid Build Coastguard Worker       --num_allocations_;
214*61c4878aSAndroid Build Coastguard Worker       return &allocation;
215*61c4878aSAndroid Build Coastguard Worker     }
216*61c4878aSAndroid Build Coastguard Worker     --index;
217*61c4878aSAndroid Build Coastguard Worker     ++prev;
218*61c4878aSAndroid Build Coastguard Worker   }
219*61c4878aSAndroid Build Coastguard Worker   PW_CRASH("unreachable");
220*61c4878aSAndroid Build Coastguard Worker }
221*61c4878aSAndroid Build Coastguard Worker 
222*61c4878aSAndroid Build Coastguard Worker }  // namespace pw::allocator::test
223