/proc/self/cwd/pw_multibuf/v2/generic_multibuf.cc
Line | Count | Source |
1 | | // Copyright 2025 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 <cstring> |
16 | | #include <utility> |
17 | | |
18 | | #include "pw_assert/check.h" |
19 | | #include "pw_containers/algorithm.h" |
20 | | #include "pw_multibuf/v2/internal/byte_iterator.h" |
21 | | #include "pw_multibuf/v2/multibuf.h" |
22 | | #include "pw_status/try.h" |
23 | | |
24 | | namespace pw::multibuf::v2::internal { |
25 | | |
26 | | template <typename Out> |
27 | 0 | static constexpr Out CheckedCast(size_t val) { |
28 | 0 | size_t max = size_t(std::numeric_limits<Out>::max()); |
29 | 0 | PW_CHECK_UINT_LE(val, max); |
30 | 0 | return static_cast<Out>(val); |
31 | 0 | } Unexecuted instantiation: generic_multibuf.cc:unsigned short pw::multibuf::v2::internal::CheckedCast<unsigned short>(unsigned long) Unexecuted instantiation: generic_multibuf.cc:long pw::multibuf::v2::internal::CheckedCast<long>(unsigned long) |
32 | | |
33 | 0 | GenericMultiBuf& GenericMultiBuf::operator=(GenericMultiBuf&& other) { |
34 | 0 | deque_ = std::move(other.deque_); |
35 | 0 | entries_per_chunk_ = |
36 | 0 | std::exchange(other.entries_per_chunk_, Entry::kMinEntriesPerChunk); |
37 | 0 | observer_ = std::exchange(other.observer_, nullptr); |
38 | 0 | return *this; |
39 | 0 | } |
40 | | |
41 | | bool GenericMultiBuf::TryReserveForInsert(const_iterator pos, |
42 | 0 | const GenericMultiBuf& mb) { |
43 | 0 | size_type entries_per_chunk = entries_per_chunk_; |
44 | 0 | while (entries_per_chunk_ < mb.entries_per_chunk_) { |
45 | 0 | if (!AddLayer(0)) { |
46 | 0 | break; |
47 | 0 | } |
48 | 0 | } |
49 | 0 | if (pos.offset_ == 0 || !IsOwned(pos.chunk_) || |
50 | 0 | TryConvertToShared(pos.chunk_)) { |
51 | 0 | if (entries_per_chunk_ >= mb.entries_per_chunk_ && |
52 | 0 | TryReserveEntries(entries_per_chunk_ * mb.num_chunks(), |
53 | 0 | pos.offset_ != 0)) { |
54 | 0 | return true; |
55 | 0 | } |
56 | 0 | } |
57 | 0 | while (entries_per_chunk_ > entries_per_chunk) { |
58 | 0 | PopLayer(); |
59 | 0 | } |
60 | 0 | return false; |
61 | 0 | } |
62 | | |
63 | 0 | bool GenericMultiBuf::TryReserveForInsert(const_iterator pos) { |
64 | 0 | return (pos.offset_ == 0 || !IsOwned(pos.chunk_) || |
65 | 0 | TryConvertToShared(pos.chunk_)) && |
66 | 0 | TryReserveEntries(entries_per_chunk_, pos.offset_ != 0); |
67 | 0 | } |
68 | | |
69 | | auto GenericMultiBuf::Insert(const_iterator pos, GenericMultiBuf&& mb) |
70 | 0 | -> iterator { |
71 | | // Make room for the other object's entries. |
72 | 0 | PW_CHECK(TryReserveForInsert(pos, mb)); |
73 | 0 | size_type chunk = InsertChunks(pos, mb.num_chunks()); |
74 | | |
75 | | // Merge the entries into this object. |
76 | 0 | size_t size = 0; |
77 | 0 | size_type index = chunk * entries_per_chunk_; |
78 | 0 | while (!mb.empty()) { |
79 | 0 | size_type i = 0; |
80 | 0 | size_type offset = mb.GetOffset(0); |
81 | 0 | size_type length = mb.GetLength(0); |
82 | 0 | for (; i < mb.entries_per_chunk_; ++i) { |
83 | 0 | deque_[index + i] = mb.deque_.front(); |
84 | 0 | mb.deque_.pop_front(); |
85 | 0 | } |
86 | | |
87 | | // If this object is deeper than `mb`, pad it with extra entries. |
88 | 0 | for (; i < entries_per_chunk_; ++i) { |
89 | 0 | deque_[index + i].view = { |
90 | 0 | .offset = offset, |
91 | 0 | .sealed = false, |
92 | 0 | .length = length, |
93 | 0 | .boundary = true, |
94 | 0 | }; |
95 | 0 | } |
96 | 0 | size += size_t{length}; |
97 | 0 | index += entries_per_chunk_; |
98 | 0 | } |
99 | 0 | if (mb.observer_ != nullptr) { |
100 | 0 | mb.observer_->Notify(Observer::Event::kBytesRemoved, size); |
101 | 0 | } |
102 | 0 | if (observer_ != nullptr) { |
103 | 0 | observer_->Notify(Observer::Event::kBytesAdded, size); |
104 | 0 | } |
105 | 0 | return MakeIterator(chunk); |
106 | 0 | } |
107 | | |
108 | | auto GenericMultiBuf::Insert(const_iterator pos, ConstByteSpan bytes) |
109 | 0 | -> iterator { |
110 | 0 | PW_CHECK(TryReserveForInsert(pos)); |
111 | 0 | [[maybe_unused]] auto [iter, unused] = Insert(pos, bytes, 0, bytes.size()); |
112 | 0 | return iter; |
113 | 0 | } |
114 | | |
115 | | auto GenericMultiBuf::Insert(const_iterator pos, |
116 | 0 | UniquePtr<const std::byte[]>&& owned) -> iterator { |
117 | 0 | ConstByteSpan bytes(owned.get(), owned.size()); |
118 | 0 | PW_CHECK(TryReserveForInsert(pos)); |
119 | 0 | auto [iter, chunk] = Insert(pos, bytes, 0, bytes.size()); |
120 | 0 | if (owned == nullptr) { |
121 | 0 | return iter; |
122 | 0 | } |
123 | 0 | deque_[memory_context_index(chunk)].deallocator = owned.deallocator(); |
124 | 0 | deque_[base_view_index(chunk)].base_view.owned = true; |
125 | 0 | owned.Release(); |
126 | 0 | return iter; |
127 | 0 | } |
128 | | |
129 | | auto GenericMultiBuf::Insert(const_iterator pos, |
130 | | const SharedPtr<const std::byte[]>& shared, |
131 | | size_t offset, |
132 | 0 | size_t length) -> iterator { |
133 | 0 | ConstByteSpan bytes(shared.get(), shared.size()); |
134 | 0 | PW_CHECK(TryReserveForInsert(pos)); |
135 | 0 | auto [iter, chunk] = Insert(pos, bytes, offset, length); |
136 | 0 | if (shared == nullptr) { |
137 | 0 | return iter; |
138 | 0 | } |
139 | | |
140 | | // Extract the shared pointer's control block using a restricted method. |
141 | 0 | const auto& handle = ControlBlockHandle::GetInstance_DO_NOT_USE(); |
142 | 0 | ControlBlock* control_block = shared.GetControlBlock(handle); |
143 | |
|
144 | 0 | deque_[memory_context_index(chunk)].control_block = control_block; |
145 | 0 | deque_[base_view_index(chunk)].base_view.shared = true; |
146 | 0 | control_block->IncrementShared(); |
147 | 0 | return iter; |
148 | 0 | } |
149 | | |
150 | | Result<GenericMultiBuf> GenericMultiBuf::Remove(const_iterator pos, |
151 | 0 | size_t size) { |
152 | 0 | PW_CHECK(IsRemovable(pos, size)); |
153 | 0 | GenericMultiBuf out(deque_.get_allocator()); |
154 | 0 | if (!TryReserveForRemove(pos, size, &out)) { |
155 | 0 | return Status::ResourceExhausted(); |
156 | 0 | } |
157 | 0 | MoveRange(pos, size, out); |
158 | 0 | if (observer_ != nullptr) { |
159 | 0 | observer_->Notify(Observer::Event::kBytesRemoved, size); |
160 | 0 | } |
161 | 0 | return Result<GenericMultiBuf>(std::move(out)); |
162 | 0 | } |
163 | | |
164 | 0 | Result<GenericMultiBuf> GenericMultiBuf::PopFrontFragment() { |
165 | 0 | PW_CHECK(!empty()); |
166 | 0 | size_t size = 0; |
167 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
168 | 0 | size_type length = GetLength(chunk); |
169 | 0 | if (length == 0) { |
170 | 0 | continue; |
171 | 0 | } |
172 | 0 | size += size_t{length}; |
173 | 0 | if (IsBoundary(chunk)) { |
174 | 0 | break; |
175 | 0 | } |
176 | 0 | } |
177 | 0 | return Remove(begin(), size); |
178 | 0 | } |
179 | | |
180 | | Result<GenericMultiBuf::const_iterator> GenericMultiBuf::Discard( |
181 | 0 | const_iterator pos, size_t size) { |
182 | 0 | if (!TryReserveForRemove(pos, size, nullptr)) { |
183 | 0 | return Status::ResourceExhausted(); |
184 | 0 | } |
185 | 0 | difference_type out_offset = pos - begin(); |
186 | 0 | ClearRange(pos, size); |
187 | 0 | if (observer_ != nullptr) { |
188 | 0 | observer_->Notify(Observer::Event::kBytesRemoved, size); |
189 | 0 | } |
190 | 0 | return cbegin() + out_offset; |
191 | 0 | } |
192 | | |
193 | 0 | bool GenericMultiBuf::IsReleasable(const_iterator pos) const { |
194 | 0 | PW_CHECK(pos != cend()); |
195 | 0 | return IsOwned(pos.chunk_); |
196 | 0 | } |
197 | | |
198 | 0 | UniquePtr<std::byte[]> GenericMultiBuf::Release(const_iterator pos) { |
199 | 0 | PW_CHECK(IsReleasable(pos)); |
200 | 0 | ByteSpan bytes = GetView(pos.chunk_, 1); |
201 | 0 | Deallocator& deallocator = GetDeallocator(pos.chunk_); |
202 | 0 | EraseRange(pos - pos.offset_, size_t{GetLength(pos.chunk_)}); |
203 | 0 | if (observer_ != nullptr) { |
204 | 0 | observer_->Notify(Observer::Event::kBytesRemoved, bytes.size()); |
205 | 0 | } |
206 | 0 | return UniquePtr<std::byte[]>(bytes.data(), bytes.size(), deallocator); |
207 | 0 | } |
208 | | |
209 | 0 | bool GenericMultiBuf::IsShareable(const_iterator pos) const { |
210 | 0 | PW_CHECK(pos != cend()); |
211 | 0 | return IsShared(pos.chunk_); |
212 | 0 | } |
213 | | |
214 | 0 | SharedPtr<std::byte[]> GenericMultiBuf::Share(const_iterator pos) const { |
215 | 0 | PW_CHECK(IsShareable(pos)); |
216 | 0 | ControlBlock& control_block = GetControlBlock(pos.chunk_); |
217 | 0 | control_block.IncrementShared(); |
218 | 0 | return SharedPtr<std::byte[]>(GetData(pos.chunk_), &control_block); |
219 | 0 | } |
220 | | |
221 | 0 | size_t GenericMultiBuf::CopyFrom(ConstByteSpan src, size_t offset) { |
222 | 0 | size_t total = 0; |
223 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
224 | 0 | if (src.empty()) { |
225 | 0 | break; |
226 | 0 | } |
227 | 0 | ByteSpan view = GetView(chunk); |
228 | 0 | if (offset < view.size()) { |
229 | 0 | size_t size = std::min(view.size() - offset, src.size()); |
230 | 0 | std::memcpy(view.data() + offset, src.data(), size); |
231 | 0 | src = src.subspan(size); |
232 | 0 | offset = 0; |
233 | 0 | total += size; |
234 | 0 | } else { |
235 | 0 | offset -= view.size(); |
236 | 0 | } |
237 | 0 | } |
238 | 0 | return total; |
239 | 0 | } |
240 | | |
241 | 0 | ConstByteSpan GenericMultiBuf::Get(ByteSpan copy, size_t offset) const { |
242 | 0 | ByteSpan buffer; |
243 | 0 | std::optional<size_type> start; |
244 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
245 | 0 | ByteSpan view = GetView(chunk); |
246 | 0 | if (buffer.empty() && offset >= view.size()) { |
247 | | // Still looking for start of data. |
248 | 0 | offset -= view.size(); |
249 | 0 | } else if (buffer.empty()) { |
250 | | // Found the start of data. |
251 | 0 | buffer = view.subspan(offset); |
252 | 0 | start = chunk; |
253 | 0 | } else if (buffer.data() + buffer.size() == view.data()) { |
254 | | // Current view is contiguous with previous; append. |
255 | 0 | buffer = ByteSpan(buffer.data(), buffer.size() + view.size()); |
256 | 0 | } else { |
257 | | // Span is discontiguous and needs to be copied. |
258 | 0 | size_t copied = CopyToImpl(copy, offset, start.value()); |
259 | 0 | return copy.subspan(0, copied); |
260 | 0 | } |
261 | 0 | } |
262 | | // Requested span is contiguous and can be directly passed to the visitor. |
263 | 0 | return buffer.size() <= copy.size() ? buffer : buffer.subspan(0, copy.size()); |
264 | 0 | } |
265 | | |
266 | 0 | void GenericMultiBuf::Clear() { |
267 | 0 | while (num_layers() > 1) { |
268 | 0 | UnsealTopLayer(); |
269 | 0 | PopLayer(); |
270 | 0 | } |
271 | 0 | size_t num_bytes = size(); |
272 | 0 | ClearRange(begin(), num_bytes); |
273 | 0 | if (observer_ != nullptr) { |
274 | 0 | observer_->Notify(Observer::Event::kBytesRemoved, num_bytes); |
275 | 0 | observer_ = nullptr; |
276 | 0 | } |
277 | 0 | } |
278 | | |
279 | 0 | void GenericMultiBuf::ShrinkToFit() { deque_.shrink_to_fit(); } |
280 | | |
281 | 0 | bool GenericMultiBuf::TryReserveLayers(size_t num_layers, size_t num_chunks) { |
282 | 0 | if (num_layers == 0 || num_chunks == 0) { |
283 | 0 | return true; |
284 | 0 | } |
285 | 0 | size_type num_entries = 0; |
286 | 0 | PW_CHECK(CheckedIncrement(num_layers, Entry::kMinEntriesPerChunk - 1)); |
287 | 0 | PW_CHECK(CheckedMul(num_layers, num_chunks, num_entries)); |
288 | 0 | if (num_entries <= deque_.size()) { |
289 | 0 | return true; |
290 | 0 | } |
291 | 0 | return TryReserveEntries(num_entries - deque_.size()); |
292 | 0 | } |
293 | | |
294 | 0 | bool GenericMultiBuf::AddLayer(size_t offset, size_t length) { |
295 | | // Given entries with layers A and B, to which we want to add layer C: |
296 | | // A1 B1 A2 B2 A3 B3 A4 B4 |
297 | | // 1). Add `shift` empty buffers: |
298 | | // A1 B1 A2 B2 A3 B3 A4 B4 -- -- -- -- |
299 | 0 | size_type shift = num_chunks(); |
300 | 0 | if (!TryReserveEntries(shift)) { |
301 | 0 | return false; |
302 | 0 | } |
303 | 0 | ++entries_per_chunk_; |
304 | 0 | for (size_t i = 0; i < shift; ++i) { |
305 | 0 | deque_.push_back({.data = nullptr}); |
306 | 0 | } |
307 | | |
308 | | // 2). Shift the existing layers over. This is expensive, but slicing usually |
309 | | // happens with `shift == 1`: |
310 | | // A1 B1 -- A2 B2 -- A3 B3 -- A4 B4 -- |
311 | 0 | for (size_type i = deque_.size(); i != 0; --i) { |
312 | 0 | if (i % entries_per_chunk_ == 0) { |
313 | 0 | --shift; |
314 | 0 | deque_[i - 1].view = { |
315 | 0 | .offset = 0, |
316 | 0 | .sealed = false, |
317 | 0 | .length = 0, |
318 | 0 | .boundary = false, |
319 | 0 | }; |
320 | 0 | } else { |
321 | 0 | deque_[i - 1] = deque_[i - 1 - shift]; |
322 | 0 | } |
323 | 0 | } |
324 | | |
325 | | // 3). Fill in the new layer C with subspans of layer B: |
326 | | // A1 B1 C1 A2 B2 C2 A3 B3 C3 A4 B4 C4 |
327 | 0 | size_type off = CheckedCast<size_type>(offset); |
328 | 0 | size_type len = length == dynamic_extent ? 0 : CheckedCast<size_type>(length); |
329 | 0 | size_type lower_layer = num_layers() - 1; |
330 | 0 | size_type num_fragments = 0; |
331 | 0 | Entry* last = nullptr; |
332 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
333 | 0 | size_type lower_off = GetOffset(chunk, lower_layer); |
334 | 0 | size_type lower_len = GetLength(chunk, lower_layer); |
335 | |
|
336 | 0 | if (lower_len != 0 && IsBoundary(chunk, lower_layer)) { |
337 | 0 | ++num_fragments; |
338 | 0 | } |
339 | | |
340 | | // Skip over entries until we reach `offset`. |
341 | 0 | Entry& entry = deque_[top_view_index(chunk)]; |
342 | 0 | if (off >= lower_len) { |
343 | 0 | off -= lower_len; |
344 | 0 | entry.view.offset = 0; |
345 | 0 | entry.view.length = 0; |
346 | 0 | continue; |
347 | 0 | } |
348 | 0 | entry.view.offset = lower_off + off; |
349 | 0 | lower_len -= off; |
350 | | |
351 | | // This is similar to `entry.view.length = std::min(lower_len, len);`, but |
352 | | // with extra cases to correctly handle `dynamic_extent` and boundaries. |
353 | 0 | if (lower_len == 0) { |
354 | 0 | entry.view.length = 0; |
355 | 0 | } else if (length == dynamic_extent) { |
356 | 0 | entry.view.length = lower_len; |
357 | 0 | last = &entry; |
358 | 0 | } else if (len == 0) { |
359 | 0 | entry.view.length = 0; |
360 | 0 | } else if (len <= lower_len) { |
361 | 0 | entry.view.length = len; |
362 | 0 | entry.view.boundary = true; |
363 | 0 | len = 0; |
364 | 0 | } else { |
365 | 0 | entry.view.length = lower_len; |
366 | 0 | len -= lower_len; |
367 | 0 | } |
368 | 0 | off = 0; |
369 | 0 | } |
370 | 0 | PW_CHECK_UINT_EQ(len, 0, "Requested layer exceeds available data"); |
371 | | |
372 | 0 | if (last != nullptr) { |
373 | 0 | last->view.boundary = true; |
374 | 0 | } |
375 | 0 | if (observer_ != nullptr) { |
376 | 0 | observer_->Notify(Observer::Event::kLayerAdded, num_fragments); |
377 | 0 | } |
378 | 0 | return true; |
379 | 0 | } |
380 | | |
381 | 0 | void GenericMultiBuf::SealTopLayer() { |
382 | 0 | PW_CHECK_UINT_GT(num_layers(), 1); |
383 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
384 | 0 | deque_[top_view_index(chunk)].view.sealed = true; |
385 | 0 | } |
386 | 0 | } |
387 | | |
388 | 0 | void GenericMultiBuf::UnsealTopLayer() { |
389 | 0 | PW_CHECK_UINT_GT(num_layers(), 1); |
390 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
391 | 0 | deque_[top_view_index(chunk)].view.sealed = false; |
392 | 0 | } |
393 | 0 | } |
394 | | |
395 | 0 | void GenericMultiBuf::TruncateTopLayer(size_t length) { |
396 | 0 | PW_CHECK_UINT_GT(num_layers(), 1); |
397 | | |
398 | 0 | size_type len = CheckedCast<size_type>(length); |
399 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
400 | 0 | PW_CHECK(!IsSealed(chunk), |
401 | 0 | "MultiBuf::TruncateTopLayer() was called on a sealed layer; call " |
402 | 0 | "UnsealTopLayer first"); |
403 | 0 | size_type chunk_len = GetLength(chunk); |
404 | 0 | if (len >= chunk_len) { |
405 | 0 | len -= chunk_len; |
406 | 0 | continue; |
407 | 0 | } |
408 | 0 | deque_[top_view_index(chunk)].view.length = len; |
409 | 0 | len = 0; |
410 | 0 | } |
411 | 0 | PW_CHECK_UINT_EQ(len, |
412 | 0 | 0, |
413 | 0 | "MultiBuf::TruncateTopLayer() was called with a length " |
414 | 0 | "longer than the MultiBuf"); |
415 | 0 | } |
416 | | |
417 | 0 | void GenericMultiBuf::PopLayer() { |
418 | 0 | PW_CHECK_UINT_GT(num_layers(), 1); |
419 | | |
420 | | // Given entries with layers A, B, and C, to remove layer C: |
421 | | // A1 B1 C1 A2 B2 C2 A3 B3 C3 A4 B4 C4 |
422 | | // 1). Check that the layer is not sealed. |
423 | 0 | size_t num_fragments = 0; |
424 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
425 | 0 | PW_CHECK(!IsSealed(chunk), |
426 | 0 | "MultiBuf::PopLayer() was called on a sealed layer; call " |
427 | 0 | "UnsealTopLayer first"); |
428 | 0 | if (GetLength(chunk) != 0 && IsBoundary(chunk)) { |
429 | 0 | ++num_fragments; |
430 | 0 | } |
431 | 0 | } |
432 | | |
433 | | // 2). Compress lower layers backward. |
434 | | // -- -- -- -- A1 B1 A2 B2 A3 B3 A4 B4 |
435 | 0 | size_type shift = 0; |
436 | 0 | size_type discard = deque_.size() / entries_per_chunk_; |
437 | 0 | size_type keep = deque_.size() - discard; |
438 | 0 | --entries_per_chunk_; |
439 | 0 | for (size_type i = 1; i <= keep; ++i) { |
440 | 0 | size_type j = deque_.size() - i; |
441 | 0 | if ((i - 1) % entries_per_chunk_ == 0) { |
442 | 0 | ++shift; |
443 | 0 | } |
444 | 0 | deque_[j] = deque_[j - shift]; |
445 | 0 | if ((j - discard) % entries_per_chunk_ != entries_per_chunk_ - 1) { |
446 | 0 | continue; |
447 | 0 | } |
448 | 0 | } |
449 | | |
450 | | // 3). Discard the first elements |
451 | | // A1 B1 A2 B2 A3 B3 A4 B4 |
452 | 0 | for (size_type i = 0; i < discard; ++i) { |
453 | 0 | deque_.pop_front(); |
454 | 0 | } |
455 | 0 | if (observer_ != nullptr) { |
456 | 0 | observer_->Notify(Observer::Event::kLayerRemoved, num_fragments); |
457 | 0 | } |
458 | 0 | } |
459 | | |
460 | | // Implementation methods |
461 | | |
462 | 0 | size_t GenericMultiBuf::CheckRange(size_t offset, size_t length, size_t size) { |
463 | 0 | PW_CHECK_UINT_LE(size, Entry::kMaxSize); |
464 | 0 | PW_CHECK_UINT_LE(offset, size); |
465 | 0 | if (length == dynamic_extent) { |
466 | 0 | return size - offset; |
467 | 0 | } |
468 | 0 | PW_CHECK_UINT_LE(length, size - offset); |
469 | 0 | return length; |
470 | 0 | } |
471 | | |
472 | 0 | GenericMultiBuf::size_type GenericMultiBuf::NumFragments() const { |
473 | 0 | size_type num_fragments = 0; |
474 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
475 | 0 | if (GetLength(chunk) != 0 && IsBoundary(chunk)) { |
476 | 0 | ++num_fragments; |
477 | 0 | } |
478 | 0 | } |
479 | 0 | return num_fragments; |
480 | 0 | } |
481 | | |
482 | 0 | bool GenericMultiBuf::TryConvertToShared(size_type chunk) { |
483 | 0 | Deallocator& deallocator = GetDeallocator(chunk); |
484 | 0 | std::byte* data = GetData(chunk); |
485 | 0 | Entry::BaseView& base_view = deque_[base_view_index(chunk)].base_view; |
486 | | |
487 | | // Create a new control block using a restricted method. |
488 | 0 | const auto& handle = ControlBlockHandle::GetInstance_DO_NOT_USE(); |
489 | 0 | auto* control_block = |
490 | 0 | ControlBlock::Create(handle, &deallocator, data, base_view.length); |
491 | |
|
492 | 0 | if (control_block == nullptr) { |
493 | 0 | return false; |
494 | 0 | } |
495 | 0 | deque_[memory_context_index(chunk)].control_block = control_block; |
496 | 0 | base_view.owned = false; |
497 | 0 | base_view.shared = true; |
498 | 0 | return true; |
499 | 0 | } |
500 | | |
501 | 0 | bool GenericMultiBuf::TryReserveEntries(size_type num_entries, bool split) { |
502 | 0 | if (split) { |
503 | 0 | PW_CHECK(CheckedAdd(num_entries, entries_per_chunk_, num_entries)); |
504 | 0 | } |
505 | 0 | PW_CHECK(CheckedAdd(num_entries, deque_.size(), num_entries)); |
506 | 0 | return deque_.try_reserve_exact(num_entries); |
507 | 0 | } |
508 | | |
509 | | GenericMultiBuf::size_type GenericMultiBuf::InsertChunks(const_iterator pos, |
510 | 0 | size_type num_chunks) { |
511 | 0 | size_type chunk = pos.chunk_; |
512 | 0 | size_type offset = pos.offset_; |
513 | 0 | if (offset != 0) { |
514 | 0 | num_chunks++; |
515 | 0 | } else if (chunk < this->num_chunks()) { |
516 | | // Insert before any empty chunks at `pos`. |
517 | 0 | while (chunk != 0 && GetLength(chunk - 1) == 0) { |
518 | 0 | --chunk; |
519 | 0 | } |
520 | 0 | } |
521 | 0 | size_type num_entries = num_chunks * entries_per_chunk_; |
522 | 0 | PW_CHECK(TryReserveEntries(num_entries, offset != 0)); |
523 | 0 | Entry entry; |
524 | 0 | entry.data = nullptr; |
525 | 0 | for (size_type i = 0; i < num_entries; ++i) { |
526 | 0 | deque_.push_back(entry); |
527 | 0 | } |
528 | 0 | size_type index = chunk * entries_per_chunk_; |
529 | 0 | for (size_type i = deque_.size() - 1; i >= index + num_entries; --i) { |
530 | 0 | deque_[i] = deque_[i - num_entries]; |
531 | 0 | } |
532 | |
|
533 | 0 | if (offset == 0) { |
534 | | // New chunk falls between existing chunks. |
535 | 0 | return chunk; |
536 | 0 | } |
537 | | // New chunk within an existing chunk, which must be split. |
538 | 0 | SplitAfter(chunk, offset, deque_, chunk + num_chunks); |
539 | 0 | SplitBefore(chunk, offset); |
540 | 0 | return chunk + 1; |
541 | 0 | } |
542 | | |
543 | | auto GenericMultiBuf::Insert(const_iterator pos, |
544 | | ConstByteSpan bytes, |
545 | | size_t offset, |
546 | 0 | size_t length) -> std::tuple<iterator, size_type> { |
547 | 0 | size_type chunk = InsertChunks(pos, 1); |
548 | 0 | deque_[memory_context_index(chunk)].deallocator = nullptr; |
549 | 0 | deque_[data_index(chunk)].data = const_cast<std::byte*>(bytes.data()); |
550 | 0 | auto off = CheckedCast<size_type>(offset); |
551 | 0 | if (length == dynamic_extent) { |
552 | 0 | length = bytes.size() - offset; |
553 | 0 | } |
554 | 0 | auto len = CheckedCast<size_type>(length); |
555 | 0 | deque_[base_view_index(chunk)].base_view = { |
556 | 0 | .offset = off, |
557 | 0 | .owned = false, |
558 | 0 | .length = len, |
559 | 0 | .shared = false, |
560 | 0 | }; |
561 | 0 | for (size_type layer = 2; layer <= num_layers(); ++layer) { |
562 | 0 | deque_[view_index(chunk, layer)].view = { |
563 | 0 | .offset = off, |
564 | 0 | .sealed = false, |
565 | 0 | .length = len, |
566 | 0 | .boundary = true, |
567 | 0 | }; |
568 | 0 | } |
569 | 0 | if (observer_ != nullptr) { |
570 | 0 | observer_->Notify(Observer::Event::kBytesAdded, length); |
571 | 0 | } |
572 | 0 | return std::make_tuple(MakeIterator(chunk), chunk); |
573 | 0 | } |
574 | | |
575 | | void GenericMultiBuf::SplitBase(size_type chunk, |
576 | | Deque& out_deque, |
577 | 0 | size_type out_chunk) { |
578 | 0 | if (&deque_ == &out_deque && chunk == out_chunk) { |
579 | 0 | return; |
580 | 0 | } |
581 | 0 | PW_CHECK(!IsOwned(chunk)); |
582 | 0 | size_type index = chunk * entries_per_chunk_; |
583 | 0 | size_type out_index = out_chunk * entries_per_chunk_; |
584 | 0 | for (size_type i = 0; i < entries_per_chunk_; ++i) { |
585 | 0 | out_deque[out_index + i] = deque_[index + i]; |
586 | 0 | } |
587 | 0 | if (IsShared(chunk)) { |
588 | 0 | GetControlBlock(chunk).IncrementShared(); |
589 | 0 | } |
590 | 0 | } |
591 | | |
592 | | void GenericMultiBuf::SplitBefore(size_type chunk, |
593 | | size_type split, |
594 | | Deque& out_deque, |
595 | 0 | size_type out_chunk) { |
596 | 0 | SplitBase(chunk, out_deque, out_chunk); |
597 | 0 | split += GetOffset(chunk); |
598 | 0 | Entry::BaseView src_base_view = deque_[base_view_index(chunk)].base_view; |
599 | 0 | Entry::BaseView& dst_base_view = |
600 | 0 | out_deque[base_view_index(out_chunk)].base_view; |
601 | 0 | dst_base_view.offset = src_base_view.offset; |
602 | 0 | dst_base_view.length = split - src_base_view.offset; |
603 | 0 | for (size_type layer = 2; layer <= num_layers(); ++layer) { |
604 | 0 | Entry::View src_view = deque_[view_index(chunk, layer)].view; |
605 | 0 | Entry::View& dst_view = out_deque[view_index(out_chunk, layer)].view; |
606 | 0 | dst_view.offset = src_view.offset; |
607 | 0 | dst_view.length = split - src_view.offset; |
608 | 0 | } |
609 | 0 | } |
610 | | |
611 | 0 | void GenericMultiBuf::SplitBefore(size_type chunk, size_type split) { |
612 | 0 | SplitBefore(chunk, split, deque_, chunk); |
613 | 0 | } |
614 | | |
615 | | void GenericMultiBuf::SplitAfter(size_type chunk, |
616 | | size_type split, |
617 | | Deque& out_deque, |
618 | 0 | size_type out_chunk) { |
619 | 0 | SplitBase(chunk, out_deque, out_chunk); |
620 | 0 | split += GetOffset(chunk); |
621 | 0 | Entry::BaseView src_base_view = deque_[base_view_index(chunk)].base_view; |
622 | 0 | Entry::BaseView& dst_base_view = |
623 | 0 | out_deque[base_view_index(out_chunk)].base_view; |
624 | 0 | dst_base_view.offset = split; |
625 | 0 | dst_base_view.length = src_base_view.offset + src_base_view.length - split; |
626 | 0 | for (size_type layer = 2; layer <= num_layers(); ++layer) { |
627 | 0 | Entry::View src_view = deque_[view_index(chunk, layer)].view; |
628 | 0 | Entry::View& dst_view = out_deque[view_index(out_chunk, layer)].view; |
629 | 0 | dst_view.offset = split; |
630 | 0 | dst_view.length = src_view.offset + src_view.length - split; |
631 | 0 | } |
632 | 0 | } |
633 | | |
634 | 0 | void GenericMultiBuf::SplitAfter(size_type chunk, size_type split) { |
635 | 0 | SplitAfter(chunk, split, deque_, chunk); |
636 | 0 | } |
637 | | |
638 | | bool GenericMultiBuf::TryReserveForRemove(const_iterator pos, |
639 | | size_t size, |
640 | 0 | GenericMultiBuf* out) { |
641 | 0 | PW_CHECK_UINT_NE(size, 0u); |
642 | 0 | auto end = pos + CheckedCast<difference_type>(size); |
643 | 0 | size_type shift = end.chunk_ - pos.chunk_; |
644 | | |
645 | | // If removing part of an owned chunk, make it shared. |
646 | 0 | if (pos.offset_ != 0 && IsOwned(pos.chunk_) && |
647 | 0 | !TryConvertToShared(pos.chunk_)) { |
648 | 0 | return false; |
649 | 0 | } |
650 | | |
651 | | // Removing a sub-chunk. |
652 | 0 | if (shift == 0 && pos.offset_ != 0) { |
653 | 0 | return (out == nullptr || out->TryReserveEntries(entries_per_chunk_)) && |
654 | 0 | TryReserveEntries(0, /*split=*/true); |
655 | 0 | } |
656 | | |
657 | | // If removing part of an owned chunk, make it shared. |
658 | 0 | if (end.offset_ != 0 && IsOwned(end.chunk_) && |
659 | 0 | !TryConvertToShared(end.chunk_)) { |
660 | 0 | return false; |
661 | 0 | } |
662 | | |
663 | | // Discarding entries, no room needed. |
664 | 0 | if (out == nullptr) { |
665 | 0 | return true; |
666 | 0 | } |
667 | | |
668 | | // Make room in `out`. |
669 | 0 | if (end.offset_ != 0) { |
670 | 0 | ++shift; |
671 | 0 | } |
672 | 0 | return out->TryReserveEntries(shift * entries_per_chunk_); |
673 | 0 | } |
674 | | |
675 | | void GenericMultiBuf::MoveRange(const_iterator pos, |
676 | | size_t size, |
677 | 0 | GenericMultiBuf& out) { |
678 | 0 | size_type chunk = pos.chunk_; |
679 | 0 | size_type offset = pos.offset_; |
680 | 0 | auto end = pos + CheckedCast<difference_type>(size); |
681 | 0 | out.entries_per_chunk_ = entries_per_chunk_; |
682 | | |
683 | | // Determine how many entries needs to be moved. |
684 | 0 | size_type shift = end.chunk_ - chunk; |
685 | | |
686 | | // Are we removing the prefix of a single chunk? |
687 | 0 | if (shift == 0 && offset == 0) { |
688 | 0 | out.InsertChunks(begin(), 1); |
689 | 0 | SplitBefore(chunk, end.offset_, out.deque_, 0); |
690 | 0 | EraseRange(pos, size); |
691 | 0 | return; |
692 | 0 | } |
693 | | |
694 | | // Are we removing a sub-chunk? If so, split the chunk in two. |
695 | 0 | if (shift == 0) { |
696 | 0 | out.InsertChunks(begin(), 1); |
697 | 0 | SplitBefore(end.chunk_, end.offset_, out.deque_, 0); |
698 | 0 | out.SplitAfter(0, offset); |
699 | 0 | EraseRange(pos, size); |
700 | 0 | return; |
701 | 0 | } |
702 | | |
703 | | // Otherwise, start by copying entries to the new deque, if provided. |
704 | 0 | size_type out_chunk = 0; |
705 | 0 | size_type reserve = end.offset_ == 0 ? shift : shift + 1; |
706 | 0 | out.InsertChunks(cend(), reserve); |
707 | | |
708 | | // Move the suffix of the first chunk. |
709 | 0 | if (offset != 0) { |
710 | 0 | SplitAfter(chunk, offset, out.deque_, out_chunk); |
711 | 0 | --shift; |
712 | 0 | ++chunk; |
713 | 0 | ++out_chunk; |
714 | 0 | } |
715 | | |
716 | | // Move the complete chunks. |
717 | 0 | size_type index = chunk * entries_per_chunk_; |
718 | 0 | size_type end_index = end.chunk_ * entries_per_chunk_; |
719 | 0 | size_type out_index = out_chunk * entries_per_chunk_; |
720 | 0 | pw::copy(deque_.begin() + index, |
721 | 0 | deque_.begin() + end_index, |
722 | 0 | out.deque_.begin() + out_index); |
723 | 0 | chunk += shift; |
724 | 0 | out_chunk += shift; |
725 | | |
726 | | // Copy the prefix of the last chunk. |
727 | 0 | if (end.offset_ != 0) { |
728 | 0 | SplitBefore(end.chunk_, end.offset_, out.deque_, out_chunk); |
729 | 0 | } |
730 | 0 | EraseRange(pos, size); |
731 | 0 | } |
732 | | |
733 | 0 | void GenericMultiBuf::ClearRange(const_iterator pos, size_t size) { |
734 | 0 | size_type chunk = pos.chunk_; |
735 | 0 | size_type offset = pos.offset_; |
736 | 0 | auto end = pos + CheckedCast<difference_type>(size); |
737 | 0 | if (offset != 0) { |
738 | 0 | ++chunk; |
739 | 0 | } |
740 | 0 | for (; chunk < end.chunk_; ++chunk) { |
741 | 0 | std::byte* data = GetData(chunk); |
742 | 0 | if (IsOwned(chunk)) { |
743 | 0 | Deallocator& deallocator = GetDeallocator(chunk); |
744 | 0 | deallocator.Deallocate(data); |
745 | 0 | continue; |
746 | 0 | } |
747 | 0 | if (!IsShared(chunk)) { |
748 | 0 | continue; |
749 | 0 | } |
750 | | // To avoid races with other shared or weak pointers to the data, put the |
751 | | // data pointer back into a SharedPtr and let it go out scope. |
752 | 0 | ControlBlock& control_block = GetControlBlock(chunk); |
753 | 0 | SharedPtr<std::byte[]> shared(data, &control_block); |
754 | 0 | } |
755 | 0 | EraseRange(pos, size); |
756 | 0 | } |
757 | | |
758 | 0 | void GenericMultiBuf::EraseRange(const_iterator pos, size_t size) { |
759 | 0 | size_type chunk = pos.chunk_; |
760 | 0 | size_type offset = pos.offset_; |
761 | 0 | auto end = pos + CheckedCast<difference_type>(size); |
762 | | |
763 | | // Are we removing a sub-chunk? If so, split the chunk in two. |
764 | 0 | if (chunk == end.chunk_ && offset != 0) { |
765 | 0 | size_type new_chunk = InsertChunks(pos, 0); |
766 | 0 | SplitAfter(new_chunk, end.offset_ - offset); |
767 | 0 | return; |
768 | 0 | } |
769 | | |
770 | | // Discard suffix of first chunk. |
771 | 0 | if (offset != 0) { |
772 | 0 | SplitBefore(chunk, offset); |
773 | 0 | ++chunk; |
774 | 0 | } |
775 | | |
776 | | // Discard prefix of last chunk. |
777 | 0 | if (end.offset_ != 0) { |
778 | 0 | SplitAfter(end.chunk_, end.offset_); |
779 | 0 | } |
780 | | |
781 | | // Discard complete chunks. |
782 | 0 | if (chunk < end.chunk_) { |
783 | 0 | deque_.erase(deque_.begin() + (chunk * entries_per_chunk_), |
784 | 0 | deque_.begin() + (end.chunk_ * entries_per_chunk_)); |
785 | 0 | } |
786 | 0 | } |
787 | | |
788 | | size_t GenericMultiBuf::CopyToImpl(ByteSpan dst, |
789 | | size_t offset, |
790 | 0 | size_type start) const { |
791 | 0 | size_t total = 0; |
792 | 0 | for (size_type chunk = start; chunk < num_chunks(); ++chunk) { |
793 | 0 | if (dst.empty()) { |
794 | 0 | break; |
795 | 0 | } |
796 | 0 | ConstByteSpan view = GetView(chunk); |
797 | 0 | if (offset < view.size()) { |
798 | 0 | size_t size = std::min(view.size() - offset, dst.size()); |
799 | 0 | std::memcpy(dst.data(), view.data() + offset, size); |
800 | 0 | dst = dst.subspan(size); |
801 | 0 | offset = 0; |
802 | 0 | total += size; |
803 | 0 | } else { |
804 | 0 | offset -= view.size(); |
805 | 0 | } |
806 | 0 | } |
807 | 0 | return total; |
808 | 0 | } |
809 | | |
810 | 0 | bool GenericMultiBuf::IsTopLayerSealed() const { |
811 | 0 | for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
812 | 0 | if (IsSealed(chunk)) { |
813 | 0 | return true; |
814 | 0 | } |
815 | 0 | } |
816 | 0 | return false; |
817 | 0 | } |
818 | | |
819 | | } // namespace pw::multibuf::v2::internal |