Skip to content

Commit 92bd07b

Browse files
authored
Rollup merge of #150096 - Amanieu:revert-borrowedbuf, r=ChrisDenton
Revert #148937 #148937: Remove initialized-bytes tracking from `BorrowedBuf` and `BorrowedCursor` This caused several performance regressions because of existing code which uses `Read::read` and therefore requires full buffer initialization. This is particularly a problem when the same buffer is re-used for multiple read calls since this means it needs to be fully re-initialized each time. There is still some benefit to landing the API changes, but we will have to add private APIs so that the existing infrastructure can track and avoid redundant initialization.
2 parents 15c54bc + 4b07875 commit 92bd07b

File tree

24 files changed

+366
-63
lines changed

24 files changed

+366
-63
lines changed

library/core/src/io/borrowed_buf.rs

Lines changed: 134 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,42 @@
22

33
use crate::fmt::{self, Debug, Formatter};
44
use crate::mem::{self, MaybeUninit};
5+
use crate::{cmp, ptr};
56

6-
/// A borrowed buffer of initially uninitialized bytes, which is incrementally filled.
7+
/// A borrowed byte buffer which is incrementally filled and initialized.
78
///
8-
/// This type makes it safer to work with `MaybeUninit` buffers, such as to read into a buffer
9-
/// without having to initialize it first. It tracks the region of bytes that have been filled and
10-
/// the region that remains uninitialized.
9+
/// This type is a sort of "double cursor". It tracks three regions in the buffer: a region at the beginning of the
10+
/// buffer that has been logically filled with data, a region that has been initialized at some point but not yet
11+
/// logically filled, and a region at the end that is fully uninitialized. The filled region is guaranteed to be a
12+
/// subset of the initialized region.
1113
///
12-
/// The contents of the buffer can be visualized as:
14+
/// In summary, the contents of the buffer can be visualized as:
1315
/// ```not_rust
14-
/// [ capacity ]
15-
/// [ len: filled and initialized | capacity - len: uninitialized ]
16+
/// [ capacity ]
17+
/// [ filled | unfilled ]
18+
/// [ initialized | uninitialized ]
1619
/// ```
1720
///
18-
/// Note that `BorrowedBuf` does not distinguish between uninitialized data and data that was
19-
/// previously initialized but no longer contains valid data.
20-
///
21-
/// A `BorrowedBuf` is created around some existing data (or capacity for data) via a unique
22-
/// reference (`&mut`). The `BorrowedBuf` can be configured (e.g., using `clear` or `set_len`), but
23-
/// cannot be directly written. To write into the buffer, use `unfilled` to create a
24-
/// `BorrowedCursor`. The cursor has write-only access to the unfilled portion of the buffer.
21+
/// A `BorrowedBuf` is created around some existing data (or capacity for data) via a unique reference
22+
/// (`&mut`). The `BorrowedBuf` can be configured (e.g., using `clear` or `set_init`), but cannot be
23+
/// directly written. To write into the buffer, use `unfilled` to create a `BorrowedCursor`. The cursor
24+
/// has write-only access to the unfilled portion of the buffer (you can think of it as a
25+
/// write-only iterator).
2526
///
2627
/// The lifetime `'data` is a bound on the lifetime of the underlying data.
2728
pub struct BorrowedBuf<'data> {
2829
/// The buffer's underlying data.
2930
buf: &'data mut [MaybeUninit<u8>],
3031
/// The length of `self.buf` which is known to be filled.
3132
filled: usize,
33+
/// The length of `self.buf` which is known to be initialized.
34+
init: usize,
3235
}
3336

3437
impl Debug for BorrowedBuf<'_> {
3538
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3639
f.debug_struct("BorrowedBuf")
40+
.field("init", &self.init)
3741
.field("filled", &self.filled)
3842
.field("capacity", &self.capacity())
3943
.finish()
@@ -44,22 +48,24 @@ impl Debug for BorrowedBuf<'_> {
4448
impl<'data> From<&'data mut [u8]> for BorrowedBuf<'data> {
4549
#[inline]
4650
fn from(slice: &'data mut [u8]) -> BorrowedBuf<'data> {
51+
let len = slice.len();
52+
4753
BorrowedBuf {
48-
// SAFETY: Always in bounds. We treat the buffer as uninitialized, even though it's
49-
// already initialized.
54+
// SAFETY: initialized data never becoming uninitialized is an invariant of BorrowedBuf
5055
buf: unsafe { (slice as *mut [u8]).as_uninit_slice_mut().unwrap() },
5156
filled: 0,
57+
init: len,
5258
}
5359
}
5460
}
5561

5662
/// Creates a new `BorrowedBuf` from an uninitialized buffer.
5763
///
58-
/// Use `set_filled` if part of the buffer is known to be already filled.
64+
/// Use `set_init` if part of the buffer is known to be already initialized.
5965
impl<'data> From<&'data mut [MaybeUninit<u8>]> for BorrowedBuf<'data> {
6066
#[inline]
6167
fn from(buf: &'data mut [MaybeUninit<u8>]) -> BorrowedBuf<'data> {
62-
BorrowedBuf { buf, filled: 0 }
68+
BorrowedBuf { buf, filled: 0, init: 0 }
6369
}
6470
}
6571

@@ -68,11 +74,14 @@ impl<'data> From<&'data mut [MaybeUninit<u8>]> for BorrowedBuf<'data> {
6874
/// Use `BorrowedCursor::with_unfilled_buf` instead for a safer alternative.
6975
impl<'data> From<BorrowedCursor<'data>> for BorrowedBuf<'data> {
7076
#[inline]
71-
fn from(buf: BorrowedCursor<'data>) -> BorrowedBuf<'data> {
77+
fn from(mut buf: BorrowedCursor<'data>) -> BorrowedBuf<'data> {
78+
let init = buf.init_mut().len();
7279
BorrowedBuf {
73-
// SAFETY: Always in bounds. We treat the buffer as uninitialized.
80+
// SAFETY: no initialized byte is ever uninitialized as per
81+
// `BorrowedBuf`'s invariant
7482
buf: unsafe { buf.buf.buf.get_unchecked_mut(buf.buf.filled..) },
7583
filled: 0,
84+
init,
7685
}
7786
}
7887
}
@@ -90,6 +99,12 @@ impl<'data> BorrowedBuf<'data> {
9099
self.filled
91100
}
92101

102+
/// Returns the length of the initialized part of the buffer.
103+
#[inline]
104+
pub fn init_len(&self) -> usize {
105+
self.init
106+
}
107+
93108
/// Returns a shared reference to the filled portion of the buffer.
94109
#[inline]
95110
pub fn filled(&self) -> &[u8] {
@@ -144,16 +159,33 @@ impl<'data> BorrowedBuf<'data> {
144159

145160
/// Clears the buffer, resetting the filled region to empty.
146161
///
147-
/// The contents of the buffer are not modified.
162+
/// The number of initialized bytes is not changed, and the contents of the buffer are not modified.
148163
#[inline]
149164
pub fn clear(&mut self) -> &mut Self {
150165
self.filled = 0;
151166
self
152167
}
168+
169+
/// Asserts that the first `n` bytes of the buffer are initialized.
170+
///
171+
/// `BorrowedBuf` assumes that bytes are never de-initialized, so this method does nothing when called with fewer
172+
/// bytes than are already known to be initialized.
173+
///
174+
/// # Safety
175+
///
176+
/// The caller must ensure that the first `n` unfilled bytes of the buffer have already been initialized.
177+
#[inline]
178+
pub unsafe fn set_init(&mut self, n: usize) -> &mut Self {
179+
self.init = cmp::max(self.init, n);
180+
self
181+
}
153182
}
154183

155184
/// A writeable view of the unfilled portion of a [`BorrowedBuf`].
156185
///
186+
/// The unfilled portion consists of an initialized and an uninitialized part; see [`BorrowedBuf`]
187+
/// for details.
188+
///
157189
/// Data can be written directly to the cursor by using [`append`](BorrowedCursor::append) or
158190
/// indirectly by getting a slice of part or all of the cursor and writing into the slice. In the
159191
/// indirect case, the caller must call [`advance`](BorrowedCursor::advance) after writing to inform
@@ -206,17 +238,48 @@ impl<'a> BorrowedCursor<'a> {
206238
self.buf.filled
207239
}
208240

241+
/// Returns a mutable reference to the initialized portion of the cursor.
242+
#[inline]
243+
pub fn init_mut(&mut self) -> &mut [u8] {
244+
// SAFETY: We only slice the initialized part of the buffer, which is always valid
245+
unsafe {
246+
let buf = self.buf.buf.get_unchecked_mut(self.buf.filled..self.buf.init);
247+
buf.assume_init_mut()
248+
}
249+
}
250+
209251
/// Returns a mutable reference to the whole cursor.
210252
///
211253
/// # Safety
212254
///
213-
/// The caller must not uninitialize any previously initialized bytes.
255+
/// The caller must not uninitialize any bytes in the initialized portion of the cursor.
214256
#[inline]
215257
pub unsafe fn as_mut(&mut self) -> &mut [MaybeUninit<u8>] {
216258
// SAFETY: always in bounds
217259
unsafe { self.buf.buf.get_unchecked_mut(self.buf.filled..) }
218260
}
219261

262+
/// Advances the cursor by asserting that `n` bytes have been filled.
263+
///
264+
/// After advancing, the `n` bytes are no longer accessible via the cursor and can only be
265+
/// accessed via the underlying buffer. I.e., the buffer's filled portion grows by `n` elements
266+
/// and its unfilled portion (and the capacity of this cursor) shrinks by `n` elements.
267+
///
268+
/// If less than `n` bytes initialized (by the cursor's point of view), `set_init` should be
269+
/// called first.
270+
///
271+
/// # Panics
272+
///
273+
/// Panics if there are less than `n` bytes initialized.
274+
#[inline]
275+
pub fn advance(&mut self, n: usize) -> &mut Self {
276+
// The subtraction cannot underflow by invariant of this type.
277+
assert!(n <= self.buf.init - self.buf.filled);
278+
279+
self.buf.filled += n;
280+
self
281+
}
282+
220283
/// Advances the cursor by asserting that `n` bytes have been filled.
221284
///
222285
/// After advancing, the `n` bytes are no longer accessible via the cursor and can only be
@@ -225,11 +288,42 @@ impl<'a> BorrowedCursor<'a> {
225288
///
226289
/// # Safety
227290
///
228-
/// The caller must ensure that the first `n` bytes of the cursor have been initialized. `n`
229-
/// must not exceed the remaining capacity of this cursor.
291+
/// The caller must ensure that the first `n` bytes of the cursor have been properly
292+
/// initialised.
230293
#[inline]
231-
pub unsafe fn advance(&mut self, n: usize) -> &mut Self {
294+
pub unsafe fn advance_unchecked(&mut self, n: usize) -> &mut Self {
232295
self.buf.filled += n;
296+
self.buf.init = cmp::max(self.buf.init, self.buf.filled);
297+
self
298+
}
299+
300+
/// Initializes all bytes in the cursor.
301+
#[inline]
302+
pub fn ensure_init(&mut self) -> &mut Self {
303+
// SAFETY: always in bounds and we never uninitialize these bytes.
304+
let uninit = unsafe { self.buf.buf.get_unchecked_mut(self.buf.init..) };
305+
306+
// SAFETY: 0 is a valid value for MaybeUninit<u8> and the length matches the allocation
307+
// since it is comes from a slice reference.
308+
unsafe {
309+
ptr::write_bytes(uninit.as_mut_ptr(), 0, uninit.len());
310+
}
311+
self.buf.init = self.buf.capacity();
312+
313+
self
314+
}
315+
316+
/// Asserts that the first `n` unfilled bytes of the cursor are initialized.
317+
///
318+
/// `BorrowedBuf` assumes that bytes are never de-initialized, so this method does nothing when
319+
/// called with fewer bytes than are already known to be initialized.
320+
///
321+
/// # Safety
322+
///
323+
/// The caller must ensure that the first `n` bytes of the buffer have already been initialized.
324+
#[inline]
325+
pub unsafe fn set_init(&mut self, n: usize) -> &mut Self {
326+
self.buf.init = cmp::max(self.buf.init, self.buf.filled + n);
233327
self
234328
}
235329

@@ -247,6 +341,10 @@ impl<'a> BorrowedCursor<'a> {
247341
self.as_mut()[..buf.len()].write_copy_of_slice(buf);
248342
}
249343

344+
// SAFETY: We just added the entire contents of buf to the filled section.
345+
unsafe {
346+
self.set_init(buf.len());
347+
}
250348
self.buf.filled += buf.len();
251349
}
252350

@@ -269,9 +367,17 @@ impl<'a> BorrowedCursor<'a> {
269367
// there, one could mark some bytes as initialized even though there aren't.
270368
assert!(core::ptr::addr_eq(prev_ptr, buf.buf));
271369

272-
// SAFETY: These bytes were filled in the `BorrowedBuf`, so they're filled in the cursor
273-
// too, because the buffer wasn't replaced.
274-
self.buf.filled += buf.filled;
370+
let filled = buf.filled;
371+
let init = buf.init;
372+
373+
// Update `init` and `filled` fields with what was written to the buffer.
374+
// `self.buf.filled` was the starting length of the `BorrowedBuf`.
375+
//
376+
// SAFETY: These amounts of bytes were initialized/filled in the `BorrowedBuf`,
377+
// and therefore they are initialized/filled in the cursor too, because the
378+
// buffer wasn't replaced.
379+
self.buf.init = self.buf.filled + init;
380+
self.buf.filled += filled;
275381

276382
res
277383
}

0 commit comments

Comments
 (0)