From 74e76288f4da4b2d7ce89b0112ef06ec206cc45d Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Wed, 16 Aug 2023 07:44:07 -0500 Subject: [PATCH 01/60] added bytes-specific get_X functions to reduce dead code --- src/bytes.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/bytes.rs b/src/bytes.rs index 0404a72db..225dbcd68 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -533,6 +533,23 @@ impl Clone for Bytes { } } +macro_rules! buf_get_impl { + ($this:ident, $typ:tt::$conv:tt) => {{ + const SIZE: usize = mem::size_of::<$typ>(); + // slice.get() returns None on failing bounds check, resulting in a panic, but skips the unnecessary code of the + // default buf impl that needs to account for non-contiguous memory + let ret = $this + .chunk() + .get(..SIZE) + .map(|src| unsafe { $typ::$conv(*(src as *const _ as *const [_; SIZE])) }) + .unwrap(); + + // if the direct conversion was possible, advance and return + $this.advance(SIZE); + return ret; + }}; +} + impl Buf for Bytes { #[inline] fn remaining(&self) -> usize { @@ -567,6 +584,102 @@ impl Buf for Bytes { ret } } + + fn get_u16(&mut self) -> u16 { + buf_get_impl!(self, u16::from_be_bytes); + } + + fn get_u16_le(&mut self) -> u16 { + buf_get_impl!(self, u16::from_le_bytes); + } + + fn get_u16_ne(&mut self) -> u16 { + buf_get_impl!(self, u16::from_ne_bytes); + } + + fn get_i16(&mut self) -> i16 { + buf_get_impl!(self, i16::from_be_bytes); + } + + fn get_i16_le(&mut self) -> i16 { + buf_get_impl!(self, i16::from_le_bytes); + } + + fn get_i16_ne(&mut self) -> i16 { + buf_get_impl!(self, i16::from_ne_bytes); + } + + fn get_u32(&mut self) -> u32 { + buf_get_impl!(self, u32::from_be_bytes); + } + + fn get_u32_le(&mut self) -> u32 { + buf_get_impl!(self, u32::from_le_bytes); + } + + fn get_u32_ne(&mut self) -> u32 { + buf_get_impl!(self, u32::from_ne_bytes); + } + + fn get_i32(&mut self) -> i32 { + buf_get_impl!(self, i32::from_be_bytes); + } + + fn get_i32_le(&mut self) -> i32 { + buf_get_impl!(self, i32::from_le_bytes); + } + + fn get_i32_ne(&mut self) -> i32 { + buf_get_impl!(self, i32::from_ne_bytes); + } + + fn get_u64(&mut self) -> u64 { + buf_get_impl!(self, u64::from_be_bytes); + } + + fn get_u64_le(&mut self) -> u64 { + buf_get_impl!(self, u64::from_le_bytes); + } + + fn get_u64_ne(&mut self) -> u64 { + buf_get_impl!(self, u64::from_ne_bytes); + } + + fn get_i64(&mut self) -> i64 { + buf_get_impl!(self, i64::from_be_bytes); + } + + fn get_i64_le(&mut self) -> i64 { + buf_get_impl!(self, i64::from_le_bytes); + } + + fn get_i64_ne(&mut self) -> i64 { + buf_get_impl!(self, i64::from_ne_bytes); + } + + fn get_u128(&mut self) -> u128 { + buf_get_impl!(self, u128::from_be_bytes); + } + + fn get_u128_le(&mut self) -> u128 { + buf_get_impl!(self, u128::from_le_bytes); + } + + fn get_u128_ne(&mut self) -> u128 { + buf_get_impl!(self, u128::from_ne_bytes); + } + + fn get_i128(&mut self) -> i128 { + buf_get_impl!(self, i128::from_be_bytes); + } + + fn get_i128_le(&mut self) -> i128 { + buf_get_impl!(self, i128::from_le_bytes); + } + + fn get_i128_ne(&mut self) -> i128 { + buf_get_impl!(self, i128::from_ne_bytes); + } } impl Deref for Bytes { From b49f3d1bc449a75c7e7a6e3976ce90934afcb78e Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Wed, 16 Aug 2023 08:21:39 -0500 Subject: [PATCH 02/60] added bytes-specific get_X tests --- tests/test_bytes.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 5ec60a5b0..a12816ab5 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1208,3 +1208,18 @@ fn test_bytes_capacity_len() { } } } + +#[test] +fn test_get_u16() { + let mut buf = Bytes::from(&b"\x21\x54zomg"[..]); + assert_eq!(0x2154, buf.get_u16()); + let mut buf = Bytes::from(&b"\x21\x54zomg"[..]); + assert_eq!(0x5421, buf.get_u16_le()); +} + +#[test] +#[should_panic] +fn test_get_u16_buffer_underflow() { + let mut buf = Bytes::from(&b"\x21"[..]); + buf.get_u16(); +} \ No newline at end of file From 329acdb2fcc7e787b93969e976c48167a79634a9 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Wed, 16 Aug 2023 22:34:27 -0500 Subject: [PATCH 03/60] added inlining for Bytes get_X functions --- src/bytes.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/bytes.rs b/src/bytes.rs index 225dbcd68..fc4dccf1d 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -585,98 +585,122 @@ impl Buf for Bytes { } } + #[inline] fn get_u16(&mut self) -> u16 { buf_get_impl!(self, u16::from_be_bytes); } + #[inline] fn get_u16_le(&mut self) -> u16 { buf_get_impl!(self, u16::from_le_bytes); } + #[inline] fn get_u16_ne(&mut self) -> u16 { buf_get_impl!(self, u16::from_ne_bytes); } + #[inline] fn get_i16(&mut self) -> i16 { buf_get_impl!(self, i16::from_be_bytes); } + #[inline] fn get_i16_le(&mut self) -> i16 { buf_get_impl!(self, i16::from_le_bytes); } + #[inline] fn get_i16_ne(&mut self) -> i16 { buf_get_impl!(self, i16::from_ne_bytes); } + #[inline] fn get_u32(&mut self) -> u32 { buf_get_impl!(self, u32::from_be_bytes); } + #[inline] fn get_u32_le(&mut self) -> u32 { buf_get_impl!(self, u32::from_le_bytes); } + #[inline] fn get_u32_ne(&mut self) -> u32 { buf_get_impl!(self, u32::from_ne_bytes); } + #[inline] fn get_i32(&mut self) -> i32 { buf_get_impl!(self, i32::from_be_bytes); } + #[inline] fn get_i32_le(&mut self) -> i32 { buf_get_impl!(self, i32::from_le_bytes); } + #[inline] fn get_i32_ne(&mut self) -> i32 { buf_get_impl!(self, i32::from_ne_bytes); } + #[inline] fn get_u64(&mut self) -> u64 { buf_get_impl!(self, u64::from_be_bytes); } + #[inline] fn get_u64_le(&mut self) -> u64 { buf_get_impl!(self, u64::from_le_bytes); } + #[inline] fn get_u64_ne(&mut self) -> u64 { buf_get_impl!(self, u64::from_ne_bytes); } + #[inline] fn get_i64(&mut self) -> i64 { buf_get_impl!(self, i64::from_be_bytes); } + #[inline] fn get_i64_le(&mut self) -> i64 { buf_get_impl!(self, i64::from_le_bytes); } + #[inline] fn get_i64_ne(&mut self) -> i64 { buf_get_impl!(self, i64::from_ne_bytes); } + #[inline] fn get_u128(&mut self) -> u128 { buf_get_impl!(self, u128::from_be_bytes); } + #[inline] fn get_u128_le(&mut self) -> u128 { buf_get_impl!(self, u128::from_le_bytes); } + #[inline] fn get_u128_ne(&mut self) -> u128 { buf_get_impl!(self, u128::from_ne_bytes); } + #[inline] fn get_i128(&mut self) -> i128 { buf_get_impl!(self, i128::from_be_bytes); } + #[inline] fn get_i128_le(&mut self) -> i128 { buf_get_impl!(self, i128::from_le_bytes); } + #[inline] fn get_i128_ne(&mut self) -> i128 { buf_get_impl!(self, i128::from_ne_bytes); } From f6e110bb02fe807101213b211be1c1b8a303ba25 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Sun, 24 Sep 2023 18:19:29 -0500 Subject: [PATCH 04/60] fixed formatting --- tests/test_bytes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index a12816ab5..779c2e924 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1222,4 +1222,4 @@ fn test_get_u16() { fn test_get_u16_buffer_underflow() { let mut buf = Bytes::from(&b"\x21"[..]); buf.get_u16(); -} \ No newline at end of file +} From 61945724ffb673d35acb7f373833ceacd3cb27ba Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:11:20 -0600 Subject: [PATCH 05/60] more direct get strategy and additional tests --- src/bytes.rs | 66 +++++++++++++++++++++++++-------------------- tests/test_bytes.rs | 17 ++++++++++++ 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index f01617a2d..a4c4563c1 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -534,13 +534,11 @@ impl Clone for Bytes { macro_rules! buf_get_impl { ($this:ident, $typ:tt::$conv:tt) => {{ const SIZE: usize = mem::size_of::<$typ>(); + + assert!($this.len >= SIZE); // slice.get() returns None on failing bounds check, resulting in a panic, but skips the unnecessary code of the // default buf impl that needs to account for non-contiguous memory - let ret = $this - .chunk() - .get(..SIZE) - .map(|src| unsafe { $typ::$conv(*(src as *const _ as *const [_; SIZE])) }) - .unwrap(); + let ret = unsafe { $typ::$conv(($this.ptr as *const $typ).read_unaligned()) }; // if the direct conversion was possible, advance and return $this.advance(SIZE); @@ -583,124 +581,134 @@ impl Buf for Bytes { } } + #[inline] + fn get_u8(&mut self) -> u8 { + buf_get_impl!(self, u8::from) + } + + #[inline] + fn get_i8(&mut self) -> i8 { + buf_get_impl!(self, i8::from) + } + #[inline] fn get_u16(&mut self) -> u16 { - buf_get_impl!(self, u16::from_be_bytes); + buf_get_impl!(self, u16::from_be); } #[inline] fn get_u16_le(&mut self) -> u16 { - buf_get_impl!(self, u16::from_le_bytes); + buf_get_impl!(self, u16::from_le); } #[inline] fn get_u16_ne(&mut self) -> u16 { - buf_get_impl!(self, u16::from_ne_bytes); + buf_get_impl!(self, u16::from); } #[inline] fn get_i16(&mut self) -> i16 { - buf_get_impl!(self, i16::from_be_bytes); + buf_get_impl!(self, i16::from_be); } #[inline] fn get_i16_le(&mut self) -> i16 { - buf_get_impl!(self, i16::from_le_bytes); + buf_get_impl!(self, i16::from_le); } #[inline] fn get_i16_ne(&mut self) -> i16 { - buf_get_impl!(self, i16::from_ne_bytes); + buf_get_impl!(self, i16::from); } #[inline] fn get_u32(&mut self) -> u32 { - buf_get_impl!(self, u32::from_be_bytes); + buf_get_impl!(self, u32::from_be); } #[inline] fn get_u32_le(&mut self) -> u32 { - buf_get_impl!(self, u32::from_le_bytes); + buf_get_impl!(self, u32::from_le); } #[inline] fn get_u32_ne(&mut self) -> u32 { - buf_get_impl!(self, u32::from_ne_bytes); + buf_get_impl!(self, u32::from); } #[inline] fn get_i32(&mut self) -> i32 { - buf_get_impl!(self, i32::from_be_bytes); + buf_get_impl!(self, i32::from_be); } #[inline] fn get_i32_le(&mut self) -> i32 { - buf_get_impl!(self, i32::from_le_bytes); + buf_get_impl!(self, i32::from_le); } #[inline] fn get_i32_ne(&mut self) -> i32 { - buf_get_impl!(self, i32::from_ne_bytes); + buf_get_impl!(self, i32::from); } #[inline] fn get_u64(&mut self) -> u64 { - buf_get_impl!(self, u64::from_be_bytes); + buf_get_impl!(self, u64::from_be); } #[inline] fn get_u64_le(&mut self) -> u64 { - buf_get_impl!(self, u64::from_le_bytes); + buf_get_impl!(self, u64::from_le); } #[inline] fn get_u64_ne(&mut self) -> u64 { - buf_get_impl!(self, u64::from_ne_bytes); + buf_get_impl!(self, u64::from); } #[inline] fn get_i64(&mut self) -> i64 { - buf_get_impl!(self, i64::from_be_bytes); + buf_get_impl!(self, i64::from_be); } #[inline] fn get_i64_le(&mut self) -> i64 { - buf_get_impl!(self, i64::from_le_bytes); + buf_get_impl!(self, i64::from_le); } #[inline] fn get_i64_ne(&mut self) -> i64 { - buf_get_impl!(self, i64::from_ne_bytes); + buf_get_impl!(self, i64::from); } #[inline] fn get_u128(&mut self) -> u128 { - buf_get_impl!(self, u128::from_be_bytes); + buf_get_impl!(self, u128::from_be); } #[inline] fn get_u128_le(&mut self) -> u128 { - buf_get_impl!(self, u128::from_le_bytes); + buf_get_impl!(self, u128::from_le); } #[inline] fn get_u128_ne(&mut self) -> u128 { - buf_get_impl!(self, u128::from_ne_bytes); + buf_get_impl!(self, u128::from); } #[inline] fn get_i128(&mut self) -> i128 { - buf_get_impl!(self, i128::from_be_bytes); + buf_get_impl!(self, i128::from_be); } #[inline] fn get_i128_le(&mut self) -> i128 { - buf_get_impl!(self, i128::from_le_bytes); + buf_get_impl!(self, i128::from_le); } #[inline] fn get_i128_ne(&mut self) -> i128 { - buf_get_impl!(self, i128::from_ne_bytes); + buf_get_impl!(self, i128::from); } } diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 779c2e924..745e1ffde 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1223,3 +1223,20 @@ fn test_get_u16_buffer_underflow() { let mut buf = Bytes::from(&b"\x21"[..]); buf.get_u16(); } + +#[test] +#[should_panic] +fn test_bytes_overread() { + let mut b = Bytes::from_static(&[0, 1, 2]); + let _ = b.get_u32(); +} + +// running this test would result in a panic without `.read_unaligned()` +// on x86 read_unaligned compiles down to a single `mov`, on platforms with no unaligned access, +// it uses rust's `copy_nonoverlapping` +#[test] +fn test_bytes_misaligned() { + let mut b = Bytes::from_static(&[0, 1, 2, 3, 4, 5, 6, 7, 8]); + b.advance(2); + let _ = b.get_u32(); +} From f73c6c8e8543ee15741c788d105e2b4235f1bc7b Mon Sep 17 00:00:00 2001 From: Luca Bruno Date: Thu, 28 Dec 2023 06:17:53 +0100 Subject: [PATCH 06/60] Simplify UninitSlice::as_uninit_slice_mut() logic (#644) This reworks `UninitSlice::as_uninit_slice_mut()` using equivalent simpler logic. --- src/buf/uninit_slice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buf/uninit_slice.rs b/src/buf/uninit_slice.rs index 0715ad233..82ebdbbb3 100644 --- a/src/buf/uninit_slice.rs +++ b/src/buf/uninit_slice.rs @@ -185,7 +185,7 @@ impl UninitSlice { /// ``` #[inline] pub unsafe fn as_uninit_slice_mut(&mut self) -> &mut [MaybeUninit] { - &mut *(self as *mut _ as *mut [MaybeUninit]) + &mut self.0 } /// Returns the number of bytes in the slice. From dbbdb63d7691066922ac4c7753e6dd95c07f8fbf Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Thu, 28 Dec 2023 00:20:13 -0500 Subject: [PATCH 07/60] Use `self.` instead of `Self::` (#642) I was a little confused about these calls using `Self::` instead of `self.` here. Is there a reason to use the former instead of the latter? --- src/buf/buf_impl.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/buf/buf_impl.rs b/src/buf/buf_impl.rs index b4ebf408a..9367eb2df 100644 --- a/src/buf/buf_impl.rs +++ b/src/buf/buf_impl.rs @@ -991,7 +991,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f32(&mut self) -> f32 { - f32::from_bits(Self::get_u32(self)) + f32::from_bits(self.get_u32()) } /// Gets an IEEE754 single-precision (4 bytes) floating point number from @@ -1012,7 +1012,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f32_le(&mut self) -> f32 { - f32::from_bits(Self::get_u32_le(self)) + f32::from_bits(self.get_u32_le()) } /// Gets an IEEE754 single-precision (4 bytes) floating point number from @@ -1036,7 +1036,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f32_ne(&mut self) -> f32 { - f32::from_bits(Self::get_u32_ne(self)) + f32::from_bits(self.get_u32_ne()) } /// Gets an IEEE754 double-precision (8 bytes) floating point number from @@ -1057,7 +1057,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f64(&mut self) -> f64 { - f64::from_bits(Self::get_u64(self)) + f64::from_bits(self.get_u64()) } /// Gets an IEEE754 double-precision (8 bytes) floating point number from @@ -1078,7 +1078,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f64_le(&mut self) -> f64 { - f64::from_bits(Self::get_u64_le(self)) + f64::from_bits(self.get_u64_le()) } /// Gets an IEEE754 double-precision (8 bytes) floating point number from @@ -1102,7 +1102,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f64_ne(&mut self) -> f64 { - f64::from_bits(Self::get_u64_ne(self)) + f64::from_bits(self.get_u64_ne()) } /// Consumes `len` bytes inside self and returns new instance of `Bytes` From 3bf6583b5cece02526b9b225e6ace0552a36ded3 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 3 Jan 2024 17:22:39 +0100 Subject: [PATCH 08/60] readme: add security policy (#649) --- SECURITY.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..b74a831cb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +Bytes is part of the Tokio project and uses the same security policy as [Tokio][tokio-security]. + +## Report a security issue + +The process for reporting an issue is the same as for [Tokio][tokio-security]. This includes private reporting via security@tokio.rs. + +[tokio-security]: https://github.com/tokio-rs/tokio/security/policy From fbc64bcc6713b51fa1253cf18fc80c904796ddb5 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Mon, 8 Jan 2024 01:10:48 +0900 Subject: [PATCH 09/60] Update loom to 0.7 (#651) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 06b19e671..cf72180fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ serde = { version = "1.0.60", optional = true, default-features = false, feature serde_test = "1.0" [target.'cfg(loom)'.dev-dependencies] -loom = "0.5" +loom = "0.7" [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] From 09214ba51bdace6f6cb91740cee9514fc08d55ce Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Mon, 8 Jan 2024 01:11:02 +0900 Subject: [PATCH 10/60] Update CI config (#650) --- .github/workflows/ci.yml | 54 +++++++++++++++++++++++----------------- Cargo.toml | 3 ++- ci/test-stable.sh | 6 ----- ci/tsan.sh | 0 4 files changed, 33 insertions(+), 30 deletions(-) mode change 100644 => 100755 ci/test-stable.sh mode change 100644 => 100755 ci/tsan.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4f7b1d93..c0658a142 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,8 @@ on: push: branches: - master + schedule: + - cron: '0 2 * * 0' env: RUSTFLAGS: -Dwarnings @@ -23,11 +25,11 @@ jobs: name: rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust - run: rustup update stable && rustup default stable + run: rustup update stable - name: Check formatting - run: cargo fmt --all -- --check + run: cargo fmt --all --check # TODO # # Apply clippy lints @@ -35,7 +37,7 @@ jobs: # name: clippy # runs-on: ubuntu-latest # steps: - # - uses: actions/checkout@v3 + # - uses: actions/checkout@v4 # - name: Apply clippy lints # run: cargo clippy --all-features @@ -48,11 +50,11 @@ jobs: name: minrust runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Install Rust - run: rustup update 1.39.0 && rustup default 1.39.0 + - uses: actions/checkout@v4 + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack - name: Check - run: . ci/test-stable.sh check + run: cargo hack check --feature-powerset --optional-deps --rust-version # Stable stable: @@ -65,23 +67,27 @@ jobs: - windows-latest runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust # --no-self-update is necessary because the windows environment cannot self-update rustup.exe. - run: rustup update stable --no-self-update && rustup default stable + run: rustup update stable --no-self-update + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack - name: Test - run: . ci/test-stable.sh test + run: ci/test-stable.sh test # Nightly nightly: name: nightly runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust run: rustup update $nightly && rustup default $nightly + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack - name: Test - run: . ci/test-stable.sh test + run: ci/test-stable.sh test # Run tests on some extra platforms cross: @@ -96,13 +102,14 @@ jobs: - wasm32-unknown-unknown runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust - run: rustup update stable && rustup default stable + run: rustup update stable + - name: Install cross + uses: taiki-e/install-action@cross + if: matrix.target != 'wasm32-unknown-unknown' - name: cross build --target ${{ matrix.target }} - run: | - cargo install cross - cross build --target ${{ matrix.target }} + run: cross build --target ${{ matrix.target }} if: matrix.target != 'wasm32-unknown-unknown' # WASM support - name: cargo build --target ${{ matrix.target }} @@ -116,18 +123,19 @@ jobs: name: tsan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust run: rustup update $nightly && rustup default $nightly - name: Install rust-src run: rustup component add rust-src - name: ASAN / TSAN - run: . ci/tsan.sh + run: ci/tsan.sh + miri: name: miri runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Miri run: ci/miri.sh @@ -136,7 +144,7 @@ jobs: name: loom runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust run: rustup update $nightly && rustup default $nightly - name: Loom tests @@ -155,7 +163,7 @@ jobs: - loom runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Rust run: rustup update $nightly && rustup default $nightly - name: Build documentation diff --git a/Cargo.toml b/Cargo.toml index cf72180fd..127d81dd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ name = "bytes" # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. version = "1.5.0" +edition = "2018" +rust-version = "1.39" license = "MIT" authors = [ "Carl Lerche ", @@ -15,7 +17,6 @@ repository = "https://github.com/tokio-rs/bytes" readme = "README.md" keywords = ["buffers", "zero-copy", "io"] categories = ["network-programming", "data-structures"] -edition = "2018" [features] default = ["std"] diff --git a/ci/test-stable.sh b/ci/test-stable.sh old mode 100644 new mode 100755 index 4421f3a97..a8eaa3c65 --- a/ci/test-stable.sh +++ b/ci/test-stable.sh @@ -4,10 +4,6 @@ set -ex cmd="${1:-test}" -# Install cargo-hack for feature flag test -host=$(rustc -Vv | grep host | sed 's/host: //') -curl -LsSf https://github.com/taiki-e/cargo-hack/releases/latest/download/cargo-hack-$host.tar.gz | tar xzf - -C ~/.cargo/bin - # Run with each feature # * --each-feature includes both default/no-default features # * --optional-deps is needed for serde feature @@ -15,8 +11,6 @@ cargo hack "${cmd}" --each-feature --optional-deps # Run with all features cargo "${cmd}" --all-features -cargo doc --no-deps --all-features - if [[ "${RUST_VERSION}" == "nightly"* ]]; then # Check benchmarks cargo check --benches diff --git a/ci/tsan.sh b/ci/tsan.sh old mode 100644 new mode 100755 From abb4a2e66cab68a6d1deb3d37377625443794cfd Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Fri, 19 Jan 2024 05:08:27 -0500 Subject: [PATCH 11/60] BytesMut: Assert alignment of Shared (#652) Back in #362, an assertion was added to ensure that the alignment of bytes::Shared is even so we can use the least significant bit as a flag. bytes_mut::Shared uses the same technique but has no such assertion so I've added one here. --- src/bytes_mut.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 57fd33e4b..dd4ff5031 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -80,6 +80,12 @@ struct Shared { ref_count: AtomicUsize, } +// Assert that the alignment of `Shared` is divisible by 2. +// This is a necessary invariant since we depend on allocating `Shared` a +// shared object to implicitly carry the `KIND_ARC` flag in its pointer. +// This flag is set when the LSB is 0. +const _: [(); 0 - mem::align_of::() % 2] = []; // Assert that the alignment of `Shared` is divisible by 2. + // Buffer storage strategy flags. const KIND_ARC: usize = 0b0; const KIND_VEC: usize = 0b1; From 0864aea9704ac12fa53ee96a7f968e51c9dabba1 Mon Sep 17 00:00:00 2001 From: Cyborus04 Date: Fri, 19 Jan 2024 17:59:30 -0500 Subject: [PATCH 12/60] add `Bytes::is_unique` (#643) --- src/bytes.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++ src/bytes_mut.rs | 1 + tests/test_bytes.rs | 33 ++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/src/bytes.rs b/src/bytes.rs index 9fed3d287..9eda9f463 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -112,6 +112,8 @@ pub(crate) struct Vtable { /// /// takes `Bytes` to value pub to_vec: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> Vec, + /// fn(data) + pub is_unique: unsafe fn(&AtomicPtr<()>) -> bool, /// fn(data, ptr, len) pub drop: unsafe fn(&mut AtomicPtr<()>, *const u8, usize), } @@ -208,6 +210,28 @@ impl Bytes { self.len == 0 } + /// Returns true if this is the only reference to the data. + /// + /// Always returns false if the data is backed by a static slice. + /// + /// The result of this method may be invalidated immediately if another + /// thread clones this value while this is being called. Ensure you have + /// unique access to this value (`&mut Bytes`) first if you need to be + /// certain the result is valid (i.e. for safety reasons) + /// # Examples + /// + /// ``` + /// use bytes::Bytes; + /// + /// let a = Bytes::from(vec![1, 2, 3]); + /// assert!(a.is_unique()); + /// let b = a.clone(); + /// assert!(!a.is_unique()); + /// ``` + pub fn is_unique(&self) -> bool { + unsafe { (self.vtable.is_unique)(&self.data) } + } + /// Creates `Bytes` instance from slice, by copying it. pub fn copy_from_slice(data: &[u8]) -> Self { data.to_vec().into() @@ -898,6 +922,7 @@ impl fmt::Debug for Vtable { const STATIC_VTABLE: Vtable = Vtable { clone: static_clone, to_vec: static_to_vec, + is_unique: static_is_unique, drop: static_drop, }; @@ -911,6 +936,10 @@ unsafe fn static_to_vec(_: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec) -> bool { + false +} + unsafe fn static_drop(_: &mut AtomicPtr<()>, _: *const u8, _: usize) { // nothing to drop for &'static [u8] } @@ -920,12 +949,14 @@ unsafe fn static_drop(_: &mut AtomicPtr<()>, _: *const u8, _: usize) { static PROMOTABLE_EVEN_VTABLE: Vtable = Vtable { clone: promotable_even_clone, to_vec: promotable_even_to_vec, + is_unique: promotable_is_unique, drop: promotable_even_drop, }; static PROMOTABLE_ODD_VTABLE: Vtable = Vtable { clone: promotable_odd_clone, to_vec: promotable_odd_to_vec, + is_unique: promotable_is_unique, drop: promotable_odd_drop, }; @@ -1020,6 +1051,18 @@ unsafe fn promotable_odd_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usi }); } +unsafe fn promotable_is_unique(data: &AtomicPtr<()>) -> bool { + let shared = data.load(Ordering::Acquire); + let kind = shared as usize & KIND_MASK; + + if kind == KIND_ARC { + let ref_cnt = (*shared.cast::()).ref_cnt.load(Ordering::Relaxed); + ref_cnt == 1 + } else { + true + } +} + unsafe fn free_boxed_slice(buf: *mut u8, offset: *const u8, len: usize) { let cap = (offset as usize - buf as usize) + len; dealloc(buf, Layout::from_size_align(cap, 1).unwrap()) @@ -1049,6 +1092,7 @@ const _: [(); 0 - mem::align_of::() % 2] = []; // Assert that the alignm static SHARED_VTABLE: Vtable = Vtable { clone: shared_clone, to_vec: shared_to_vec, + is_unique: shared_is_unique, drop: shared_drop, }; @@ -1094,6 +1138,12 @@ unsafe fn shared_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec shared_to_vec_impl(data.load(Ordering::Relaxed).cast(), ptr, len) } +pub(crate) unsafe fn shared_is_unique(data: &AtomicPtr<()>) -> bool { + let shared = data.load(Ordering::Acquire); + let ref_cnt = (*shared.cast::()).ref_cnt.load(Ordering::Relaxed); + ref_cnt == 1 +} + unsafe fn shared_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) { data.with_mut(|shared| { release_shared(shared.cast()); diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index dd4ff5031..88d596cf6 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1708,6 +1708,7 @@ unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) static SHARED_VTABLE: Vtable = Vtable { clone: shared_v_clone, to_vec: shared_v_to_vec, + is_unique: crate::bytes::shared_is_unique, drop: shared_v_drop, }; diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 5ec60a5b0..76adfdbf4 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1208,3 +1208,36 @@ fn test_bytes_capacity_len() { } } } + +#[test] +fn static_is_unique() { + let b = Bytes::from_static(LONG); + assert!(!b.is_unique()); +} + +#[test] +fn vec_is_unique() { + let v: Vec = LONG.to_vec(); + let b = Bytes::from(v); + assert!(b.is_unique()); +} + +#[test] +fn arc_is_unique() { + let v: Vec = LONG.to_vec(); + let b = Bytes::from(v); + let c = b.clone(); + assert!(!b.is_unique()); + drop(c); + assert!(b.is_unique()); +} + +#[test] +fn shared_is_unique() { + let v: Vec = LONG.to_vec(); + let b = Bytes::from(v); + let c = b.clone(); + assert!(!c.is_unique()); + drop(b); + assert!(c.is_unique()); +} From 0ba3b4c4cd74a0ad8566277e1a1533fa9e895756 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sun, 28 Jan 2024 05:37:11 -0500 Subject: [PATCH 13/60] Remove unnecessary namespace qualifier (#660) --- src/bytes.rs | 2 +- src/bytes_mut.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 9eda9f463..3da747d44 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -580,7 +580,7 @@ impl Buf for Bytes { } } - fn copy_to_bytes(&mut self, len: usize) -> crate::Bytes { + fn copy_to_bytes(&mut self, len: usize) -> Self { if len == self.remaining() { core::mem::replace(self, Bytes::new()) } else { diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 88d596cf6..1628a85a5 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1078,7 +1078,7 @@ impl Buf for BytesMut { } } - fn copy_to_bytes(&mut self, len: usize) -> crate::Bytes { + fn copy_to_bytes(&mut self, len: usize) -> Bytes { self.split_to(len).freeze() } } @@ -1110,7 +1110,7 @@ unsafe impl BufMut for BytesMut { // Specialize these methods so they can skip checking `remaining_mut` // and `advance_mut`. - fn put(&mut self, mut src: T) + fn put(&mut self, mut src: T) where Self: Sized, { From 9257a6ea0852c03f4672e5f8346d3d614543e270 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sun, 28 Jan 2024 05:50:56 -0500 Subject: [PATCH 14/60] Remove an unnecessary else branch (#662) --- src/bytes_mut.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 1628a85a5..d143f605c 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -726,11 +726,11 @@ impl BytesMut { } return; - } else { - new_cap = cmp::max(new_cap, original_capacity); } } + new_cap = cmp::max(new_cap, original_capacity); + // Create a new vector to store the data let mut v = ManuallyDrop::new(Vec::with_capacity(new_cap)); From e24587dd6197dbc58d6c2b6eb7186df99b04d881 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sun, 28 Jan 2024 07:00:30 -0500 Subject: [PATCH 15/60] Remove unreachable else branch (#661) --- src/bytes_mut.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index d143f605c..8783ae7f3 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1628,7 +1628,7 @@ impl From for Vec { let (off, _) = bytes.get_vec_pos(); rebuild_vec(bytes.ptr.as_ptr(), bytes.len, bytes.cap, off) } - } else if kind == KIND_ARC { + } else { let shared = bytes.data as *mut Shared; if unsafe { (*shared).is_unique() } { @@ -1640,8 +1640,6 @@ impl From for Vec { } else { return bytes.deref().to_vec(); } - } else { - return bytes.deref().to_vec(); }; let len = bytes.len; From d2e7abdb290e663f025a22a7d9e14e019b6abdb2 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Wed, 31 Jan 2024 09:41:23 -0500 Subject: [PATCH 16/60] refactor: make parameter mut in From (#667) Instead of re-declaring `vec`, we can just use a mut parameter. --- src/bytes.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 3da747d44..0b443c85b 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -828,8 +828,7 @@ impl From<&'static str> for Bytes { } impl From> for Bytes { - fn from(vec: Vec) -> Bytes { - let mut vec = vec; + fn from(mut vec: Vec) -> Bytes { let ptr = vec.as_mut_ptr(); let len = vec.len(); let cap = vec.capacity(); From 8bcac21cb44c112f20e8dd31475033ff448e35ce Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Tue, 6 Feb 2024 13:17:01 -0500 Subject: [PATCH 17/60] Restore commented tests (#665) These seem to have been commented by accident in #298, and are still passing. --- src/bytes_mut.rs | 79 +++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 8783ae7f3..5566f2d1a 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1409,56 +1409,59 @@ fn original_capacity_from_repr(repr: usize) -> usize { 1 << (repr + (MIN_ORIGINAL_CAPACITY_WIDTH - 1)) } -/* -#[test] -fn test_original_capacity_to_repr() { - assert_eq!(original_capacity_to_repr(0), 0); +#[cfg(test)] +mod tests { + use super::*; - let max_width = 32; + #[test] + fn test_original_capacity_to_repr() { + assert_eq!(original_capacity_to_repr(0), 0); - for width in 1..(max_width + 1) { - let cap = 1 << width - 1; + let max_width = 32; - let expected = if width < MIN_ORIGINAL_CAPACITY_WIDTH { - 0 - } else if width < MAX_ORIGINAL_CAPACITY_WIDTH { - width - MIN_ORIGINAL_CAPACITY_WIDTH - } else { - MAX_ORIGINAL_CAPACITY_WIDTH - MIN_ORIGINAL_CAPACITY_WIDTH - }; + for width in 1..(max_width + 1) { + let cap = 1 << width - 1; - assert_eq!(original_capacity_to_repr(cap), expected); + let expected = if width < MIN_ORIGINAL_CAPACITY_WIDTH { + 0 + } else if width < MAX_ORIGINAL_CAPACITY_WIDTH { + width - MIN_ORIGINAL_CAPACITY_WIDTH + } else { + MAX_ORIGINAL_CAPACITY_WIDTH - MIN_ORIGINAL_CAPACITY_WIDTH + }; - if width > 1 { - assert_eq!(original_capacity_to_repr(cap + 1), expected); - } + assert_eq!(original_capacity_to_repr(cap), expected); - // MIN_ORIGINAL_CAPACITY_WIDTH must be bigger than 7 to pass tests below - if width == MIN_ORIGINAL_CAPACITY_WIDTH + 1 { - assert_eq!(original_capacity_to_repr(cap - 24), expected - 1); - assert_eq!(original_capacity_to_repr(cap + 76), expected); - } else if width == MIN_ORIGINAL_CAPACITY_WIDTH + 2 { - assert_eq!(original_capacity_to_repr(cap - 1), expected - 1); - assert_eq!(original_capacity_to_repr(cap - 48), expected - 1); + if width > 1 { + assert_eq!(original_capacity_to_repr(cap + 1), expected); + } + + // MIN_ORIGINAL_CAPACITY_WIDTH must be bigger than 7 to pass tests below + if width == MIN_ORIGINAL_CAPACITY_WIDTH + 1 { + assert_eq!(original_capacity_to_repr(cap - 24), expected - 1); + assert_eq!(original_capacity_to_repr(cap + 76), expected); + } else if width == MIN_ORIGINAL_CAPACITY_WIDTH + 2 { + assert_eq!(original_capacity_to_repr(cap - 1), expected - 1); + assert_eq!(original_capacity_to_repr(cap - 48), expected - 1); + } } } -} -#[test] -fn test_original_capacity_from_repr() { - assert_eq!(0, original_capacity_from_repr(0)); + #[test] + fn test_original_capacity_from_repr() { + assert_eq!(0, original_capacity_from_repr(0)); - let min_cap = 1 << MIN_ORIGINAL_CAPACITY_WIDTH; + let min_cap = 1 << MIN_ORIGINAL_CAPACITY_WIDTH; - assert_eq!(min_cap, original_capacity_from_repr(1)); - assert_eq!(min_cap * 2, original_capacity_from_repr(2)); - assert_eq!(min_cap * 4, original_capacity_from_repr(3)); - assert_eq!(min_cap * 8, original_capacity_from_repr(4)); - assert_eq!(min_cap * 16, original_capacity_from_repr(5)); - assert_eq!(min_cap * 32, original_capacity_from_repr(6)); - assert_eq!(min_cap * 64, original_capacity_from_repr(7)); + assert_eq!(min_cap, original_capacity_from_repr(1)); + assert_eq!(min_cap * 2, original_capacity_from_repr(2)); + assert_eq!(min_cap * 4, original_capacity_from_repr(3)); + assert_eq!(min_cap * 8, original_capacity_from_repr(4)); + assert_eq!(min_cap * 16, original_capacity_from_repr(5)); + assert_eq!(min_cap * 32, original_capacity_from_repr(6)); + assert_eq!(min_cap * 64, original_capacity_from_repr(7)); + } } -*/ unsafe impl Send for BytesMut {} unsafe impl Sync for BytesMut {} From 47e83056f28e15e4ca68056a0136f3920b753783 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Tue, 6 Feb 2024 13:41:44 -0500 Subject: [PATCH 18/60] Use sub instead of offset (#668) We're always subtracting here, and we already have a usize, so `sub` seems like a more appropriate usage to me. --- src/bytes_mut.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 5566f2d1a..d1c141122 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -617,7 +617,7 @@ impl BytesMut { // // Just move the pointer back to the start after copying // data back. - let base_ptr = self.ptr.as_ptr().offset(-(off as isize)); + let base_ptr = self.ptr.as_ptr().sub(off); // Since `off >= self.len()`, the two regions don't overlap. ptr::copy_nonoverlapping(self.ptr.as_ptr(), base_ptr, self.len); self.ptr = vptr(base_ptr); @@ -1697,7 +1697,7 @@ fn offset_from(dst: *mut u8, original: *mut u8) -> usize { } unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) -> Vec { - let ptr = ptr.offset(-(off as isize)); + let ptr = ptr.sub(off); len += off; cap += off; From c6972d61328be113ec8e80c207815a4b84fe616c Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Tue, 6 Feb 2024 13:46:49 -0500 Subject: [PATCH 19/60] Calculate original capacity only if necessary (#666) We don't need the original capacity if the shared data is unique, so let's not calculate it until after that check. --- src/bytes_mut.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index d1c141122..619defc21 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -652,13 +652,7 @@ impl BytesMut { // Compute the new capacity let mut new_cap = len.checked_add(additional).expect("overflow"); - let original_capacity; - let original_capacity_repr; - unsafe { - original_capacity_repr = (*shared).original_capacity_repr; - original_capacity = original_capacity_from_repr(original_capacity_repr); - // First, try to reclaim the buffer. This is possible if the current // handle is the only outstanding handle pointing to the buffer. if (*shared).is_unique() { @@ -729,6 +723,9 @@ impl BytesMut { } } + let original_capacity_repr = unsafe { (*shared).original_capacity_repr }; + let original_capacity = original_capacity_from_repr(original_capacity_repr); + new_cap = cmp::max(new_cap, original_capacity); // Create a new vector to store the data From f586ffc52589f01be1b4a44d6544b3d0226773d6 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Tue, 6 Feb 2024 14:03:37 -0500 Subject: [PATCH 20/60] set_vec_pos does not need a second parameter (#672) The second argument to `set_vec_pos` always contains the value of `self.data`. Let's just use `self.data` and remove the second parameter altogether. --- src/bytes_mut.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 619defc21..bb72a2107 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -247,7 +247,7 @@ impl BytesMut { if self.kind() == KIND_VEC { // Just re-use `Bytes` internal Vec vtable unsafe { - let (off, _) = self.get_vec_pos(); + let off = self.get_vec_pos(); let vec = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off); mem::forget(self); let mut b: Bytes = vec.into(); @@ -596,7 +596,7 @@ impl BytesMut { // We need to make sure that this optimization does not kill the // amortized runtimes of BytesMut's operations. unsafe { - let (off, prev) = self.get_vec_pos(); + let off = self.get_vec_pos(); // Only reuse space if we can satisfy the requested additional space. // @@ -621,7 +621,7 @@ impl BytesMut { // Since `off >= self.len()`, the two regions don't overlap. ptr::copy_nonoverlapping(self.ptr.as_ptr(), base_ptr, self.len); self.ptr = vptr(base_ptr); - self.set_vec_pos(0, prev); + self.set_vec_pos(0); // Length stays constant, but since we moved backwards we // can gain capacity back. @@ -867,11 +867,10 @@ impl BytesMut { // complicated. First, we have to track how far ahead the // "start" of the byte buffer from the beginning of the vec. We // also have to ensure that we don't exceed the maximum shift. - let (mut pos, prev) = self.get_vec_pos(); - pos += start; + let pos = self.get_vec_pos() + start; if pos <= MAX_VEC_POS { - self.set_vec_pos(pos, prev); + self.set_vec_pos(pos); } else { // The repr must be upgraded to ARC. This will never happen // on 64 bit systems and will only happen on 32 bit systems @@ -979,19 +978,18 @@ impl BytesMut { } #[inline] - unsafe fn get_vec_pos(&mut self) -> (usize, usize) { + unsafe fn get_vec_pos(&mut self) -> usize { debug_assert_eq!(self.kind(), KIND_VEC); - let prev = self.data as usize; - (prev >> VEC_POS_OFFSET, prev) + self.data as usize >> VEC_POS_OFFSET } #[inline] - unsafe fn set_vec_pos(&mut self, pos: usize, prev: usize) { + unsafe fn set_vec_pos(&mut self, pos: usize) { debug_assert_eq!(self.kind(), KIND_VEC); debug_assert!(pos <= MAX_VEC_POS); - self.data = invalid_ptr((pos << VEC_POS_OFFSET) | (prev & NOT_VEC_POS_MASK)); + self.data = invalid_ptr((pos << VEC_POS_OFFSET) | (self.data as usize & NOT_VEC_POS_MASK)); } /// Returns the remaining spare capacity of the buffer as a slice of `MaybeUninit`. @@ -1040,7 +1038,7 @@ impl Drop for BytesMut { if kind == KIND_VEC { unsafe { - let (off, _) = self.get_vec_pos(); + let off = self.get_vec_pos(); // Vector storage, free the vector let _ = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off); @@ -1625,7 +1623,7 @@ impl From for Vec { let mut vec = if kind == KIND_VEC { unsafe { - let (off, _) = bytes.get_vec_pos(); + let off = bytes.get_vec_pos(); rebuild_vec(bytes.ptr.as_ptr(), bytes.len, bytes.cap, off) } } else { From 1bcd2129d195a0722d8b5b1a16c7d33698701f2e Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Tue, 6 Feb 2024 17:34:30 -0500 Subject: [PATCH 21/60] get_vec_pos: use &self instead of &mut self (#670) I can't see any reason that get_vec_pos needs a &mut self. --- src/bytes_mut.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index bb72a2107..901889671 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -243,7 +243,7 @@ impl BytesMut { /// th.join().unwrap(); /// ``` #[inline] - pub fn freeze(mut self) -> Bytes { + pub fn freeze(self) -> Bytes { if self.kind() == KIND_VEC { // Just re-use `Bytes` internal Vec vtable unsafe { @@ -978,7 +978,7 @@ impl BytesMut { } #[inline] - unsafe fn get_vec_pos(&mut self) -> usize { + unsafe fn get_vec_pos(&self) -> usize { debug_assert_eq!(self.kind(), KIND_VEC); self.data as usize >> VEC_POS_OFFSET @@ -1618,7 +1618,7 @@ impl PartialEq for BytesMut { } impl From for Vec { - fn from(mut bytes: BytesMut) -> Self { + fn from(bytes: BytesMut) -> Self { let kind = bytes.kind(); let mut vec = if kind == KIND_VEC { From 46289278f52a26c12298779f4aaebad1dcb26d35 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Fri, 23 Feb 2024 17:22:58 -0500 Subject: [PATCH 22/60] Refactor split_at/split_to (#663) * set len a little more concisely * inline set_end * remove kind assertions * remove a duplicate assertion * remove redundant assertion and min * rename set_start to advance_unchecked --- src/bytes_mut.rs | 50 +++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 901889671..220bdb005 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -317,8 +317,10 @@ impl BytesMut { ); unsafe { let mut other = self.shallow_clone(); - other.set_start(at); - self.set_end(at); + // SAFETY: We've checked that `at` <= `self.capacity()` above. + other.advance_unchecked(at); + self.cap = at; + self.len = cmp::min(self.len, at); other } } @@ -391,8 +393,11 @@ impl BytesMut { unsafe { let mut other = self.shallow_clone(); - other.set_end(at); - self.set_start(at); + // SAFETY: We've checked that `at` <= `self.len()` and we know that `self.len()` <= + // `self.capacity()`. + self.advance_unchecked(at); + other.cap = at; + other.len = at; other } } @@ -851,14 +856,19 @@ impl BytesMut { unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) } } - unsafe fn set_start(&mut self, start: usize) { + /// Advance the buffer without bounds checking. + /// + /// # SAFETY + /// + /// The caller must ensure that `count` <= `self.cap`. + unsafe fn advance_unchecked(&mut self, count: usize) { // Setting the start to 0 is a no-op, so return early if this is the // case. - if start == 0 { + if count == 0 { return; } - debug_assert!(start <= self.cap, "internal: set_start out of bounds"); + debug_assert!(count <= self.cap, "internal: set_start out of bounds"); let kind = self.kind(); @@ -867,7 +877,7 @@ impl BytesMut { // complicated. First, we have to track how far ahead the // "start" of the byte buffer from the beginning of the vec. We // also have to ensure that we don't exceed the maximum shift. - let pos = self.get_vec_pos() + start; + let pos = self.get_vec_pos() + count; if pos <= MAX_VEC_POS { self.set_vec_pos(pos); @@ -883,23 +893,9 @@ impl BytesMut { // Updating the start of the view is setting `ptr` to point to the // new start and updating the `len` field to reflect the new length // of the view. - self.ptr = vptr(self.ptr.as_ptr().add(start)); - - if self.len >= start { - self.len -= start; - } else { - self.len = 0; - } - - self.cap -= start; - } - - unsafe fn set_end(&mut self, end: usize) { - debug_assert_eq!(self.kind(), KIND_ARC); - assert!(end <= self.cap, "set_end out of bounds"); - - self.cap = end; - self.len = cmp::min(self.len, end); + self.ptr = vptr(self.ptr.as_ptr().add(count)); + self.len = self.len.checked_sub(count).unwrap_or(0); + self.cap -= count; } fn try_unsplit(&mut self, other: BytesMut) -> Result<(), BytesMut> { @@ -1069,7 +1065,9 @@ impl Buf for BytesMut { self.remaining(), ); unsafe { - self.set_start(cnt); + // SAFETY: We've checked that `cnt` <= `self.remaining()` and we know that + // `self.remaining()` <= `self.cap`. + self.advance_unchecked(cnt); } } From 99584cc10d66cb6880a20c5ac9b9a960f9c17823 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sat, 2 Mar 2024 10:40:17 -0500 Subject: [PATCH 23/60] Use Iterator from the prelude (#673) CI is [failing][failure] due to unused_imports because Iterator is already in the prelude. Removing it fixes things up. [failure]: https://github.com/tokio-rs/bytes/actions/runs/8034858583/job/21946873895 --- src/bytes_mut.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 220bdb005..734f4df92 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1,4 +1,4 @@ -use core::iter::{FromIterator, Iterator}; +use core::iter::FromIterator; use core::mem::{self, ManuallyDrop, MaybeUninit}; use core::ops::{Deref, DerefMut}; use core::ptr::{self, NonNull}; From c5fae00c76dbd1af7ea7b6cde7a9281d82ee7cd2 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sun, 3 Mar 2024 08:59:30 -0500 Subject: [PATCH 24/60] copy_to_bytes: Add panic section to docs (#676) Fixes #454. --- src/buf/buf_impl.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/buf/buf_impl.rs b/src/buf/buf_impl.rs index 9367eb2df..38ecf4bd9 100644 --- a/src/buf/buf_impl.rs +++ b/src/buf/buf_impl.rs @@ -1120,6 +1120,10 @@ pub trait Buf { /// let bytes = (&b"hello world"[..]).copy_to_bytes(5); /// assert_eq!(&bytes[..], &b"hello"[..]); /// ``` + /// + /// # Panics + /// + /// This function panics if `len > self.remaining()`. fn copy_to_bytes(&mut self, len: usize) -> crate::Bytes { use super::BufMut; From 7968f6f83d17175683e04ce56aa48e44ed7d0d98 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 4 Mar 2024 03:04:40 -0500 Subject: [PATCH 25/60] Remove redundant reserve call (#674) --- src/bytes_mut.rs | 2 -- tests/test_bytes.rs | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 734f4df92..1b4a4d9ae 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1283,9 +1283,7 @@ impl Extend for BytesMut { // TODO: optimize // 1. If self.kind() == KIND_VEC, use Vec::extend - // 2. Make `reserve` inline-able for b in iter { - self.reserve(1); self.put_u8(b); } } diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 76adfdbf4..e3820d76e 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -598,6 +598,28 @@ fn extend_mut_from_bytes() { assert_eq!(*bytes, LONG[..]); } +#[test] +fn extend_past_lower_limit_of_size_hint() { + // See https://github.com/tokio-rs/bytes/pull/674#pullrequestreview-1913035700 + struct Iter(I); + + impl> Iterator for Iter { + type Item = u8; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + (5, None) + } + } + + let mut bytes = BytesMut::with_capacity(5); + bytes.extend(Iter(std::iter::repeat(0).take(10))); + assert_eq!(bytes.len(), 10); +} + #[test] fn extend_mut_without_size_hint() { let mut bytes = BytesMut::with_capacity(0); From ca004117f86afccd36148dee7c8413cfaf9de6a4 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 4 Mar 2024 03:05:00 -0500 Subject: [PATCH 26/60] Remove commented tests for Bytes::unsplit (#677) Bytes doesn't have an unsplit method anymore. We can always retrieve these from git history if necessary. --- tests/test_bytes.rs | 91 --------------------------------------------- 1 file changed, 91 deletions(-) diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index e3820d76e..84c3d5a43 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -732,97 +732,6 @@ fn partial_eq_bytesmut() { assert!(bytesmut != bytes2); } -/* -#[test] -fn bytes_unsplit_basic() { - let buf = Bytes::from(&b"aaabbbcccddd"[..]); - - let splitted = buf.split_off(6); - assert_eq!(b"aaabbb", &buf[..]); - assert_eq!(b"cccddd", &splitted[..]); - - buf.unsplit(splitted); - assert_eq!(b"aaabbbcccddd", &buf[..]); -} - -#[test] -fn bytes_unsplit_empty_other() { - let buf = Bytes::from(&b"aaabbbcccddd"[..]); - - // empty other - let other = Bytes::new(); - - buf.unsplit(other); - assert_eq!(b"aaabbbcccddd", &buf[..]); -} - -#[test] -fn bytes_unsplit_empty_self() { - // empty self - let mut buf = Bytes::new(); - - let mut other = Bytes::with_capacity(64); - other.extend_from_slice(b"aaabbbcccddd"); - - buf.unsplit(other); - assert_eq!(b"aaabbbcccddd", &buf[..]); -} - -#[test] -fn bytes_unsplit_arc_different() { - let mut buf = Bytes::with_capacity(64); - buf.extend_from_slice(b"aaaabbbbeeee"); - - buf.split_off(8); //arc - - let mut buf2 = Bytes::with_capacity(64); - buf2.extend_from_slice(b"ccccddddeeee"); - - buf2.split_off(8); //arc - - buf.unsplit(buf2); - assert_eq!(b"aaaabbbbccccdddd", &buf[..]); -} - -#[test] -fn bytes_unsplit_arc_non_contiguous() { - let mut buf = Bytes::with_capacity(64); - buf.extend_from_slice(b"aaaabbbbeeeeccccdddd"); - - let mut buf2 = buf.split_off(8); //arc - - let buf3 = buf2.split_off(4); //arc - - buf.unsplit(buf3); - assert_eq!(b"aaaabbbbccccdddd", &buf[..]); -} - -#[test] -fn bytes_unsplit_two_split_offs() { - let mut buf = Bytes::with_capacity(64); - buf.extend_from_slice(b"aaaabbbbccccdddd"); - - let mut buf2 = buf.split_off(8); //arc - let buf3 = buf2.split_off(4); //arc - - buf2.unsplit(buf3); - buf.unsplit(buf2); - assert_eq!(b"aaaabbbbccccdddd", &buf[..]); -} - -#[test] -fn bytes_unsplit_overlapping_references() { - let mut buf = Bytes::with_capacity(64); - buf.extend_from_slice(b"abcdefghijklmnopqrstuvwxyz"); - let mut buf0010 = buf.slice(0..10); - let buf1020 = buf.slice(10..20); - let buf0515 = buf.slice(5..15); - buf0010.unsplit(buf1020); - assert_eq!(b"abcdefghijklmnopqrst", &buf0010[..]); - assert_eq!(b"fghijklmno", &buf0515[..]); -} -*/ - #[test] fn bytes_mut_unsplit_basic() { let mut buf = BytesMut::with_capacity(64); From 536db06f168bdef967afbeac0561bf774e9a1315 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Thu, 14 Mar 2024 09:40:03 -0400 Subject: [PATCH 27/60] Use ManuallyDrop instead of mem::forget (#675) --- src/bytes_mut.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 1b4a4d9ae..282aaa710 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -244,23 +244,22 @@ impl BytesMut { /// ``` #[inline] pub fn freeze(self) -> Bytes { - if self.kind() == KIND_VEC { + let bytes = ManuallyDrop::new(self); + if bytes.kind() == KIND_VEC { // Just re-use `Bytes` internal Vec vtable unsafe { - let off = self.get_vec_pos(); - let vec = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off); - mem::forget(self); + let off = bytes.get_vec_pos(); + let vec = rebuild_vec(bytes.ptr.as_ptr(), bytes.len, bytes.cap, off); let mut b: Bytes = vec.into(); b.advance(off); b } } else { - debug_assert_eq!(self.kind(), KIND_ARC); + debug_assert_eq!(bytes.kind(), KIND_ARC); - let ptr = self.ptr.as_ptr(); - let len = self.len; - let data = AtomicPtr::new(self.data.cast()); - mem::forget(self); + let ptr = bytes.ptr.as_ptr(); + let len = bytes.len; + let data = AtomicPtr::new(bytes.data.cast()); unsafe { Bytes::with_vtable(ptr, len, data, &SHARED_VTABLE) } } } @@ -829,11 +828,11 @@ impl BytesMut { // internal change could make a simple pattern (`BytesMut::from(vec)`) // suddenly a lot more expensive. #[inline] - pub(crate) fn from_vec(mut vec: Vec) -> BytesMut { + pub(crate) fn from_vec(vec: Vec) -> BytesMut { + let mut vec = ManuallyDrop::new(vec); let ptr = vptr(vec.as_mut_ptr()); let len = vec.len(); let cap = vec.capacity(); - mem::forget(vec); let original_capacity_repr = original_capacity_to_repr(cap); let data = (original_capacity_repr << ORIGINAL_CAPACITY_OFFSET) | KIND_VEC; @@ -1616,6 +1615,7 @@ impl PartialEq for BytesMut { impl From for Vec { fn from(bytes: BytesMut) -> Self { let kind = bytes.kind(); + let bytes = ManuallyDrop::new(bytes); let mut vec = if kind == KIND_VEC { unsafe { @@ -1632,7 +1632,7 @@ impl From for Vec { vec } else { - return bytes.deref().to_vec(); + return ManuallyDrop::into_inner(bytes).deref().to_vec(); } }; @@ -1643,8 +1643,6 @@ impl From for Vec { vec.set_len(len); } - mem::forget(bytes); - vec } } From ce8d8a0a029c0d296ade752ecc8c3e1ce9eee47f Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Fri, 22 Mar 2024 15:55:20 -0400 Subject: [PATCH 28/60] chore: prepare bytes v1.6.0 (#681) --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67b9f673a..23357174b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +# 1.6.0 (March 22, 2024) + +### Added + +- Add `Bytes::is_unique` (#643) + +### Documented + +- Fix changelog typo (#628) +- Fix some spelling mistakes (#633) +- Typo fix (#637) +- Fix broken links (#639) +- Add security policy (#649) + +### Internal changes + +- Move comment to correct constant (#629) +- Various cleanup (#635) +- Simplify `UninitSlice::as_uninit_slice_mut()` logic (#644) +- Use `self.` instead of `Self::` (#642) +- `BytesMut`: Assert alignment of `Shared` (#652) +- Remove unnecessary namespace qualifier (#660) +- Remove an unnecessary else branch (#662) +- Remove unreachable else branch (#661) +- make parameter mut in `From` (#667) +- Restore commented tests (#665) +- Use `sub` instead of `offset` (#668) +- Calculate original capacity only if necessary (#666) +- `set_vec_pos` does not need a second parameter (#672) +- `get_vec_pos`: use `&self` instead of `&mut self` (#670) +- Refactor `split_at`/`split_to` (#663) +- Use `Iterator` from the prelude (#673) +- `copy_to_bytes`: Add panic section to docs (#676) +- Remove redundant reserve call (#674) +- Use `ManuallyDrop` instead of `mem::forget` (#675) + + # 1.5.0 (September 7, 2023) ### Added diff --git a/Cargo.toml b/Cargo.toml index 127d81dd5..793582af1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "bytes" # When releasing to crates.io: # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.5.0" +version = "1.6.0" edition = "2018" rust-version = "1.39" license = "MIT" From 0d4cc7ffed2eadfb2028bade65b9ac0b6d231fc4 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Mon, 8 Apr 2024 11:05:04 -0400 Subject: [PATCH 29/60] Bytes: Use ManuallyDrop instead of mem::forget (#678) --- src/bytes.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 0b443c85b..4a0a94fa1 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -1,6 +1,7 @@ use core::iter::FromIterator; +use core::mem::{self, ManuallyDrop}; use core::ops::{Deref, RangeBounds}; -use core::{cmp, fmt, hash, mem, ptr, slice, usize}; +use core::{cmp, fmt, hash, ptr, slice, usize}; use alloc::{ alloc::{dealloc, Layout}, @@ -828,13 +829,15 @@ impl From<&'static str> for Bytes { } impl From> for Bytes { - fn from(mut vec: Vec) -> Bytes { + fn from(vec: Vec) -> Bytes { + let mut vec = ManuallyDrop::new(vec); let ptr = vec.as_mut_ptr(); let len = vec.len(); let cap = vec.capacity(); // Avoid an extra allocation if possible. if len == cap { + let vec = ManuallyDrop::into_inner(vec); return Bytes::from(vec.into_boxed_slice()); } @@ -843,7 +846,6 @@ impl From> for Bytes { cap, ref_cnt: AtomicUsize::new(1), }); - mem::forget(vec); let shared = Box::into_raw(shared); // The pointer should be aligned, so this assert should @@ -900,7 +902,7 @@ impl From for Bytes { impl From for Vec { fn from(bytes: Bytes) -> Vec { - let bytes = mem::ManuallyDrop::new(bytes); + let bytes = ManuallyDrop::new(bytes); unsafe { (bytes.vtable.to_vec)(&bytes.data, bytes.ptr, bytes.len) } } } @@ -1116,11 +1118,11 @@ unsafe fn shared_to_vec_impl(shared: *mut Shared, ptr: *const u8, len: usize) -> .compare_exchange(1, 0, Ordering::AcqRel, Ordering::Relaxed) .is_ok() { - let buf = (*shared).buf; - let cap = (*shared).cap; - - // Deallocate Shared - drop(Box::from_raw(shared as *mut mem::ManuallyDrop)); + // Deallocate the `Shared` instance without running its destructor. + let shared = *Box::from_raw(shared); + let shared = ManuallyDrop::new(shared); + let buf = shared.buf; + let cap = shared.cap; // Copy back buffer ptr::copy(ptr, buf, len); From e4af48633cec419e8274571d353fe166d5e23a3e Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Tue, 9 Apr 2024 08:35:54 -0400 Subject: [PATCH 30/60] Don't set `len` in `BytesMut::reserve` (#682) A fundamental invariant of `reserve` is that it can extend capacity while the stored data remains the same, even if it's moved to a new allocation. As a result, `len` can never change during a call to `reserve`. --- src/bytes_mut.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 282aaa710..c9f563430 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -639,8 +639,8 @@ impl BytesMut { // Update the info self.ptr = vptr(v.as_mut_ptr().add(off)); - self.len = v.len() - off; self.cap = v.capacity() - off; + debug_assert_eq!(self.len, v.len() - off); } return; @@ -746,8 +746,8 @@ impl BytesMut { let data = (original_capacity_repr << ORIGINAL_CAPACITY_OFFSET) | KIND_VEC; self.data = invalid_ptr(data); self.ptr = vptr(v.as_mut_ptr()); - self.len = v.len(); self.cap = v.capacity(); + debug_assert_eq!(self.len, v.len()); } /// Appends given bytes to this `BytesMut`. From 4eb62b912a199bef711e7e12243d972f4f0cdca8 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Wed, 10 Apr 2024 04:09:09 -0400 Subject: [PATCH 31/60] Bytes::split_to - check fast path first (#689) If `at == self.len()` then we already know `at <= self.len()`. If `at == 0`, it can't be greater than `self.len()`. --- src/bytes.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 4a0a94fa1..63c06cee7 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -434,13 +434,6 @@ impl Bytes { /// Panics if `at > len`. #[must_use = "consider Bytes::advance if you don't need the other half"] pub fn split_to(&mut self, at: usize) -> Self { - assert!( - at <= self.len(), - "split_to out of bounds: {:?} <= {:?}", - at, - self.len(), - ); - if at == self.len() { return mem::replace(self, Bytes::new()); } @@ -449,6 +442,13 @@ impl Bytes { return Bytes::new(); } + assert!( + at <= self.len(), + "split_to out of bounds: {:?} <= {:?}", + at, + self.len(), + ); + let mut ret = self.clone(); unsafe { self.inc_start(at) }; From b5fbfc3edb35a03ca560d29a0911e0495299575e Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 10 Apr 2024 22:45:31 +0800 Subject: [PATCH 32/60] perf: improve Bytes::copy_to_bytes (#688) Signed-off-by: tison --- src/bytes.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 63c06cee7..c3240ce09 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -582,13 +582,7 @@ impl Buf for Bytes { } fn copy_to_bytes(&mut self, len: usize) -> Self { - if len == self.remaining() { - core::mem::replace(self, Bytes::new()) - } else { - let ret = self.slice(..len); - self.advance(len); - ret - } + self.split_to(len) } } From 327615e5d4ba27e9647734d83ef9ad88d7dd8a38 Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Thu, 11 Apr 2024 11:45:18 +0200 Subject: [PATCH 33/60] test(benches): encloses bytes into `test::black_box` for clone benches (#691) Closes #690 Without it, it seems to me that compiler is able to inline the vtable, resulting in similar results for `clone_shared` and `clone_arg_vec`. --- benches/bytes.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/bytes.rs b/benches/bytes.rs index 61d1e832a..8782d0066 100644 --- a/benches/bytes.rs +++ b/benches/bytes.rs @@ -47,7 +47,7 @@ fn clone_static(b: &mut Bencher) { b.iter(|| { for _ in 0..1024 { - test::black_box(&bytes.clone()); + test::black_box(test::black_box(&bytes).clone()); } }) } @@ -58,7 +58,7 @@ fn clone_shared(b: &mut Bencher) { b.iter(|| { for _ in 0..1024 { - test::black_box(&bytes.clone()); + test::black_box(test::black_box(&bytes).clone()); } }) } @@ -70,7 +70,7 @@ fn clone_arc_vec(b: &mut Bencher) { b.iter(|| { for _ in 0..1024 { - test::black_box(&bytes.clone()); + test::black_box(test::black_box(&bytes).clone()); } }) } From 4e2c9c065a06bf9cb5d7dd46e3b29f62a1c20057 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Wed, 17 Apr 2024 05:27:00 -0400 Subject: [PATCH 34/60] Truncate tweaks (#694) --- src/bytes_mut.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index c9f563430..0248df856 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -422,8 +422,9 @@ impl BytesMut { /// assert_eq!(buf, b"hello"[..]); /// ``` pub fn truncate(&mut self, len: usize) { - if len <= self.len() { + if len < self.len() { unsafe { + // SAFETY: Shrinking the buffer cannot expose uninitialized bytes. self.set_len(len); } } From 9d3ec1cffb76141b4706bb289beced8b04ecac4a Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Wed, 24 Apr 2024 05:49:53 -0400 Subject: [PATCH 35/60] Resize refactor (#696) * use checked_sub * return when additional == 0 * move safe operation out of unsafe block * use spare_capacity_mut instead of chunk_mut We don't need to check capacity because it's already been reserved above. * Add safety comments * refactor to use guard clauses This would be better written with let-else, but we won't get that until `MSRV >= 1.65.x`. * use if-let instead of unwrap * reduce scope of unsafe blocks Co-authored-by: Alice Ryhl --------- Co-authored-by: Alice Ryhl --- src/bytes_mut.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 0248df856..0ea0272c5 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -468,18 +468,26 @@ impl BytesMut { /// assert_eq!(&buf[..], &[0x1, 0x1, 0x3, 0x3]); /// ``` pub fn resize(&mut self, new_len: usize, value: u8) { - let len = self.len(); - if new_len > len { - let additional = new_len - len; - self.reserve(additional); - unsafe { - let dst = self.chunk_mut().as_mut_ptr(); - ptr::write_bytes(dst, value, additional); - self.set_len(new_len); - } + let additional = if let Some(additional) = new_len.checked_sub(self.len()) { + additional } else { self.truncate(new_len); + return; + }; + + if additional == 0 { + return; } + + self.reserve(additional); + let dst = self.spare_capacity_mut().as_mut_ptr(); + // SAFETY: `spare_capacity_mut` returns a valid, properly aligned pointer and we've + // reserved enough space to write `additional` bytes. + unsafe { ptr::write_bytes(dst, value, additional) }; + + // SAFETY: There are at least `new_len` initialized bytes in the buffer so no + // uninitialized bytes are being exposed. + unsafe { self.set_len(new_len) }; } /// Sets the length of the buffer. From ce09d7d358ab1d1d31ed9d0b52a747c0a21ea401 Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Wed, 24 Apr 2024 08:23:39 -0400 Subject: [PATCH 36/60] Bytes::split_off - check fast path first (#693) Follow up to https://github.com/tokio-rs/bytes/pull/689 * If `at == self.len()`, we already know `at <= self.len()`. * If `at == 0`, we already know `at <= self.len()`. --- src/bytes.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index c3240ce09..908cee9ad 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -385,13 +385,6 @@ impl Bytes { /// Panics if `at > len`. #[must_use = "consider Bytes::truncate if you don't need the other half"] pub fn split_off(&mut self, at: usize) -> Self { - assert!( - at <= self.len(), - "split_off out of bounds: {:?} <= {:?}", - at, - self.len(), - ); - if at == self.len() { return Bytes::new(); } @@ -400,6 +393,13 @@ impl Bytes { return mem::replace(self, Bytes::new()); } + assert!( + at <= self.len(), + "split_off out of bounds: {:?} <= {:?}", + at, + self.len(), + ); + let mut ret = self.clone(); self.len = at; From baa5053572ed9e88ca1058ec2b5a3f08046c5a40 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 25 Apr 2024 09:08:16 +0200 Subject: [PATCH 37/60] Reuse capacity when possible in ::advance impl (#698) --- src/bytes_mut.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 0ea0272c5..35e19005a 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1066,6 +1066,14 @@ impl Buf for BytesMut { #[inline] fn advance(&mut self, cnt: usize) { + // Advancing by the length is the same as resetting the length to 0, + // except this way we get to reuse the full capacity. + if cnt == self.remaining() { + // SAFETY: Zero is not greater than the capacity. + unsafe { self.set_len(0) }; + return; + } + assert!( cnt <= self.remaining(), "cannot advance past `remaining`: {:?} <= {:?}", From a8806c245700e583134e67b7e0b87f1256b95bfa Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Thu, 25 Apr 2024 10:43:15 +0200 Subject: [PATCH 38/60] Improve BytesMut::split suggestion (#699) --- src/bytes_mut.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 35e19005a..75762996a 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -349,7 +349,7 @@ impl BytesMut { /// /// assert_eq!(other, b"hello world"[..]); /// ``` - #[must_use = "consider BytesMut::advance(len()) if you don't need the other half"] + #[must_use = "consider BytesMut::clear if you don't need the other half"] pub fn split(&mut self) -> BytesMut { let len = self.len(); self.split_to(len) From cb7f8449b5efc7022dc592b3a1d7dd33079f4c8f Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Fri, 26 Apr 2024 09:24:05 +0200 Subject: [PATCH 39/60] Tweak clear and truncate length modifications (#700) --- src/bytes_mut.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 75762996a..b01bb1adc 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -422,11 +422,9 @@ impl BytesMut { /// assert_eq!(buf, b"hello"[..]); /// ``` pub fn truncate(&mut self, len: usize) { - if len < self.len() { - unsafe { - // SAFETY: Shrinking the buffer cannot expose uninitialized bytes. - self.set_len(len); - } + if len <= self.len() { + // SAFETY: Shrinking the buffer cannot expose uninitialized bytes. + unsafe { self.set_len(len) }; } } @@ -442,7 +440,8 @@ impl BytesMut { /// assert!(buf.is_empty()); /// ``` pub fn clear(&mut self) { - self.truncate(0); + // SAFETY: Setting the length to zero cannot expose uninitialized bytes. + unsafe { self.set_len(0) }; } /// Resizes the buffer so that `len` is equal to `new_len`. @@ -1069,8 +1068,7 @@ impl Buf for BytesMut { // Advancing by the length is the same as resetting the length to 0, // except this way we get to reuse the full capacity. if cnt == self.remaining() { - // SAFETY: Zero is not greater than the capacity. - unsafe { self.set_len(0) }; + self.clear(); return; } From 0c17e99283185b94ab68cdf0fb62da53cbd765ee Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sun, 5 May 2024 14:19:18 +0200 Subject: [PATCH 40/60] ci: silence unexpected-cfgs warnings due to `#[cfg(loom)]` (#703) --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 1b3e6fc40..4dd118007 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(unknown_lints, unexpected_cfgs)] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] #![doc(test( no_crate_inject, From 86694b05649c0c1666044b2ba5c386c2328aac18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20Fugulin?= Date: Sun, 5 May 2024 11:58:00 -0400 Subject: [PATCH 41/60] Add zero-copy make_mut (#695) --- src/bytes.rs | 150 +++++++++++++++++++++++++++++++++- src/bytes_mut.rs | 32 +++++++- tests/test_bytes.rs | 111 +++++++++++++++++++++++++ tests/test_bytes_odd_alloc.rs | 50 ++++++++++++ 4 files changed, 341 insertions(+), 2 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 908cee9ad..b4359b08d 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -15,7 +15,7 @@ use crate::buf::IntoIter; #[allow(unused)] use crate::loom::sync::atomic::AtomicMut; use crate::loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; -use crate::Buf; +use crate::{Buf, BytesMut}; /// A cheaply cloneable and sliceable chunk of contiguous memory. /// @@ -113,6 +113,7 @@ pub(crate) struct Vtable { /// /// takes `Bytes` to value pub to_vec: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> Vec, + pub to_mut: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> BytesMut, /// fn(data) pub is_unique: unsafe fn(&AtomicPtr<()>) -> bool, /// fn(data, ptr, len) @@ -507,6 +508,49 @@ impl Bytes { self.truncate(0); } + /// Try to convert self into `BytesMut`. + /// + /// If `self` is unique for the entire original buffer, this will succeed + /// and return a `BytesMut` with the contents of `self` without copying. + /// If `self` is not unique for the entire original buffer, this will fail + /// and return self. + /// + /// # Examples + /// + /// ``` + /// use bytes::{Bytes, BytesMut}; + /// + /// let bytes = Bytes::from(b"hello".to_vec()); + /// assert_eq!(bytes.try_into_mut(), Ok(BytesMut::from(&b"hello"[..]))); + /// ``` + pub fn try_into_mut(self) -> Result { + if self.is_unique() { + Ok(self.make_mut()) + } else { + Err(self) + } + } + + /// Convert self into `BytesMut`. + /// + /// If `self` is unique for the entire original buffer, this will return a + /// `BytesMut` with the contents of `self` without copying. + /// If `self` is not unique for the entire original buffer, this will make + /// a copy of `self` subset of the original buffer in a new `BytesMut`. + /// + /// # Examples + /// + /// ``` + /// use bytes::{Bytes, BytesMut}; + /// + /// let bytes = Bytes::from(b"hello".to_vec()); + /// assert_eq!(bytes.make_mut(), BytesMut::from(&b"hello"[..])); + /// ``` + pub fn make_mut(self) -> BytesMut { + let bytes = ManuallyDrop::new(self); + unsafe { (bytes.vtable.to_mut)(&bytes.data, bytes.ptr, bytes.len) } + } + #[inline] pub(crate) unsafe fn with_vtable( ptr: *const u8, @@ -917,6 +961,7 @@ impl fmt::Debug for Vtable { const STATIC_VTABLE: Vtable = Vtable { clone: static_clone, to_vec: static_to_vec, + to_mut: static_to_mut, is_unique: static_is_unique, drop: static_drop, }; @@ -931,6 +976,11 @@ unsafe fn static_to_vec(_: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec, ptr: *const u8, len: usize) -> BytesMut { + let slice = slice::from_raw_parts(ptr, len); + BytesMut::from(slice) +} + fn static_is_unique(_: &AtomicPtr<()>) -> bool { false } @@ -944,6 +994,7 @@ unsafe fn static_drop(_: &mut AtomicPtr<()>, _: *const u8, _: usize) { static PROMOTABLE_EVEN_VTABLE: Vtable = Vtable { clone: promotable_even_clone, to_vec: promotable_even_to_vec, + to_mut: promotable_even_to_mut, is_unique: promotable_is_unique, drop: promotable_even_drop, }; @@ -951,6 +1002,7 @@ static PROMOTABLE_EVEN_VTABLE: Vtable = Vtable { static PROMOTABLE_ODD_VTABLE: Vtable = Vtable { clone: promotable_odd_clone, to_vec: promotable_odd_to_vec, + to_mut: promotable_odd_to_mut, is_unique: promotable_is_unique, drop: promotable_odd_drop, }; @@ -994,12 +1046,47 @@ unsafe fn promotable_to_vec( } } +unsafe fn promotable_to_mut( + data: &AtomicPtr<()>, + ptr: *const u8, + len: usize, + f: fn(*mut ()) -> *mut u8, +) -> BytesMut { + let shared = data.load(Ordering::Acquire); + let kind = shared as usize & KIND_MASK; + + if kind == KIND_ARC { + shared_to_mut_impl(shared.cast(), ptr, len) + } else { + // KIND_VEC is a view of an underlying buffer at a certain offset. + // The ptr + len always represents the end of that buffer. + // Before truncating it, it is first promoted to KIND_ARC. + // Thus, we can safely reconstruct a Vec from it without leaking memory. + debug_assert_eq!(kind, KIND_VEC); + + let buf = f(shared); + let off = offset_from(ptr, buf); + let cap = off + len; + let v = Vec::from_raw_parts(buf, cap, cap); + + let mut b = BytesMut::from_vec(v); + b.advance_unchecked(off); + b + } +} + unsafe fn promotable_even_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec { promotable_to_vec(data, ptr, len, |shared| { ptr_map(shared.cast(), |addr| addr & !KIND_MASK) }) } +unsafe fn promotable_even_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut { + promotable_to_mut(data, ptr, len, |shared| { + ptr_map(shared.cast(), |addr| addr & !KIND_MASK) + }) +} + unsafe fn promotable_even_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usize) { data.with_mut(|shared| { let shared = *shared; @@ -1031,6 +1118,10 @@ unsafe fn promotable_odd_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize promotable_to_vec(data, ptr, len, |shared| shared.cast()) } +unsafe fn promotable_odd_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut { + promotable_to_mut(data, ptr, len, |shared| shared.cast()) +} + unsafe fn promotable_odd_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usize) { data.with_mut(|shared| { let shared = *shared; @@ -1087,6 +1178,7 @@ const _: [(); 0 - mem::align_of::() % 2] = []; // Assert that the alignm static SHARED_VTABLE: Vtable = Vtable { clone: shared_clone, to_vec: shared_to_vec, + to_mut: shared_to_mut, is_unique: shared_is_unique, drop: shared_drop, }; @@ -1133,6 +1225,45 @@ unsafe fn shared_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec shared_to_vec_impl(data.load(Ordering::Relaxed).cast(), ptr, len) } +unsafe fn shared_to_mut_impl(shared: *mut Shared, ptr: *const u8, len: usize) -> BytesMut { + // The goal is to check if the current handle is the only handle + // that currently has access to the buffer. This is done by + // checking if the `ref_cnt` is currently 1. + // + // The `Acquire` ordering synchronizes with the `Release` as + // part of the `fetch_sub` in `release_shared`. The `fetch_sub` + // operation guarantees that any mutations done in other threads + // are ordered before the `ref_cnt` is decremented. As such, + // this `Acquire` will guarantee that those mutations are + // visible to the current thread. + // + // Otherwise, we take the other branch, copy the data and call `release_shared`. + if (*shared).ref_cnt.load(Ordering::Acquire) == 1 { + // Deallocate the `Shared` instance without running its destructor. + let shared = *Box::from_raw(shared); + let shared = ManuallyDrop::new(shared); + let buf = shared.buf; + let cap = shared.cap; + + // Rebuild Vec + let off = offset_from(ptr, buf); + let v = Vec::from_raw_parts(buf, len + off, cap); + + let mut b = BytesMut::from_vec(v); + b.advance_unchecked(off); + b + } else { + // Copy the data from Shared in a new Vec, then release it + let v = slice::from_raw_parts(ptr, len).to_vec(); + release_shared(shared); + BytesMut::from_vec(v) + } +} + +unsafe fn shared_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut { + shared_to_mut_impl(data.load(Ordering::Relaxed).cast(), ptr, len) +} + pub(crate) unsafe fn shared_is_unique(data: &AtomicPtr<()>) -> bool { let shared = data.load(Ordering::Acquire); let ref_cnt = (*shared.cast::()).ref_cnt.load(Ordering::Relaxed); @@ -1291,6 +1422,23 @@ where new_addr as *mut u8 } +/// Precondition: dst >= original +/// +/// The following line is equivalent to: +/// +/// ```rust,ignore +/// self.ptr.as_ptr().offset_from(ptr) as usize; +/// ``` +/// +/// But due to min rust is 1.39 and it is only stabilized +/// in 1.47, we cannot use it. +#[inline] +fn offset_from(dst: *const u8, original: *const u8) -> usize { + debug_assert!(dst >= original); + + dst as usize - original as usize +} + // compile-fails /// ```compile_fail diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index b01bb1adc..569f8be63 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -868,7 +868,7 @@ impl BytesMut { /// # SAFETY /// /// The caller must ensure that `count` <= `self.cap`. - unsafe fn advance_unchecked(&mut self, count: usize) { + pub(crate) unsafe fn advance_unchecked(&mut self, count: usize) { // Setting the start to 0 is a no-op, so return early if this is the // case. if count == 0 { @@ -1713,6 +1713,7 @@ unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) static SHARED_VTABLE: Vtable = Vtable { clone: shared_v_clone, to_vec: shared_v_to_vec, + to_mut: shared_v_to_mut, is_unique: crate::bytes::shared_is_unique, drop: shared_v_drop, }; @@ -1747,6 +1748,35 @@ unsafe fn shared_v_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> V } } +unsafe fn shared_v_to_mut(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> BytesMut { + let shared: *mut Shared = data.load(Ordering::Relaxed).cast(); + + if (*shared).is_unique() { + let shared = &mut *shared; + + // The capacity is always the original capacity of the buffer + // minus the offset from the start of the buffer + let v = &mut shared.vec; + let v_capacity = v.capacity(); + let v_ptr = v.as_mut_ptr(); + let offset = offset_from(ptr as *mut u8, v_ptr); + let cap = v_capacity - offset; + + let ptr = vptr(ptr as *mut u8); + + BytesMut { + ptr, + len, + cap, + data: shared, + } + } else { + let v = slice::from_raw_parts(ptr, len).to_vec(); + release_shared(shared); + BytesMut::from_vec(v) + } +} + unsafe fn shared_v_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) { data.with_mut(|shared| { release_shared(*shared as *mut Shared); diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 84c3d5a43..2f283af2f 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1172,3 +1172,114 @@ fn shared_is_unique() { drop(b); assert!(c.is_unique()); } + +#[test] +fn test_bytes_make_mut_static() { + let bs = b"1b23exfcz3r"; + + // Test STATIC_VTABLE.to_mut + let bytes_mut = Bytes::from_static(bs).make_mut(); + assert_eq!(bytes_mut, bs[..]); +} + +#[test] +fn test_bytes_make_mut_bytes_mut_vec() { + let bs = b"1b23exfcz3r"; + let bs_long = b"1b23exfcz3r1b23exfcz3r"; + + // Test case where kind == KIND_VEC + let mut bytes_mut: BytesMut = bs[..].into(); + bytes_mut = bytes_mut.freeze().make_mut(); + assert_eq!(bytes_mut, bs[..]); + bytes_mut.extend_from_slice(&bs[..]); + assert_eq!(bytes_mut, bs_long[..]); +} + +#[test] +fn test_bytes_make_mut_bytes_mut_shared() { + let bs = b"1b23exfcz3r"; + + // Set kind to KIND_ARC so that after freeze, Bytes will use bytes_mut.SHARED_VTABLE + let mut bytes_mut: BytesMut = bs[..].into(); + drop(bytes_mut.split_off(bs.len())); + + let b1 = bytes_mut.freeze(); + let b2 = b1.clone(); + + // shared.is_unique() = False + let mut b1m = b1.make_mut(); + assert_eq!(b1m, bs[..]); + b1m[0] = b'9'; + + // shared.is_unique() = True + let b2m = b2.make_mut(); + assert_eq!(b2m, bs[..]); +} + +#[test] +fn test_bytes_make_mut_bytes_mut_offset() { + let bs = b"1b23exfcz3r"; + + // Test bytes_mut.SHARED_VTABLE.to_mut impl where offset != 0 + let mut bytes_mut1: BytesMut = bs[..].into(); + let bytes_mut2 = bytes_mut1.split_off(9); + + let b1 = bytes_mut1.freeze(); + let b2 = bytes_mut2.freeze(); + + let b1m = b1.make_mut(); + let b2m = b2.make_mut(); + + assert_eq!(b2m, bs[9..]); + assert_eq!(b1m, bs[..9]); +} + +#[test] +fn test_bytes_make_mut_promotable_even_vec() { + let vec = vec![33u8; 1024]; + + // Test case where kind == KIND_VEC + let b1 = Bytes::from(vec.clone()); + let b1m = b1.make_mut(); + assert_eq!(b1m, vec); +} + +#[test] +fn test_bytes_make_mut_promotable_even_arc_1() { + let vec = vec![33u8; 1024]; + + // Test case where kind == KIND_ARC, ref_cnt == 1 + let b1 = Bytes::from(vec.clone()); + drop(b1.clone()); + let b1m = b1.make_mut(); + assert_eq!(b1m, vec); +} + +#[test] +fn test_bytes_make_mut_promotable_even_arc_2() { + let vec = vec![33u8; 1024]; + + // Test case where kind == KIND_ARC, ref_cnt == 2 + let b1 = Bytes::from(vec.clone()); + let b2 = b1.clone(); + let b1m = b1.make_mut(); + assert_eq!(b1m, vec); + + // Test case where vtable = SHARED_VTABLE, kind == KIND_ARC, ref_cnt == 1 + let b2m = b2.make_mut(); + assert_eq!(b2m, vec); +} + +#[test] +fn test_bytes_make_mut_promotable_even_arc_offset() { + let vec = vec![33u8; 1024]; + + // Test case where offset != 0 + let mut b1 = Bytes::from(vec.clone()); + let b2 = b1.split_off(20); + let b1m = b1.make_mut(); + let b2m = b2.make_mut(); + + assert_eq!(b2m, vec[20..]); + assert_eq!(b1m, vec[..20]); +} diff --git a/tests/test_bytes_odd_alloc.rs b/tests/test_bytes_odd_alloc.rs index 27ed87736..8008a0e47 100644 --- a/tests/test_bytes_odd_alloc.rs +++ b/tests/test_bytes_odd_alloc.rs @@ -95,3 +95,53 @@ fn test_bytes_into_vec() { assert_eq!(Vec::from(b2), vec[20..]); assert_eq!(Vec::from(b1), vec[..20]); } + +#[test] +fn test_bytes_make_mut_vec() { + let vec = vec![33u8; 1024]; + + // Test case where kind == KIND_VEC + let b1 = Bytes::from(vec.clone()); + let b1m = b1.make_mut(); + assert_eq!(b1m, vec); +} + +#[test] +fn test_bytes_make_mut_arc_1() { + let vec = vec![33u8; 1024]; + + // Test case where kind == KIND_ARC, ref_cnt == 1 + let b1 = Bytes::from(vec.clone()); + drop(b1.clone()); + let b1m = b1.make_mut(); + assert_eq!(b1m, vec); +} + +#[test] +fn test_bytes_make_mut_arc_2() { + let vec = vec![33u8; 1024]; + + // Test case where kind == KIND_ARC, ref_cnt == 2 + let b1 = Bytes::from(vec.clone()); + let b2 = b1.clone(); + let b1m = b1.make_mut(); + assert_eq!(b1m, vec); + + // Test case where vtable = SHARED_VTABLE, kind == KIND_ARC, ref_cnt == 1 + let b2m = b2.make_mut(); + assert_eq!(b2m, vec); +} + +#[test] +fn test_bytes_make_mut_arc_offset() { + let vec = vec![33u8; 1024]; + + // Test case where offset != 0 + let mut b1 = Bytes::from(vec.clone()); + let b2 = b1.split_off(20); + let b1m = b1.make_mut(); + let b2m = b2.make_mut(); + + assert_eq!(b2m, vec[20..]); + assert_eq!(b1m, vec[..20]); +} From 4950c503768fcebce6f9ab9dbaac2a7da30b35ba Mon Sep 17 00:00:00 2001 From: Brad Dunbar Date: Sat, 11 May 2024 13:41:50 -0400 Subject: [PATCH 42/60] Offset from (#705) --- src/bytes.rs | 25 ++++--------------------- src/bytes_mut.rs | 19 +------------------ src/lib.rs | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 39 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index b4359b08d..e23d9a81f 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -15,7 +15,7 @@ use crate::buf::IntoIter; #[allow(unused)] use crate::loom::sync::atomic::AtomicMut; use crate::loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; -use crate::{Buf, BytesMut}; +use crate::{offset_from, Buf, BytesMut}; /// A cheaply cloneable and sliceable chunk of contiguous memory. /// @@ -1037,7 +1037,7 @@ unsafe fn promotable_to_vec( let buf = f(shared); - let cap = (ptr as usize - buf as usize) + len; + let cap = offset_from(ptr, buf) + len; // Copy back buffer ptr::copy(ptr, buf, len); @@ -1150,7 +1150,7 @@ unsafe fn promotable_is_unique(data: &AtomicPtr<()>) -> bool { } unsafe fn free_boxed_slice(buf: *mut u8, offset: *const u8, len: usize) { - let cap = (offset as usize - buf as usize) + len; + let cap = offset_from(offset, buf) + len; dealloc(buf, Layout::from_size_align(cap, 1).unwrap()) } @@ -1312,7 +1312,7 @@ unsafe fn shallow_clone_vec( // vector. let shared = Box::new(Shared { buf, - cap: (offset as usize - buf as usize) + len, + cap: offset_from(offset, buf) + len, // Initialize refcount to 2. One for this reference, and one // for the new clone that will be returned from // `shallow_clone`. @@ -1422,23 +1422,6 @@ where new_addr as *mut u8 } -/// Precondition: dst >= original -/// -/// The following line is equivalent to: -/// -/// ```rust,ignore -/// self.ptr.as_ptr().offset_from(ptr) as usize; -/// ``` -/// -/// But due to min rust is 1.39 and it is only stabilized -/// in 1.47, we cannot use it. -#[inline] -fn offset_from(dst: *const u8, original: *const u8) -> usize { - debug_assert!(dst >= original); - - dst as usize - original as usize -} - // compile-fails /// ```compile_fail diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 569f8be63..537f01ad3 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -17,7 +17,7 @@ use crate::bytes::Vtable; #[allow(unused)] use crate::loom::sync::atomic::AtomicMut; use crate::loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; -use crate::{Buf, BufMut, Bytes}; +use crate::{offset_from, Buf, BufMut, Bytes}; /// A unique reference to a contiguous slice of memory. /// @@ -1683,23 +1683,6 @@ fn invalid_ptr(addr: usize) -> *mut T { ptr.cast::() } -/// Precondition: dst >= original -/// -/// The following line is equivalent to: -/// -/// ```rust,ignore -/// self.ptr.as_ptr().offset_from(ptr) as usize; -/// ``` -/// -/// But due to min rust is 1.39 and it is only stabilized -/// in 1.47, we cannot use it. -#[inline] -fn offset_from(dst: *mut u8, original: *mut u8) -> usize { - debug_assert!(dst >= original); - - dst as usize - original as usize -} - unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) -> Vec { let ptr = ptr.sub(off); len += off; diff --git a/src/lib.rs b/src/lib.rs index 4dd118007..7ddd2205b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,3 +148,18 @@ fn panic_does_not_fit(size: usize, nbytes: usize) -> ! { size, nbytes ); } + +/// Precondition: dst >= original +/// +/// The following line is equivalent to: +/// +/// ```rust,ignore +/// self.ptr.as_ptr().offset_from(ptr) as usize; +/// ``` +/// +/// But due to min rust is 1.39 and it is only stabilized +/// in 1.47, we cannot use it. +#[inline] +fn offset_from(dst: *const u8, original: *const u8) -> usize { + dst as usize - original as usize +} From caf520ac7f2c466d26bd88eca33ddc53c408e17e Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sun, 19 May 2024 21:28:03 +0200 Subject: [PATCH 43/60] Fix iter tests to use the actual bytes IntoIter instead of std (#707) --- tests/test_iter.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_iter.rs b/tests/test_iter.rs index a5bfddddf..bad901860 100644 --- a/tests/test_iter.rs +++ b/tests/test_iter.rs @@ -1,11 +1,11 @@ #![warn(rust_2018_idioms)] -use bytes::Bytes; +use bytes::{buf::IntoIter, Bytes}; #[test] fn iter_len() { let buf = Bytes::from_static(b"hello world"); - let iter = buf.iter(); + let iter = IntoIter::new(buf); assert_eq!(iter.size_hint(), (11, Some(11))); assert_eq!(iter.len(), 11); @@ -13,8 +13,8 @@ fn iter_len() { #[test] fn empty_iter_len() { - let buf = Bytes::from_static(b""); - let iter = buf.iter(); + let buf = Bytes::new(); + let iter = IntoIter::new(buf); assert_eq!(iter.size_hint(), (0, Some(0))); assert_eq!(iter.len(), 0); From fa1daac3ae1dcb07dffe3a41a041dffd6edf177b Mon Sep 17 00:00:00 2001 From: Anthony Ramine <123095+nox@users.noreply.github.com> Date: Tue, 28 May 2024 10:14:02 +0200 Subject: [PATCH 44/60] Change Bytes::make_mut to impl From for BytesMut (closes #709) (#710) >::make_mut returns a &mut T, such an API is doable for Bytes too and thus we should reserve Bytes::make_mut for that. Furthermore, it would be helpful to use From as a trait bound in some cases with other traits such as Hyper's body trait, where Hyper gives you Bytes values. Finally, making it impl From for BytesMut means the API is more easily discoverable as it appears on both Bytes and BytesMut. --- src/bytes.rs | 44 ++++++++++++++++++----------------- src/bytes/promotable.rs | 0 tests/test_bytes.rs | 40 +++++++++++++++---------------- tests/test_bytes_odd_alloc.rs | 22 +++++++++--------- 4 files changed, 54 insertions(+), 52 deletions(-) create mode 100644 src/bytes/promotable.rs diff --git a/src/bytes.rs b/src/bytes.rs index e23d9a81f..e0c33b3e6 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -525,32 +525,12 @@ impl Bytes { /// ``` pub fn try_into_mut(self) -> Result { if self.is_unique() { - Ok(self.make_mut()) + Ok(self.into()) } else { Err(self) } } - /// Convert self into `BytesMut`. - /// - /// If `self` is unique for the entire original buffer, this will return a - /// `BytesMut` with the contents of `self` without copying. - /// If `self` is not unique for the entire original buffer, this will make - /// a copy of `self` subset of the original buffer in a new `BytesMut`. - /// - /// # Examples - /// - /// ``` - /// use bytes::{Bytes, BytesMut}; - /// - /// let bytes = Bytes::from(b"hello".to_vec()); - /// assert_eq!(bytes.make_mut(), BytesMut::from(&b"hello"[..])); - /// ``` - pub fn make_mut(self) -> BytesMut { - let bytes = ManuallyDrop::new(self); - unsafe { (bytes.vtable.to_mut)(&bytes.data, bytes.ptr, bytes.len) } - } - #[inline] pub(crate) unsafe fn with_vtable( ptr: *const u8, @@ -932,6 +912,28 @@ impl From> for Bytes { } } +impl From for BytesMut { + /// Convert self into `BytesMut`. + /// + /// If `bytes` is unique for the entire original buffer, this will return a + /// `BytesMut` with the contents of `bytes` without copying. + /// If `bytes` is not unique for the entire original buffer, this will make + /// a copy of `bytes` subset of the original buffer in a new `BytesMut`. + /// + /// # Examples + /// + /// ``` + /// use bytes::{Bytes, BytesMut}; + /// + /// let bytes = Bytes::from(b"hello".to_vec()); + /// assert_eq!(BytesMut::from(bytes), BytesMut::from(&b"hello"[..])); + /// ``` + fn from(bytes: Bytes) -> Self { + let bytes = ManuallyDrop::new(bytes); + unsafe { (bytes.vtable.to_mut)(&bytes.data, bytes.ptr, bytes.len) } + } +} + impl From for Bytes { fn from(s: String) -> Bytes { Bytes::from(s.into_bytes()) diff --git a/src/bytes/promotable.rs b/src/bytes/promotable.rs new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 2f283af2f..3ac429832 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1174,29 +1174,29 @@ fn shared_is_unique() { } #[test] -fn test_bytes_make_mut_static() { +fn test_bytesmut_from_bytes_static() { let bs = b"1b23exfcz3r"; // Test STATIC_VTABLE.to_mut - let bytes_mut = Bytes::from_static(bs).make_mut(); + let bytes_mut = BytesMut::from(Bytes::from_static(bs)); assert_eq!(bytes_mut, bs[..]); } #[test] -fn test_bytes_make_mut_bytes_mut_vec() { +fn test_bytesmut_from_bytes_bytes_mut_vec() { let bs = b"1b23exfcz3r"; let bs_long = b"1b23exfcz3r1b23exfcz3r"; // Test case where kind == KIND_VEC let mut bytes_mut: BytesMut = bs[..].into(); - bytes_mut = bytes_mut.freeze().make_mut(); + bytes_mut = BytesMut::from(bytes_mut.freeze()); assert_eq!(bytes_mut, bs[..]); bytes_mut.extend_from_slice(&bs[..]); assert_eq!(bytes_mut, bs_long[..]); } #[test] -fn test_bytes_make_mut_bytes_mut_shared() { +fn test_bytesmut_from_bytes_bytes_mut_shared() { let bs = b"1b23exfcz3r"; // Set kind to KIND_ARC so that after freeze, Bytes will use bytes_mut.SHARED_VTABLE @@ -1207,17 +1207,17 @@ fn test_bytes_make_mut_bytes_mut_shared() { let b2 = b1.clone(); // shared.is_unique() = False - let mut b1m = b1.make_mut(); + let mut b1m = BytesMut::from(b1); assert_eq!(b1m, bs[..]); b1m[0] = b'9'; // shared.is_unique() = True - let b2m = b2.make_mut(); + let b2m = BytesMut::from(b2); assert_eq!(b2m, bs[..]); } #[test] -fn test_bytes_make_mut_bytes_mut_offset() { +fn test_bytesmut_from_bytes_bytes_mut_offset() { let bs = b"1b23exfcz3r"; // Test bytes_mut.SHARED_VTABLE.to_mut impl where offset != 0 @@ -1227,58 +1227,58 @@ fn test_bytes_make_mut_bytes_mut_offset() { let b1 = bytes_mut1.freeze(); let b2 = bytes_mut2.freeze(); - let b1m = b1.make_mut(); - let b2m = b2.make_mut(); + let b1m = BytesMut::from(b1); + let b2m = BytesMut::from(b2); assert_eq!(b2m, bs[9..]); assert_eq!(b1m, bs[..9]); } #[test] -fn test_bytes_make_mut_promotable_even_vec() { +fn test_bytesmut_from_bytes_promotable_even_vec() { let vec = vec![33u8; 1024]; // Test case where kind == KIND_VEC let b1 = Bytes::from(vec.clone()); - let b1m = b1.make_mut(); + let b1m = BytesMut::from(b1); assert_eq!(b1m, vec); } #[test] -fn test_bytes_make_mut_promotable_even_arc_1() { +fn test_bytesmut_from_bytes_promotable_even_arc_1() { let vec = vec![33u8; 1024]; // Test case where kind == KIND_ARC, ref_cnt == 1 let b1 = Bytes::from(vec.clone()); drop(b1.clone()); - let b1m = b1.make_mut(); + let b1m = BytesMut::from(b1); assert_eq!(b1m, vec); } #[test] -fn test_bytes_make_mut_promotable_even_arc_2() { +fn test_bytesmut_from_bytes_promotable_even_arc_2() { let vec = vec![33u8; 1024]; // Test case where kind == KIND_ARC, ref_cnt == 2 let b1 = Bytes::from(vec.clone()); let b2 = b1.clone(); - let b1m = b1.make_mut(); + let b1m = BytesMut::from(b1); assert_eq!(b1m, vec); // Test case where vtable = SHARED_VTABLE, kind == KIND_ARC, ref_cnt == 1 - let b2m = b2.make_mut(); + let b2m = BytesMut::from(b2); assert_eq!(b2m, vec); } #[test] -fn test_bytes_make_mut_promotable_even_arc_offset() { +fn test_bytesmut_from_bytes_promotable_even_arc_offset() { let vec = vec![33u8; 1024]; // Test case where offset != 0 let mut b1 = Bytes::from(vec.clone()); let b2 = b1.split_off(20); - let b1m = b1.make_mut(); - let b2m = b2.make_mut(); + let b1m = BytesMut::from(b1); + let b2m = BytesMut::from(b2); assert_eq!(b2m, vec[20..]); assert_eq!(b1m, vec[..20]); diff --git a/tests/test_bytes_odd_alloc.rs b/tests/test_bytes_odd_alloc.rs index 8008a0e47..4758dc2f9 100644 --- a/tests/test_bytes_odd_alloc.rs +++ b/tests/test_bytes_odd_alloc.rs @@ -6,7 +6,7 @@ use std::alloc::{GlobalAlloc, Layout, System}; use std::ptr; -use bytes::Bytes; +use bytes::{Bytes, BytesMut}; #[global_allocator] static ODD: Odd = Odd; @@ -97,50 +97,50 @@ fn test_bytes_into_vec() { } #[test] -fn test_bytes_make_mut_vec() { +fn test_bytesmut_from_bytes_vec() { let vec = vec![33u8; 1024]; // Test case where kind == KIND_VEC let b1 = Bytes::from(vec.clone()); - let b1m = b1.make_mut(); + let b1m = BytesMut::from(b1); assert_eq!(b1m, vec); } #[test] -fn test_bytes_make_mut_arc_1() { +fn test_bytesmut_from_bytes_arc_1() { let vec = vec![33u8; 1024]; // Test case where kind == KIND_ARC, ref_cnt == 1 let b1 = Bytes::from(vec.clone()); drop(b1.clone()); - let b1m = b1.make_mut(); + let b1m = BytesMut::from(b1); assert_eq!(b1m, vec); } #[test] -fn test_bytes_make_mut_arc_2() { +fn test_bytesmut_from_bytes_arc_2() { let vec = vec![33u8; 1024]; // Test case where kind == KIND_ARC, ref_cnt == 2 let b1 = Bytes::from(vec.clone()); let b2 = b1.clone(); - let b1m = b1.make_mut(); + let b1m = BytesMut::from(b1); assert_eq!(b1m, vec); // Test case where vtable = SHARED_VTABLE, kind == KIND_ARC, ref_cnt == 1 - let b2m = b2.make_mut(); + let b2m = BytesMut::from(b2); assert_eq!(b2m, vec); } #[test] -fn test_bytes_make_mut_arc_offset() { +fn test_bytesmut_from_bytes_arc_offset() { let vec = vec![33u8; 1024]; // Test case where offset != 0 let mut b1 = Bytes::from(vec.clone()); let b2 = b1.split_off(20); - let b1m = b1.make_mut(); - let b2m = b2.make_mut(); + let b1m = BytesMut::from(b1); + let b2m = BytesMut::from(b2); assert_eq!(b2m, vec[20..]); assert_eq!(b1m, vec[..20]); From 7a5154ba8b54970b7bb07c4902bc8a7981f4e57c Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Fri, 21 Jun 2024 14:07:25 +0200 Subject: [PATCH 45/60] Clarify how `BytesMut::zeroed` works and advantages to manual impl (#714) --- src/bytes_mut.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 537f01ad3..a01b29634 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -264,7 +264,14 @@ impl BytesMut { } } - /// Creates a new `BytesMut`, which is initialized with zero. + /// Creates a new `BytesMut` containing `len` zeros. + /// + /// The resulting object has a length of `len` and a capacity greater + /// than or equal to `len`. The entire length of the object will be filled + /// with zeros. + /// + /// On some platforms or allocators this function may be faster than + /// a manual implementation. /// /// # Examples /// @@ -273,6 +280,7 @@ impl BytesMut { /// /// let zeros = BytesMut::zeroed(42); /// + /// assert!(zeros.capacity() >= 42); /// assert_eq!(zeros.len(), 42); /// zeros.into_iter().for_each(|x| assert_eq!(x, 0)); /// ``` From 8cc940779fd6a489a2d7ca53fbbc44f84210083e Mon Sep 17 00:00:00 2001 From: Sebastian Hahn Date: Fri, 28 Jun 2024 14:26:32 +0200 Subject: [PATCH 46/60] Allow reclaiming the current allocation (#686) --- src/bytes_mut.rs | 89 +++++++++++++++++++++++++++++++++++++++++---- tests/test_bytes.rs | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 7 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index a01b29634..f2a3dab5c 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -597,12 +597,13 @@ impl BytesMut { return; } - self.reserve_inner(additional); + // will always succeed + let _ = self.reserve_inner(additional, true); } - // In separate function to allow the short-circuits in `reserve` to - // be inline-able. Significant helps performance. - fn reserve_inner(&mut self, additional: usize) { + // In separate function to allow the short-circuits in `reserve` and `try_reclaim` to + // be inline-able. Significantly helps performance. Returns false if it did not succeed. + fn reserve_inner(&mut self, additional: usize, allocate: bool) -> bool { let len = self.len(); let kind = self.kind(); @@ -647,6 +648,9 @@ impl BytesMut { // can gain capacity back. self.cap += off; } else { + if !allocate { + return false; + } // Not enough space, or reusing might be too much overhead: // allocate more space! let mut v = @@ -659,7 +663,7 @@ impl BytesMut { debug_assert_eq!(self.len, v.len() - off); } - return; + return true; } } @@ -670,7 +674,11 @@ impl BytesMut { // allocating a new vector with the requested capacity. // // Compute the new capacity - let mut new_cap = len.checked_add(additional).expect("overflow"); + let mut new_cap = match len.checked_add(additional) { + Some(new_cap) => new_cap, + None if !allocate => return false, + None => panic!("overflow"), + }; unsafe { // First, try to reclaim the buffer. This is possible if the current @@ -701,6 +709,9 @@ impl BytesMut { self.ptr = vptr(ptr); self.cap = v.capacity(); } else { + if !allocate { + return false; + } // calculate offset let off = (self.ptr.as_ptr() as usize) - (v.as_ptr() as usize); @@ -739,9 +750,12 @@ impl BytesMut { self.cap = v.capacity() - off; } - return; + return true; } } + if !allocate { + return false; + } let original_capacity_repr = unsafe { (*shared).original_capacity_repr }; let original_capacity = original_capacity_from_repr(original_capacity_repr); @@ -764,6 +778,67 @@ impl BytesMut { self.ptr = vptr(v.as_mut_ptr()); self.cap = v.capacity(); debug_assert_eq!(self.len, v.len()); + return true; + } + + /// Attempts to cheaply reclaim already allocated capacity for at least `additional` more + /// bytes to be inserted into the given `BytesMut` and returns `true` if it succeeded. + /// + /// `try_reclaim` behaves exactly like `reserve`, except that it never allocates new storage + /// and returns a `bool` indicating whether it was successful in doing so: + /// + /// `try_reclaim` returns false under these conditions: + /// - The spare capacity left is less than `additional` bytes AND + /// - The existing allocation cannot be reclaimed cheaply or it was less than + /// `additional` bytes in size + /// + /// Reclaiming the allocation cheaply is possible if the `BytesMut` has no outstanding + /// references through other `BytesMut`s or `Bytes` which point to the same underlying + /// storage. + /// + /// # Examples + /// + /// ``` + /// use bytes::BytesMut; + /// + /// let mut buf = BytesMut::with_capacity(64); + /// assert_eq!(true, buf.try_reclaim(64)); + /// assert_eq!(64, buf.capacity()); + /// + /// buf.extend_from_slice(b"abcd"); + /// let mut split = buf.split(); + /// assert_eq!(60, buf.capacity()); + /// assert_eq!(4, split.capacity()); + /// assert_eq!(false, split.try_reclaim(64)); + /// assert_eq!(false, buf.try_reclaim(64)); + /// // The split buffer is filled with "abcd" + /// assert_eq!(false, split.try_reclaim(4)); + /// // buf is empty and has capacity for 60 bytes + /// assert_eq!(true, buf.try_reclaim(60)); + /// + /// drop(buf); + /// assert_eq!(false, split.try_reclaim(64)); + /// + /// split.clear(); + /// assert_eq!(4, split.capacity()); + /// assert_eq!(true, split.try_reclaim(64)); + /// assert_eq!(64, split.capacity()); + /// ``` + // I tried splitting out try_reclaim_inner after the short circuits, but it was inlined + // regardless with Rust 1.78.0 so probably not worth it + #[inline] + #[must_use = "consider BytesMut::reserve if you need an infallible reservation"] + pub fn try_reclaim(&mut self, additional: usize) -> bool { + let len = self.len(); + let rem = self.capacity() - len; + + if additional <= rem { + // The handle can already store at least `additional` more bytes, so + // there is no further work needed to be done. + return true; + } + + self.reserve_inner(additional, false) } /// Appends given bytes to this `BytesMut`. diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 3ac429832..017ce66ea 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1283,3 +1283,73 @@ fn test_bytesmut_from_bytes_promotable_even_arc_offset() { assert_eq!(b2m, vec[20..]); assert_eq!(b1m, vec[..20]); } + +#[test] +fn try_reclaim_empty() { + let mut buf = BytesMut::new(); + assert_eq!(false, buf.try_reclaim(6)); + buf.reserve(6); + assert_eq!(true, buf.try_reclaim(6)); + let cap = buf.capacity(); + assert!(cap >= 6); + assert_eq!(false, buf.try_reclaim(cap + 1)); + + let mut buf = BytesMut::new(); + buf.reserve(6); + let cap = buf.capacity(); + assert!(cap >= 6); + let mut split = buf.split(); + drop(buf); + assert_eq!(0, split.capacity()); + assert_eq!(true, split.try_reclaim(6)); + assert_eq!(false, split.try_reclaim(cap + 1)); +} + +#[test] +fn try_reclaim_vec() { + let mut buf = BytesMut::with_capacity(6); + buf.put_slice(b"abc"); + // Reclaiming a ludicrous amount of space should calmly return false + assert_eq!(false, buf.try_reclaim(usize::MAX)); + + assert_eq!(false, buf.try_reclaim(6)); + buf.advance(2); + assert_eq!(4, buf.capacity()); + // We can reclaim 5 bytes, because the byte in the buffer can be moved to the front. 6 bytes + // cannot be reclaimed because there is already one byte stored + assert_eq!(false, buf.try_reclaim(6)); + assert_eq!(true, buf.try_reclaim(5)); + buf.advance(1); + assert_eq!(true, buf.try_reclaim(6)); + assert_eq!(6, buf.capacity()); +} + +#[test] +fn try_reclaim_arc() { + let mut buf = BytesMut::with_capacity(6); + buf.put_slice(b"abc"); + let x = buf.split().freeze(); + buf.put_slice(b"def"); + // Reclaiming a ludicrous amount of space should calmly return false + assert_eq!(false, buf.try_reclaim(usize::MAX)); + + let y = buf.split().freeze(); + let z = y.clone(); + assert_eq!(false, buf.try_reclaim(6)); + drop(x); + drop(z); + assert_eq!(false, buf.try_reclaim(6)); + drop(y); + assert_eq!(true, buf.try_reclaim(6)); + assert_eq!(6, buf.capacity()); + assert_eq!(0, buf.len()); + buf.put_slice(b"abc"); + buf.put_slice(b"def"); + assert_eq!(6, buf.capacity()); + assert_eq!(6, buf.len()); + assert_eq!(false, buf.try_reclaim(6)); + buf.advance(4); + assert_eq!(true, buf.try_reclaim(4)); + buf.advance(2); + assert_eq!(true, buf.try_reclaim(6)); +} From 3443ca5a0be21cdb2424bd20d49746bd622ed195 Mon Sep 17 00:00:00 2001 From: vvvviiv Date: Tue, 9 Jul 2024 20:13:09 +0800 Subject: [PATCH 47/60] docs: clarify the behavior of `Buf::chunk` (#717) --- src/buf/buf_impl.rs | 8 +++++--- src/buf/buf_mut.rs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/buf/buf_impl.rs b/src/buf/buf_impl.rs index 38ecf4bd9..c44d4fb38 100644 --- a/src/buf/buf_impl.rs +++ b/src/buf/buf_impl.rs @@ -141,9 +141,11 @@ pub trait Buf { /// /// # Implementer notes /// - /// This function should never panic. Once the end of the buffer is reached, - /// i.e., `Buf::remaining` returns 0, calls to `chunk()` should return an - /// empty slice. + /// This function should never panic. `chunk()` should return an empty + /// slice **if and only if** `remaining()` returns 0. In other words, + /// `chunk()` returning an empty slice implies that `remaining()` will + /// return 0 and `remaining()` returning 0 implies that `chunk()` will + /// return an empty slice. // The `chunk` method was previously called `bytes`. This alias makes the rename // more easily discoverable. #[cfg_attr(docsrs, doc(alias = "bytes"))] diff --git a/src/buf/buf_mut.rs b/src/buf/buf_mut.rs index 304e11b13..e13278d2c 100644 --- a/src/buf/buf_mut.rs +++ b/src/buf/buf_mut.rs @@ -165,7 +165,7 @@ pub unsafe trait BufMut { /// /// # Implementer notes /// - /// This function should never panic. `chunk_mut` should return an empty + /// This function should never panic. `chunk_mut()` should return an empty /// slice **if and only if** `remaining_mut()` returns 0. In other words, /// `chunk_mut()` returning an empty slice implies that `remaining_mut()` will /// return 0 and `remaining_mut()` returning 0 implies that `chunk_mut()` will From 9965a04b5684079bb614addd750340ffc165a9f5 Mon Sep 17 00:00:00 2001 From: EXPLOSION Date: Wed, 10 Jul 2024 19:08:47 +0900 Subject: [PATCH 48/60] Remove unnecessary file (#719) --- src/bytes/promotable.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/bytes/promotable.rs diff --git a/src/bytes/promotable.rs b/src/bytes/promotable.rs deleted file mode 100644 index e69de29bb..000000000 From 6b4b0eda2980f09df18380c80f8ae6109ae70d83 Mon Sep 17 00:00:00 2001 From: Emily Crandall Fleischman Date: Fri, 12 Jul 2024 19:09:46 -0400 Subject: [PATCH 49/60] Fix `Bytes::is_unique` when created from shared `BytesMut` (#718) The `is_unique` entry in the vtable for `Bytes` created from a shared `BytesMut` just called the `shared_is_unique` function from the `bytes` module. However, that function dereferences the `data` argument` as `bytes::Shared`, but the actual underlying type is `bytes_mut::Shared`. --- src/bytes_mut.rs | 8 +++++++- tests/test_bytes.rs | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 282aaa710..2342077d2 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1698,7 +1698,7 @@ unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) static SHARED_VTABLE: Vtable = Vtable { clone: shared_v_clone, to_vec: shared_v_to_vec, - is_unique: crate::bytes::shared_is_unique, + is_unique: shared_v_is_unique, drop: shared_v_drop, }; @@ -1732,6 +1732,12 @@ unsafe fn shared_v_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> V } } +unsafe fn shared_v_is_unique(data: &AtomicPtr<()>) -> bool { + let shared = data.load(Ordering::Acquire); + let ref_count = (*shared.cast::()).ref_count.load(Ordering::Relaxed); + ref_count == 1 +} + unsafe fn shared_v_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) { data.with_mut(|shared| { release_shared(*shared as *mut Shared); diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 84c3d5a43..b2135905d 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1172,3 +1172,12 @@ fn shared_is_unique() { drop(b); assert!(c.is_unique()); } + +#[test] +fn mut_shared_is_unique() { + let mut b = BytesMut::from(LONG); + let c = b.split().freeze(); + assert!(!c.is_unique()); + drop(b); + assert!(c.is_unique()); +} From fd13c7dcdb840653bf81294d141da77d3f1f9e1f Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Sat, 13 Jul 2024 07:45:33 +0000 Subject: [PATCH 50/60] chore: prepare bytes v1.6.1 (#720) --- CHANGELOG.md | 5 +++++ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23357174b..ac5d8d5ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.6.1 (July 13, 2024) + +This release fixes a bug where `Bytes::is_unique` returns incorrect values when +the `Bytes` originates from a shared `BytesMut`. (#718) + # 1.6.0 (March 22, 2024) ### Added diff --git a/Cargo.toml b/Cargo.toml index 793582af1..ef44fbb9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "bytes" # When releasing to crates.io: # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.6.0" +version = "1.6.1" edition = "2018" rust-version = "1.39" license = "MIT" From 03fdde9dcfe69caf681ecaa1d97f8105a9c9a6c1 Mon Sep 17 00:00:00 2001 From: Motoyuki Kimura Date: Wed, 31 Jul 2024 20:33:47 +0900 Subject: [PATCH 51/60] chore: prepare v1.7.0 (#724) --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ Cargo.toml | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5d8d5ce..7a4c5ba08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +# 1.7.0 (July 31, 2024) + +### Added + +- Add conversion from `Bytes` to `BytesMut` (#695, #710) +- Add reclaim method without additional allocation (#686) + +### Documented + +- Clarify how `BytesMut::zeroed` works (#714) +- Clarify the behavior of `Buf::chunk` (#717) + +### Changed + +- Change length condition of `BytesMut::truncate` +- Reuse capacity when possible in `::advance` impl (#698) +- Improve `must_use` suggestion of `BytesMut::split` (#699) + +### Internal changes + +- Use `ManuallyDrop` instead of `mem::forget` (#678) +- Don't set `len` in `BytesMut::reserve` (#682) +- Optimize `Bytes::copy_to_bytes` (#688) +- Refactor `BytesMut::truncate` (#694) +- Refactor `BytesMut::resize` (#696) +- Reorder assertion in `Bytes::split_to`, `Bytes::split_off` (#689, #693) +- Use `offset_from` in more places (#705) +- Correct the wrong usage of `IntoIter` (#707) + # 1.6.1 (July 13, 2024) This release fixes a bug where `Bytes::is_unique` returns incorrect values when diff --git a/Cargo.toml b/Cargo.toml index ef44fbb9d..ac8bdf7d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "bytes" # When releasing to crates.io: # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.6.1" +version = "1.7.0" edition = "2018" rust-version = "1.39" license = "MIT" From f488be48d07d899dc428c5cd7f5c11a95bf7716c Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 1 Aug 2024 21:38:17 +0100 Subject: [PATCH 52/60] Revert "Reuse capacity when possible in ::advance impl" (#726) This reverts commit baa5053572ed9e88ca1058ec2b5a3f08046c5a40. --- src/bytes_mut.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 2829a7cfb..ec74c4e97 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -1148,13 +1148,6 @@ impl Buf for BytesMut { #[inline] fn advance(&mut self, cnt: usize) { - // Advancing by the length is the same as resetting the length to 0, - // except this way we get to reuse the full capacity. - if cnt == self.remaining() { - self.clear(); - return; - } - assert!( cnt <= self.remaining(), "cannot advance past `remaining`: {:?} <= {:?}", From dc4fb3e8f45650500187f8cdbad5ac8ffdb7df0a Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 1 Aug 2024 21:55:24 +0100 Subject: [PATCH 53/60] chore: prepare bytes v1.7.1 (#727) --- CHANGELOG.md | 8 ++++++++ Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a4c5ba08..93c1419cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 1.7.1 (August 1, 2024) + +This release reverts the following change due to a regression: + +- Reuse capacity when possible in `::advance` impl (#698) + +The revert can be found at #726. + # 1.7.0 (July 31, 2024) ### Added diff --git a/Cargo.toml b/Cargo.toml index ac8bdf7d6..e07253942 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ name = "bytes" # When releasing to crates.io: # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.7.0" +version = "1.7.1" edition = "2018" rust-version = "1.39" license = "MIT" From ed7d5ff39e39c2802c0fa9e2fc308f6a3e0beda7 Mon Sep 17 00:00:00 2001 From: Cameron Bytheway Date: Fri, 2 Aug 2024 13:07:45 -0600 Subject: [PATCH 54/60] test: ensure BytesMut::advance reduces capacity (#728) --- tests/test_bytes.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 8e5d0ae1f..59c967b66 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -676,6 +676,43 @@ fn advance_bytes_mut() { assert_eq!(a, b"d zomg wat wat"[..]); } +// Ensures BytesMut::advance reduces always capacity +// +// See https://github.com/tokio-rs/bytes/issues/725 +#[test] +fn advance_bytes_mut_remaining_capacity() { + // reduce the search space under miri + let max_capacity = if cfg!(miri) { 16 } else { 256 }; + for capacity in 0..=max_capacity { + for len in 0..=capacity { + for advance in 0..=len { + eprintln!("testing capacity={capacity}, len={len}, advance={advance}"); + let mut buf = BytesMut::with_capacity(capacity); + + buf.resize(len, 42); + assert_eq!(buf.len(), len, "resize should write `len` bytes"); + assert_eq!( + buf.remaining(), + len, + "Buf::remaining() should equal BytesMut::len" + ); + + buf.advance(advance); + assert_eq!( + buf.remaining(), + len - advance, + "Buf::advance should reduce the remaining len" + ); + assert_eq!( + buf.capacity(), + capacity - advance, + "Buf::advance should reduce the remaining capacity" + ); + } + } + } +} + #[test] #[should_panic] fn advance_past_len() { From 291df5acc94b82a48765e67eeb1c1a2074539e68 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Mon, 19 Aug 2024 10:19:35 +0200 Subject: [PATCH 55/60] Fix double spaces in comments and doc comments (#731) --- src/buf/buf_mut.rs | 10 +++++----- src/buf/uninit_slice.rs | 2 +- src/bytes.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/buf/buf_mut.rs b/src/buf/buf_mut.rs index e13278d2c..2a3b5e9ee 100644 --- a/src/buf/buf_mut.rs +++ b/src/buf/buf_mut.rs @@ -1107,7 +1107,7 @@ pub unsafe trait BufMut { } } - /// Writes an IEEE754 single-precision (4 bytes) floating point number to + /// Writes an IEEE754 single-precision (4 bytes) floating point number to /// `self` in big-endian byte order. /// /// The current position is advanced by 4. @@ -1131,7 +1131,7 @@ pub unsafe trait BufMut { self.put_u32(n.to_bits()); } - /// Writes an IEEE754 single-precision (4 bytes) floating point number to + /// Writes an IEEE754 single-precision (4 bytes) floating point number to /// `self` in little-endian byte order. /// /// The current position is advanced by 4. @@ -1183,7 +1183,7 @@ pub unsafe trait BufMut { self.put_u32_ne(n.to_bits()); } - /// Writes an IEEE754 double-precision (8 bytes) floating point number to + /// Writes an IEEE754 double-precision (8 bytes) floating point number to /// `self` in big-endian byte order. /// /// The current position is advanced by 8. @@ -1207,7 +1207,7 @@ pub unsafe trait BufMut { self.put_u64(n.to_bits()); } - /// Writes an IEEE754 double-precision (8 bytes) floating point number to + /// Writes an IEEE754 double-precision (8 bytes) floating point number to /// `self` in little-endian byte order. /// /// The current position is advanced by 8. @@ -1231,7 +1231,7 @@ pub unsafe trait BufMut { self.put_u64_le(n.to_bits()); } - /// Writes an IEEE754 double-precision (8 bytes) floating point number to + /// Writes an IEEE754 double-precision (8 bytes) floating point number to /// `self` in native-endian byte order. /// /// The current position is advanced by 8. diff --git a/src/buf/uninit_slice.rs b/src/buf/uninit_slice.rs index 82ebdbbb3..aea096ae6 100644 --- a/src/buf/uninit_slice.rs +++ b/src/buf/uninit_slice.rs @@ -110,7 +110,7 @@ impl UninitSlice { unsafe { self[index..].as_mut_ptr().write(byte) } } - /// Copies bytes from `src` into `self`. + /// Copies bytes from `src` into `self`. /// /// The length of `src` must be the same as `self`. /// diff --git a/src/bytes.rs b/src/bytes.rs index e0c33b3e6..da0ba3a6f 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -1301,7 +1301,7 @@ unsafe fn shallow_clone_vec( offset: *const u8, len: usize, ) -> Bytes { - // If the buffer is still tracked in a `Vec`. It is time to + // If the buffer is still tracked in a `Vec`. It is time to // promote the vec to an `Arc`. This could potentially be called // concurrently, so some care must be taken. From 79fb85323cf4cf14d9b85f487b65fc147030cf4b Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Fri, 30 Aug 2024 14:20:29 +0200 Subject: [PATCH 56/60] fix: apply sign extension when decoding int (#732) Closes #730 --- src/buf/buf_impl.rs | 10 ++++++++-- tests/test_buf.rs | 13 +++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/buf/buf_impl.rs b/src/buf/buf_impl.rs index c44d4fb38..9ef464075 100644 --- a/src/buf/buf_impl.rs +++ b/src/buf/buf_impl.rs @@ -66,6 +66,12 @@ macro_rules! buf_get_impl { }}; } +// https://en.wikipedia.org/wiki/Sign_extension +fn sign_extend(val: u64, nbytes: usize) -> i64 { + let shift = (8 - nbytes) * 8; + (val << shift) as i64 >> shift +} + /// Read bytes from a buffer. /// /// A buffer stores bytes in memory such that read operations are infallible. @@ -923,7 +929,7 @@ pub trait Buf { /// This function panics if there is not enough remaining data in `self`, or /// if `nbytes` is greater than 8. fn get_int(&mut self, nbytes: usize) -> i64 { - buf_get_impl!(be => self, i64, nbytes); + sign_extend(self.get_uint(nbytes), nbytes) } /// Gets a signed n-byte integer from `self` in little-endian byte order. @@ -944,7 +950,7 @@ pub trait Buf { /// This function panics if there is not enough remaining data in `self`, or /// if `nbytes` is greater than 8. fn get_int_le(&mut self, nbytes: usize) -> i64 { - buf_get_impl!(le => self, i64, nbytes); + sign_extend(self.get_uint_le(nbytes), nbytes) } /// Gets a signed n-byte integer from `self` in native-endian byte order. diff --git a/tests/test_buf.rs b/tests/test_buf.rs index 3940f9247..5aadea43e 100644 --- a/tests/test_buf.rs +++ b/tests/test_buf.rs @@ -36,6 +36,19 @@ fn test_get_u16() { assert_eq!(0x5421, buf.get_u16_le()); } +#[test] +fn test_get_int() { + let mut buf = &b"\xd6zomg"[..]; + assert_eq!(-42, buf.get_int(1)); + let mut buf = &b"\xd6zomg"[..]; + assert_eq!(-42, buf.get_int_le(1)); + + let mut buf = &b"\xfe\x1d\xc0zomg"[..]; + assert_eq!(0xffffffffffc01dfeu64 as i64, buf.get_int_le(3)); + let mut buf = &b"\xfe\x1d\xc0zomg"[..]; + assert_eq!(0xfffffffffffe1dc0u64 as i64, buf.get_int(3)); +} + #[test] #[should_panic] fn test_get_u16_buffer_underflow() { From 4b6d35df39004b2d1c487a5eca7a15b2c32e0322 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Wed, 16 Aug 2023 07:44:07 -0500 Subject: [PATCH 57/60] added bytes-specific get_X functions to reduce dead code --- src/bytes.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/bytes.rs b/src/bytes.rs index da0ba3a6f..e85af5d42 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -580,6 +580,23 @@ impl Clone for Bytes { } } +macro_rules! buf_get_impl { + ($this:ident, $typ:tt::$conv:tt) => {{ + const SIZE: usize = mem::size_of::<$typ>(); + // slice.get() returns None on failing bounds check, resulting in a panic, but skips the unnecessary code of the + // default buf impl that needs to account for non-contiguous memory + let ret = $this + .chunk() + .get(..SIZE) + .map(|src| unsafe { $typ::$conv(*(src as *const _ as *const [_; SIZE])) }) + .unwrap(); + + // if the direct conversion was possible, advance and return + $this.advance(SIZE); + return ret; + }}; +} + impl Buf for Bytes { #[inline] fn remaining(&self) -> usize { @@ -608,6 +625,102 @@ impl Buf for Bytes { fn copy_to_bytes(&mut self, len: usize) -> Self { self.split_to(len) } + + fn get_u16(&mut self) -> u16 { + buf_get_impl!(self, u16::from_be_bytes); + } + + fn get_u16_le(&mut self) -> u16 { + buf_get_impl!(self, u16::from_le_bytes); + } + + fn get_u16_ne(&mut self) -> u16 { + buf_get_impl!(self, u16::from_ne_bytes); + } + + fn get_i16(&mut self) -> i16 { + buf_get_impl!(self, i16::from_be_bytes); + } + + fn get_i16_le(&mut self) -> i16 { + buf_get_impl!(self, i16::from_le_bytes); + } + + fn get_i16_ne(&mut self) -> i16 { + buf_get_impl!(self, i16::from_ne_bytes); + } + + fn get_u32(&mut self) -> u32 { + buf_get_impl!(self, u32::from_be_bytes); + } + + fn get_u32_le(&mut self) -> u32 { + buf_get_impl!(self, u32::from_le_bytes); + } + + fn get_u32_ne(&mut self) -> u32 { + buf_get_impl!(self, u32::from_ne_bytes); + } + + fn get_i32(&mut self) -> i32 { + buf_get_impl!(self, i32::from_be_bytes); + } + + fn get_i32_le(&mut self) -> i32 { + buf_get_impl!(self, i32::from_le_bytes); + } + + fn get_i32_ne(&mut self) -> i32 { + buf_get_impl!(self, i32::from_ne_bytes); + } + + fn get_u64(&mut self) -> u64 { + buf_get_impl!(self, u64::from_be_bytes); + } + + fn get_u64_le(&mut self) -> u64 { + buf_get_impl!(self, u64::from_le_bytes); + } + + fn get_u64_ne(&mut self) -> u64 { + buf_get_impl!(self, u64::from_ne_bytes); + } + + fn get_i64(&mut self) -> i64 { + buf_get_impl!(self, i64::from_be_bytes); + } + + fn get_i64_le(&mut self) -> i64 { + buf_get_impl!(self, i64::from_le_bytes); + } + + fn get_i64_ne(&mut self) -> i64 { + buf_get_impl!(self, i64::from_ne_bytes); + } + + fn get_u128(&mut self) -> u128 { + buf_get_impl!(self, u128::from_be_bytes); + } + + fn get_u128_le(&mut self) -> u128 { + buf_get_impl!(self, u128::from_le_bytes); + } + + fn get_u128_ne(&mut self) -> u128 { + buf_get_impl!(self, u128::from_ne_bytes); + } + + fn get_i128(&mut self) -> i128 { + buf_get_impl!(self, i128::from_be_bytes); + } + + fn get_i128_le(&mut self) -> i128 { + buf_get_impl!(self, i128::from_le_bytes); + } + + fn get_i128_ne(&mut self) -> i128 { + buf_get_impl!(self, i128::from_ne_bytes); + } } impl Deref for Bytes { From 50c4d2fc335ca6d0759afad40a5c31938d47e165 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Wed, 16 Aug 2023 08:21:39 -0500 Subject: [PATCH 58/60] added bytes-specific get_X tests --- src/bytes.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/bytes.rs b/src/bytes.rs index e85af5d42..75c349c36 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -626,98 +626,122 @@ impl Buf for Bytes { self.split_to(len) } + #[inline] fn get_u16(&mut self) -> u16 { buf_get_impl!(self, u16::from_be_bytes); } + #[inline] fn get_u16_le(&mut self) -> u16 { buf_get_impl!(self, u16::from_le_bytes); } + #[inline] fn get_u16_ne(&mut self) -> u16 { buf_get_impl!(self, u16::from_ne_bytes); } + #[inline] fn get_i16(&mut self) -> i16 { buf_get_impl!(self, i16::from_be_bytes); } + #[inline] fn get_i16_le(&mut self) -> i16 { buf_get_impl!(self, i16::from_le_bytes); } + #[inline] fn get_i16_ne(&mut self) -> i16 { buf_get_impl!(self, i16::from_ne_bytes); } + #[inline] fn get_u32(&mut self) -> u32 { buf_get_impl!(self, u32::from_be_bytes); } + #[inline] fn get_u32_le(&mut self) -> u32 { buf_get_impl!(self, u32::from_le_bytes); } + #[inline] fn get_u32_ne(&mut self) -> u32 { buf_get_impl!(self, u32::from_ne_bytes); } + #[inline] fn get_i32(&mut self) -> i32 { buf_get_impl!(self, i32::from_be_bytes); } + #[inline] fn get_i32_le(&mut self) -> i32 { buf_get_impl!(self, i32::from_le_bytes); } + #[inline] fn get_i32_ne(&mut self) -> i32 { buf_get_impl!(self, i32::from_ne_bytes); } + #[inline] fn get_u64(&mut self) -> u64 { buf_get_impl!(self, u64::from_be_bytes); } + #[inline] fn get_u64_le(&mut self) -> u64 { buf_get_impl!(self, u64::from_le_bytes); } + #[inline] fn get_u64_ne(&mut self) -> u64 { buf_get_impl!(self, u64::from_ne_bytes); } + #[inline] fn get_i64(&mut self) -> i64 { buf_get_impl!(self, i64::from_be_bytes); } + #[inline] fn get_i64_le(&mut self) -> i64 { buf_get_impl!(self, i64::from_le_bytes); } + #[inline] fn get_i64_ne(&mut self) -> i64 { buf_get_impl!(self, i64::from_ne_bytes); } + #[inline] fn get_u128(&mut self) -> u128 { buf_get_impl!(self, u128::from_be_bytes); } + #[inline] fn get_u128_le(&mut self) -> u128 { buf_get_impl!(self, u128::from_le_bytes); } + #[inline] fn get_u128_ne(&mut self) -> u128 { buf_get_impl!(self, u128::from_ne_bytes); } + #[inline] fn get_i128(&mut self) -> i128 { buf_get_impl!(self, i128::from_be_bytes); } + #[inline] fn get_i128_le(&mut self) -> i128 { buf_get_impl!(self, i128::from_le_bytes); } + #[inline] fn get_i128_ne(&mut self) -> i128 { buf_get_impl!(self, i128::from_ne_bytes); } From 2fb872eb326ce945d4ca9fcd640b46ed9450d9e3 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:11:20 -0600 Subject: [PATCH 59/60] more direct get strategy and additional tests --- src/bytes.rs | 66 +++++++++++++++++++++++++-------------------- tests/test_bytes.rs | 17 ++++++++++++ 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 75c349c36..7c3b72dd9 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -583,13 +583,11 @@ impl Clone for Bytes { macro_rules! buf_get_impl { ($this:ident, $typ:tt::$conv:tt) => {{ const SIZE: usize = mem::size_of::<$typ>(); + + assert!($this.len >= SIZE); // slice.get() returns None on failing bounds check, resulting in a panic, but skips the unnecessary code of the // default buf impl that needs to account for non-contiguous memory - let ret = $this - .chunk() - .get(..SIZE) - .map(|src| unsafe { $typ::$conv(*(src as *const _ as *const [_; SIZE])) }) - .unwrap(); + let ret = unsafe { $typ::$conv(($this.ptr as *const $typ).read_unaligned()) }; // if the direct conversion was possible, advance and return $this.advance(SIZE); @@ -626,124 +624,134 @@ impl Buf for Bytes { self.split_to(len) } + #[inline] + fn get_u8(&mut self) -> u8 { + buf_get_impl!(self, u8::from) + } + + #[inline] + fn get_i8(&mut self) -> i8 { + buf_get_impl!(self, i8::from) + } + #[inline] fn get_u16(&mut self) -> u16 { - buf_get_impl!(self, u16::from_be_bytes); + buf_get_impl!(self, u16::from_be); } #[inline] fn get_u16_le(&mut self) -> u16 { - buf_get_impl!(self, u16::from_le_bytes); + buf_get_impl!(self, u16::from_le); } #[inline] fn get_u16_ne(&mut self) -> u16 { - buf_get_impl!(self, u16::from_ne_bytes); + buf_get_impl!(self, u16::from); } #[inline] fn get_i16(&mut self) -> i16 { - buf_get_impl!(self, i16::from_be_bytes); + buf_get_impl!(self, i16::from_be); } #[inline] fn get_i16_le(&mut self) -> i16 { - buf_get_impl!(self, i16::from_le_bytes); + buf_get_impl!(self, i16::from_le); } #[inline] fn get_i16_ne(&mut self) -> i16 { - buf_get_impl!(self, i16::from_ne_bytes); + buf_get_impl!(self, i16::from); } #[inline] fn get_u32(&mut self) -> u32 { - buf_get_impl!(self, u32::from_be_bytes); + buf_get_impl!(self, u32::from_be); } #[inline] fn get_u32_le(&mut self) -> u32 { - buf_get_impl!(self, u32::from_le_bytes); + buf_get_impl!(self, u32::from_le); } #[inline] fn get_u32_ne(&mut self) -> u32 { - buf_get_impl!(self, u32::from_ne_bytes); + buf_get_impl!(self, u32::from); } #[inline] fn get_i32(&mut self) -> i32 { - buf_get_impl!(self, i32::from_be_bytes); + buf_get_impl!(self, i32::from_be); } #[inline] fn get_i32_le(&mut self) -> i32 { - buf_get_impl!(self, i32::from_le_bytes); + buf_get_impl!(self, i32::from_le); } #[inline] fn get_i32_ne(&mut self) -> i32 { - buf_get_impl!(self, i32::from_ne_bytes); + buf_get_impl!(self, i32::from); } #[inline] fn get_u64(&mut self) -> u64 { - buf_get_impl!(self, u64::from_be_bytes); + buf_get_impl!(self, u64::from_be); } #[inline] fn get_u64_le(&mut self) -> u64 { - buf_get_impl!(self, u64::from_le_bytes); + buf_get_impl!(self, u64::from_le); } #[inline] fn get_u64_ne(&mut self) -> u64 { - buf_get_impl!(self, u64::from_ne_bytes); + buf_get_impl!(self, u64::from); } #[inline] fn get_i64(&mut self) -> i64 { - buf_get_impl!(self, i64::from_be_bytes); + buf_get_impl!(self, i64::from_be); } #[inline] fn get_i64_le(&mut self) -> i64 { - buf_get_impl!(self, i64::from_le_bytes); + buf_get_impl!(self, i64::from_le); } #[inline] fn get_i64_ne(&mut self) -> i64 { - buf_get_impl!(self, i64::from_ne_bytes); + buf_get_impl!(self, i64::from); } #[inline] fn get_u128(&mut self) -> u128 { - buf_get_impl!(self, u128::from_be_bytes); + buf_get_impl!(self, u128::from_be); } #[inline] fn get_u128_le(&mut self) -> u128 { - buf_get_impl!(self, u128::from_le_bytes); + buf_get_impl!(self, u128::from_le); } #[inline] fn get_u128_ne(&mut self) -> u128 { - buf_get_impl!(self, u128::from_ne_bytes); + buf_get_impl!(self, u128::from); } #[inline] fn get_i128(&mut self) -> i128 { - buf_get_impl!(self, i128::from_be_bytes); + buf_get_impl!(self, i128::from_be); } #[inline] fn get_i128_le(&mut self) -> i128 { - buf_get_impl!(self, i128::from_le_bytes); + buf_get_impl!(self, i128::from_le); } #[inline] fn get_i128_ne(&mut self) -> i128 { - buf_get_impl!(self, i128::from_ne_bytes); + buf_get_impl!(self, i128::from); } } diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 59c967b66..03780f1d9 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -1399,3 +1399,20 @@ fn try_reclaim_arc() { buf.advance(2); assert_eq!(true, buf.try_reclaim(6)); } + +#[test] +#[should_panic] +fn test_bytes_overread() { + let mut b = Bytes::from_static(&[0, 1, 2]); + let _ = b.get_u32(); +} + +// running this test would result in a panic without `.read_unaligned()` +// on x86 read_unaligned compiles down to a single `mov`, on platforms with no unaligned access, +// it uses rust's `copy_nonoverlapping` +#[test] +fn test_bytes_misaligned() { + let mut b = Bytes::from_static(&[0, 1, 2, 3, 4, 5, 6, 7, 8]); + b.advance(2); + let _ = b.get_u32(); +} From b43c8c1cb88a52cb94a14a9443175a0f2cecf1d3 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Sun, 1 Sep 2024 18:21:11 -0500 Subject: [PATCH 60/60] whoops --- src/bytes.rs | 130 --------------------------------------------------- 1 file changed, 130 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 88338ee00..7c3b72dd9 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -753,136 +753,6 @@ impl Buf for Bytes { fn get_i128_ne(&mut self) -> i128 { buf_get_impl!(self, i128::from); } - - #[inline] - fn get_u8(&mut self) -> u8 { - buf_get_impl!(self, u8::from) - } - - #[inline] - fn get_i8(&mut self) -> i8 { - buf_get_impl!(self, i8::from) - } - - #[inline] - fn get_u16(&mut self) -> u16 { - buf_get_impl!(self, u16::from_be); - } - - #[inline] - fn get_u16_le(&mut self) -> u16 { - buf_get_impl!(self, u16::from_le); - } - - #[inline] - fn get_u16_ne(&mut self) -> u16 { - buf_get_impl!(self, u16::from); - } - - #[inline] - fn get_i16(&mut self) -> i16 { - buf_get_impl!(self, i16::from_be); - } - - #[inline] - fn get_i16_le(&mut self) -> i16 { - buf_get_impl!(self, i16::from_le); - } - - #[inline] - fn get_i16_ne(&mut self) -> i16 { - buf_get_impl!(self, i16::from); - } - - #[inline] - fn get_u32(&mut self) -> u32 { - buf_get_impl!(self, u32::from_be); - } - - #[inline] - fn get_u32_le(&mut self) -> u32 { - buf_get_impl!(self, u32::from_le); - } - - #[inline] - fn get_u32_ne(&mut self) -> u32 { - buf_get_impl!(self, u32::from); - } - - #[inline] - fn get_i32(&mut self) -> i32 { - buf_get_impl!(self, i32::from_be); - } - - #[inline] - fn get_i32_le(&mut self) -> i32 { - buf_get_impl!(self, i32::from_le); - } - - #[inline] - fn get_i32_ne(&mut self) -> i32 { - buf_get_impl!(self, i32::from); - } - - #[inline] - fn get_u64(&mut self) -> u64 { - buf_get_impl!(self, u64::from_be); - } - - #[inline] - fn get_u64_le(&mut self) -> u64 { - buf_get_impl!(self, u64::from_le); - } - - #[inline] - fn get_u64_ne(&mut self) -> u64 { - buf_get_impl!(self, u64::from); - } - - #[inline] - fn get_i64(&mut self) -> i64 { - buf_get_impl!(self, i64::from_be); - } - - #[inline] - fn get_i64_le(&mut self) -> i64 { - buf_get_impl!(self, i64::from_le); - } - - #[inline] - fn get_i64_ne(&mut self) -> i64 { - buf_get_impl!(self, i64::from); - } - - #[inline] - fn get_u128(&mut self) -> u128 { - buf_get_impl!(self, u128::from_be); - } - - #[inline] - fn get_u128_le(&mut self) -> u128 { - buf_get_impl!(self, u128::from_le); - } - - #[inline] - fn get_u128_ne(&mut self) -> u128 { - buf_get_impl!(self, u128::from); - } - - #[inline] - fn get_i128(&mut self) -> i128 { - buf_get_impl!(self, i128::from_be); - } - - #[inline] - fn get_i128_le(&mut self) -> i128 { - buf_get_impl!(self, i128::from_le); - } - - #[inline] - fn get_i128_ne(&mut self) -> i128 { - buf_get_impl!(self, i128::from); - } } impl Deref for Bytes {