xref: /aosp_15_r20/external/pigweed/pw_multibuf/chunk.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_multibuf/chunk.h"
16*61c4878aSAndroid Build Coastguard Worker 
17*61c4878aSAndroid Build Coastguard Worker #include <mutex>
18*61c4878aSAndroid Build Coastguard Worker 
19*61c4878aSAndroid Build Coastguard Worker #include "pw_assert/check.h"
20*61c4878aSAndroid Build Coastguard Worker 
21*61c4878aSAndroid Build Coastguard Worker namespace pw::multibuf {
22*61c4878aSAndroid Build Coastguard Worker namespace {
CheckedAdd(std::byte * ptr,size_t offset)23*61c4878aSAndroid Build Coastguard Worker std::byte* CheckedAdd(std::byte* ptr, size_t offset) {
24*61c4878aSAndroid Build Coastguard Worker   uintptr_t ptr_int = reinterpret_cast<uintptr_t>(ptr);
25*61c4878aSAndroid Build Coastguard Worker   if (std::numeric_limits<uintptr_t>::max() - ptr_int < offset) {
26*61c4878aSAndroid Build Coastguard Worker     return nullptr;
27*61c4878aSAndroid Build Coastguard Worker   }
28*61c4878aSAndroid Build Coastguard Worker   return reinterpret_cast<std::byte*>(ptr_int + offset);
29*61c4878aSAndroid Build Coastguard Worker }
30*61c4878aSAndroid Build Coastguard Worker 
CheckedSub(std::byte * ptr,size_t offset)31*61c4878aSAndroid Build Coastguard Worker std::byte* CheckedSub(std::byte* ptr, size_t offset) {
32*61c4878aSAndroid Build Coastguard Worker   uintptr_t ptr_int = reinterpret_cast<uintptr_t>(ptr);
33*61c4878aSAndroid Build Coastguard Worker   if (ptr_int < offset) {
34*61c4878aSAndroid Build Coastguard Worker     return nullptr;
35*61c4878aSAndroid Build Coastguard Worker   }
36*61c4878aSAndroid Build Coastguard Worker   return reinterpret_cast<std::byte*>(ptr_int - offset);
37*61c4878aSAndroid Build Coastguard Worker }
38*61c4878aSAndroid Build Coastguard Worker 
BeginPtr(ByteSpan span)39*61c4878aSAndroid Build Coastguard Worker std::byte* BeginPtr(ByteSpan span) { return span.data(); }
40*61c4878aSAndroid Build Coastguard Worker 
EndPtr(ByteSpan span)41*61c4878aSAndroid Build Coastguard Worker std::byte* EndPtr(ByteSpan span) { return span.data() + span.size(); }
42*61c4878aSAndroid Build Coastguard Worker 
43*61c4878aSAndroid Build Coastguard Worker }  // namespace
44*61c4878aSAndroid Build Coastguard Worker 
CanMerge(const Chunk & next_chunk) const45*61c4878aSAndroid Build Coastguard Worker bool Chunk::CanMerge(const Chunk& next_chunk) const {
46*61c4878aSAndroid Build Coastguard Worker   return region_tracker_ == next_chunk.region_tracker_ &&
47*61c4878aSAndroid Build Coastguard Worker          EndPtr(span_) == BeginPtr(next_chunk.span_);
48*61c4878aSAndroid Build Coastguard Worker }
49*61c4878aSAndroid Build Coastguard Worker 
Merge(OwnedChunk & next_chunk_owned)50*61c4878aSAndroid Build Coastguard Worker bool Chunk::Merge(OwnedChunk& next_chunk_owned) {
51*61c4878aSAndroid Build Coastguard Worker   if (!CanMerge(*next_chunk_owned)) {
52*61c4878aSAndroid Build Coastguard Worker     return false;
53*61c4878aSAndroid Build Coastguard Worker   }
54*61c4878aSAndroid Build Coastguard Worker   Chunk* next_chunk = std::move(next_chunk_owned).Take();
55*61c4878aSAndroid Build Coastguard Worker 
56*61c4878aSAndroid Build Coastguard Worker   // Note: Both chunks have the same ``region_tracker_``.
57*61c4878aSAndroid Build Coastguard Worker   //
58*61c4878aSAndroid Build Coastguard Worker   // We lock the one from `next_chunk` to satisfy the automatic
59*61c4878aSAndroid Build Coastguard Worker   // checker that ``RemoveFromRegionList`` is safe to call.
60*61c4878aSAndroid Build Coastguard Worker   std::lock_guard lock(next_chunk->region_tracker_->lock_);
61*61c4878aSAndroid Build Coastguard Worker   PW_DCHECK(next_in_region_ == next_chunk);
62*61c4878aSAndroid Build Coastguard Worker   span_ = ByteSpan(data(), size() + next_chunk->size());
63*61c4878aSAndroid Build Coastguard Worker   next_chunk->RemoveFromRegionList();
64*61c4878aSAndroid Build Coastguard Worker   region_tracker_->DeallocateChunkClass(next_chunk);
65*61c4878aSAndroid Build Coastguard Worker   return true;
66*61c4878aSAndroid Build Coastguard Worker }
67*61c4878aSAndroid Build Coastguard Worker 
InsertAfterInRegionList(Chunk * new_chunk)68*61c4878aSAndroid Build Coastguard Worker void Chunk::InsertAfterInRegionList(Chunk* new_chunk)
69*61c4878aSAndroid Build Coastguard Worker     PW_EXCLUSIVE_LOCKS_REQUIRED(region_tracker_->lock_) {
70*61c4878aSAndroid Build Coastguard Worker   new_chunk->next_in_region_ = next_in_region_;
71*61c4878aSAndroid Build Coastguard Worker   new_chunk->prev_in_region_ = this;
72*61c4878aSAndroid Build Coastguard Worker   if (next_in_region_ != nullptr) {
73*61c4878aSAndroid Build Coastguard Worker     next_in_region_->prev_in_region_ = new_chunk;
74*61c4878aSAndroid Build Coastguard Worker   }
75*61c4878aSAndroid Build Coastguard Worker   next_in_region_ = new_chunk;
76*61c4878aSAndroid Build Coastguard Worker }
77*61c4878aSAndroid Build Coastguard Worker 
InsertBeforeInRegionList(Chunk * new_chunk)78*61c4878aSAndroid Build Coastguard Worker void Chunk::InsertBeforeInRegionList(Chunk* new_chunk)
79*61c4878aSAndroid Build Coastguard Worker     PW_EXCLUSIVE_LOCKS_REQUIRED(region_tracker_->lock_) {
80*61c4878aSAndroid Build Coastguard Worker   new_chunk->next_in_region_ = this;
81*61c4878aSAndroid Build Coastguard Worker   new_chunk->prev_in_region_ = prev_in_region_;
82*61c4878aSAndroid Build Coastguard Worker   if (prev_in_region_ != nullptr) {
83*61c4878aSAndroid Build Coastguard Worker     prev_in_region_->next_in_region_ = new_chunk;
84*61c4878aSAndroid Build Coastguard Worker   }
85*61c4878aSAndroid Build Coastguard Worker   prev_in_region_ = new_chunk;
86*61c4878aSAndroid Build Coastguard Worker }
87*61c4878aSAndroid Build Coastguard Worker 
RemoveFromRegionList()88*61c4878aSAndroid Build Coastguard Worker void Chunk::RemoveFromRegionList()
89*61c4878aSAndroid Build Coastguard Worker     PW_EXCLUSIVE_LOCKS_REQUIRED(region_tracker_->lock_) {
90*61c4878aSAndroid Build Coastguard Worker   if (prev_in_region_ != nullptr) {
91*61c4878aSAndroid Build Coastguard Worker     prev_in_region_->next_in_region_ = next_in_region_;
92*61c4878aSAndroid Build Coastguard Worker   }
93*61c4878aSAndroid Build Coastguard Worker   if (next_in_region_ != nullptr) {
94*61c4878aSAndroid Build Coastguard Worker     next_in_region_->prev_in_region_ = prev_in_region_;
95*61c4878aSAndroid Build Coastguard Worker   }
96*61c4878aSAndroid Build Coastguard Worker   prev_in_region_ = nullptr;
97*61c4878aSAndroid Build Coastguard Worker   next_in_region_ = nullptr;
98*61c4878aSAndroid Build Coastguard Worker }
99*61c4878aSAndroid Build Coastguard Worker 
CreateFirstChunk()100*61c4878aSAndroid Build Coastguard Worker std::optional<OwnedChunk> ChunkRegionTracker::CreateFirstChunk() {
101*61c4878aSAndroid Build Coastguard Worker   void* memory = AllocateChunkClass();
102*61c4878aSAndroid Build Coastguard Worker   if (memory == nullptr) {
103*61c4878aSAndroid Build Coastguard Worker     return std::nullopt;
104*61c4878aSAndroid Build Coastguard Worker   }
105*61c4878aSAndroid Build Coastguard Worker   // Note: `Region()` is `const`, so no lock is required.
106*61c4878aSAndroid Build Coastguard Worker   Chunk* chunk = new (memory) Chunk(this, Region());
107*61c4878aSAndroid Build Coastguard Worker   return OwnedChunk(chunk);
108*61c4878aSAndroid Build Coastguard Worker }
109*61c4878aSAndroid Build Coastguard Worker 
Free()110*61c4878aSAndroid Build Coastguard Worker void Chunk::Free() {
111*61c4878aSAndroid Build Coastguard Worker   span_ = ByteSpan();
112*61c4878aSAndroid Build Coastguard Worker   bool region_empty;
113*61c4878aSAndroid Build Coastguard Worker   // Record `region_track` so that it is available for use even after
114*61c4878aSAndroid Build Coastguard Worker   // `this` is free'd by the call to `DeallocateChunkClass`.
115*61c4878aSAndroid Build Coastguard Worker   ChunkRegionTracker* region_tracker = region_tracker_;
116*61c4878aSAndroid Build Coastguard Worker   {
117*61c4878aSAndroid Build Coastguard Worker     std::lock_guard lock(region_tracker_->lock_);
118*61c4878aSAndroid Build Coastguard Worker     region_empty = prev_in_region_ == nullptr && next_in_region_ == nullptr;
119*61c4878aSAndroid Build Coastguard Worker     RemoveFromRegionList();
120*61c4878aSAndroid Build Coastguard Worker     // NOTE: do *not* attempt to access any fields of `this` after this point.
121*61c4878aSAndroid Build Coastguard Worker     //
122*61c4878aSAndroid Build Coastguard Worker     // The lock must be held while deallocating this, otherwise another
123*61c4878aSAndroid Build Coastguard Worker     // ``Chunk::Release`` in the same region could race and call
124*61c4878aSAndroid Build Coastguard Worker     // ``ChunkRegionTracker::Destroy``, making this call no longer valid.
125*61c4878aSAndroid Build Coastguard Worker     region_tracker->DeallocateChunkClass(this);
126*61c4878aSAndroid Build Coastguard Worker   }
127*61c4878aSAndroid Build Coastguard Worker   if (region_empty) {
128*61c4878aSAndroid Build Coastguard Worker     region_tracker->Destroy();
129*61c4878aSAndroid Build Coastguard Worker   }
130*61c4878aSAndroid Build Coastguard Worker }
131*61c4878aSAndroid Build Coastguard Worker 
Release()132*61c4878aSAndroid Build Coastguard Worker void OwnedChunk::Release() {
133*61c4878aSAndroid Build Coastguard Worker   if (inner_ == nullptr) {
134*61c4878aSAndroid Build Coastguard Worker     return;
135*61c4878aSAndroid Build Coastguard Worker   }
136*61c4878aSAndroid Build Coastguard Worker   inner_->Free();
137*61c4878aSAndroid Build Coastguard Worker   inner_ = nullptr;
138*61c4878aSAndroid Build Coastguard Worker }
139*61c4878aSAndroid Build Coastguard Worker 
ClaimPrefix(size_t bytes_to_claim)140*61c4878aSAndroid Build Coastguard Worker bool Chunk::ClaimPrefix(size_t bytes_to_claim) {
141*61c4878aSAndroid Build Coastguard Worker   if (bytes_to_claim == 0) {
142*61c4878aSAndroid Build Coastguard Worker     return true;
143*61c4878aSAndroid Build Coastguard Worker   }
144*61c4878aSAndroid Build Coastguard Worker   // In order to roll back `bytes_to_claim`, the current chunk must start at
145*61c4878aSAndroid Build Coastguard Worker   // least `bytes_to_claim` after the beginning of the current region.
146*61c4878aSAndroid Build Coastguard Worker   std::byte* new_start = CheckedSub(data(), bytes_to_claim);
147*61c4878aSAndroid Build Coastguard Worker   // Note: `Region()` is `const`, so no lock is required.
148*61c4878aSAndroid Build Coastguard Worker   if (new_start == nullptr || new_start < BeginPtr(region_tracker_->Region())) {
149*61c4878aSAndroid Build Coastguard Worker     return false;
150*61c4878aSAndroid Build Coastguard Worker   }
151*61c4878aSAndroid Build Coastguard Worker 
152*61c4878aSAndroid Build Coastguard Worker   // `lock` is acquired in order to traverse the linked list and mutate `span_`.
153*61c4878aSAndroid Build Coastguard Worker   std::lock_guard lock(region_tracker_->lock_);
154*61c4878aSAndroid Build Coastguard Worker 
155*61c4878aSAndroid Build Coastguard Worker   // If there are any chunks before this one, they must not end after
156*61c4878aSAndroid Build Coastguard Worker   // `new_start`.
157*61c4878aSAndroid Build Coastguard Worker   Chunk* prev = prev_in_region_;
158*61c4878aSAndroid Build Coastguard Worker   if (prev != nullptr && EndPtr(*prev) > new_start) {
159*61c4878aSAndroid Build Coastguard Worker     return false;
160*61c4878aSAndroid Build Coastguard Worker   }
161*61c4878aSAndroid Build Coastguard Worker 
162*61c4878aSAndroid Build Coastguard Worker   size_t old_size = span_.size();
163*61c4878aSAndroid Build Coastguard Worker   span_ = ByteSpan(new_start, old_size + bytes_to_claim);
164*61c4878aSAndroid Build Coastguard Worker   return true;
165*61c4878aSAndroid Build Coastguard Worker }
166*61c4878aSAndroid Build Coastguard Worker 
ClaimSuffix(size_t bytes_to_claim)167*61c4878aSAndroid Build Coastguard Worker bool Chunk::ClaimSuffix(size_t bytes_to_claim) {
168*61c4878aSAndroid Build Coastguard Worker   if (bytes_to_claim == 0) {
169*61c4878aSAndroid Build Coastguard Worker     return true;
170*61c4878aSAndroid Build Coastguard Worker   }
171*61c4878aSAndroid Build Coastguard Worker   // In order to expand forward `bytes_to_claim`, the current chunk must start
172*61c4878aSAndroid Build Coastguard Worker   // at least `subytes` before the end of the current region.
173*61c4878aSAndroid Build Coastguard Worker   std::byte* new_end = CheckedAdd(EndPtr(*this), bytes_to_claim);
174*61c4878aSAndroid Build Coastguard Worker   // Note: `Region()` is `const`, so no lock is required.
175*61c4878aSAndroid Build Coastguard Worker   if (new_end == nullptr || new_end > EndPtr(region_tracker_->Region())) {
176*61c4878aSAndroid Build Coastguard Worker     return false;
177*61c4878aSAndroid Build Coastguard Worker   }
178*61c4878aSAndroid Build Coastguard Worker 
179*61c4878aSAndroid Build Coastguard Worker   // `lock` is acquired in order to traverse the linked list and mutate `span_`.
180*61c4878aSAndroid Build Coastguard Worker   std::lock_guard lock(region_tracker_->lock_);
181*61c4878aSAndroid Build Coastguard Worker 
182*61c4878aSAndroid Build Coastguard Worker   // If there are any chunks after this one, they must not start before
183*61c4878aSAndroid Build Coastguard Worker   // `new_end`.
184*61c4878aSAndroid Build Coastguard Worker   Chunk* next = next_in_region_;
185*61c4878aSAndroid Build Coastguard Worker   if (next != nullptr && BeginPtr(next->span_) < new_end) {
186*61c4878aSAndroid Build Coastguard Worker     return false;
187*61c4878aSAndroid Build Coastguard Worker   }
188*61c4878aSAndroid Build Coastguard Worker 
189*61c4878aSAndroid Build Coastguard Worker   size_t old_size = span_.size();
190*61c4878aSAndroid Build Coastguard Worker   span_ = ByteSpan(data(), old_size + bytes_to_claim);
191*61c4878aSAndroid Build Coastguard Worker   return true;
192*61c4878aSAndroid Build Coastguard Worker }
193*61c4878aSAndroid Build Coastguard Worker 
DiscardPrefix(size_t bytes_to_discard)194*61c4878aSAndroid Build Coastguard Worker void Chunk::DiscardPrefix(size_t bytes_to_discard) {
195*61c4878aSAndroid Build Coastguard Worker   Slice(bytes_to_discard, size());
196*61c4878aSAndroid Build Coastguard Worker }
197*61c4878aSAndroid Build Coastguard Worker 
Slice(size_t begin,size_t end)198*61c4878aSAndroid Build Coastguard Worker void Chunk::Slice(size_t begin, size_t end) {
199*61c4878aSAndroid Build Coastguard Worker   PW_DCHECK(begin <= size());
200*61c4878aSAndroid Build Coastguard Worker   PW_DCHECK(end <= size());
201*61c4878aSAndroid Build Coastguard Worker   PW_DCHECK(end >= begin);
202*61c4878aSAndroid Build Coastguard Worker   ByteSpan new_span(data() + begin, end - begin);
203*61c4878aSAndroid Build Coastguard Worker   std::lock_guard lock(region_tracker_->lock_);
204*61c4878aSAndroid Build Coastguard Worker   span_ = new_span;
205*61c4878aSAndroid Build Coastguard Worker }
206*61c4878aSAndroid Build Coastguard Worker 
Truncate(size_t len)207*61c4878aSAndroid Build Coastguard Worker void Chunk::Truncate(size_t len) { Slice(0, len); }
208*61c4878aSAndroid Build Coastguard Worker 
TakePrefix(size_t bytes_to_take)209*61c4878aSAndroid Build Coastguard Worker std::optional<OwnedChunk> Chunk::TakePrefix(size_t bytes_to_take) {
210*61c4878aSAndroid Build Coastguard Worker   void* new_chunk_memory = region_tracker_->AllocateChunkClass();
211*61c4878aSAndroid Build Coastguard Worker   if (new_chunk_memory == nullptr) {
212*61c4878aSAndroid Build Coastguard Worker     return std::nullopt;
213*61c4878aSAndroid Build Coastguard Worker   }
214*61c4878aSAndroid Build Coastguard Worker 
215*61c4878aSAndroid Build Coastguard Worker   PW_DCHECK(bytes_to_take <= size());
216*61c4878aSAndroid Build Coastguard Worker   ByteSpan first_span = ByteSpan(data(), bytes_to_take);
217*61c4878aSAndroid Build Coastguard Worker   ByteSpan second_span =
218*61c4878aSAndroid Build Coastguard Worker       ByteSpan(data() + bytes_to_take, size() - bytes_to_take);
219*61c4878aSAndroid Build Coastguard Worker 
220*61c4878aSAndroid Build Coastguard Worker   std::lock_guard lock(region_tracker_->lock_);
221*61c4878aSAndroid Build Coastguard Worker   span_ = second_span;
222*61c4878aSAndroid Build Coastguard Worker   Chunk* new_chunk = new (new_chunk_memory) Chunk(region_tracker_, first_span);
223*61c4878aSAndroid Build Coastguard Worker   InsertBeforeInRegionList(new_chunk);
224*61c4878aSAndroid Build Coastguard Worker   return OwnedChunk(new_chunk);
225*61c4878aSAndroid Build Coastguard Worker }
226*61c4878aSAndroid Build Coastguard Worker 
TakeSuffix(size_t bytes_to_take)227*61c4878aSAndroid Build Coastguard Worker std::optional<OwnedChunk> Chunk::TakeSuffix(size_t bytes_to_take) {
228*61c4878aSAndroid Build Coastguard Worker   void* new_chunk_memory = region_tracker_->AllocateChunkClass();
229*61c4878aSAndroid Build Coastguard Worker   if (new_chunk_memory == nullptr) {
230*61c4878aSAndroid Build Coastguard Worker     return std::nullopt;
231*61c4878aSAndroid Build Coastguard Worker   }
232*61c4878aSAndroid Build Coastguard Worker 
233*61c4878aSAndroid Build Coastguard Worker   PW_DCHECK(bytes_to_take <= size());
234*61c4878aSAndroid Build Coastguard Worker   ByteSpan first_span = ByteSpan(data(), size() - bytes_to_take);
235*61c4878aSAndroid Build Coastguard Worker   ByteSpan second_span = ByteSpan(EndPtr(*this) - bytes_to_take, bytes_to_take);
236*61c4878aSAndroid Build Coastguard Worker 
237*61c4878aSAndroid Build Coastguard Worker   std::lock_guard lock(region_tracker_->lock_);
238*61c4878aSAndroid Build Coastguard Worker   span_ = first_span;
239*61c4878aSAndroid Build Coastguard Worker   Chunk* new_chunk = new (new_chunk_memory) Chunk(region_tracker_, second_span);
240*61c4878aSAndroid Build Coastguard Worker   InsertAfterInRegionList(new_chunk);
241*61c4878aSAndroid Build Coastguard Worker   return OwnedChunk(new_chunk);
242*61c4878aSAndroid Build Coastguard Worker }
243*61c4878aSAndroid Build Coastguard Worker 
244*61c4878aSAndroid Build Coastguard Worker }  // namespace pw::multibuf
245