Skip to content

Commit 377e7c1

Browse files
committed
Merge remote-tracking branch 'origin/main' into danlaine/contiguous-2
2 parents adcc38c + 266ff72 commit 377e7c1

30 files changed

Lines changed: 3236 additions & 537 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ crc = "3.4.0"
114114
crc-fast = { version = "1.10.0", default-features = false }
115115
criterion = "0.7.0"
116116
crossbeam-queue = "0.3.12"
117+
crossbeam-utils = "0.8.21"
117118
crossterm = "0.29.0"
118119
ctutils = "0.3.1"
119120
curve25519-dalek = "4.1.3"

codec/src/codec.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
//! Core traits for encoding and decoding.
22
33
use crate::error::Error;
4+
#[cfg(not(feature = "std"))]
5+
use alloc::vec::Vec;
46
use bytes::{Buf, BufMut, Bytes, BytesMut};
7+
#[cfg(feature = "std")]
8+
use std::vec::Vec;
59

610
/// Trait for types with a known, fixed encoded size.
711
///
@@ -23,20 +27,86 @@ pub trait EncodeSize {
2327
/// Returns the encoded size of this value (in bytes).
2428
fn encode_size(&self) -> usize;
2529

30+
/// Returns the total encoded size of a sequence, excluding any container-specific length
31+
/// prefix.
32+
///
33+
/// Container implementations call this hook so element types can provide a more efficient
34+
/// aggregate size calculation. The default preserves normal element-by-element sizing.
35+
/// Fixed-size implementations compute `SIZE * len`, which avoids an O(n) sizing prepass
36+
/// before containers such as `Vec<T>` allocate their output buffer.
37+
///
38+
/// This hook exists because stable Rust cannot express the overlapping specialized
39+
/// container impls that would otherwise provide this aggregate path directly.
40+
///
41+
/// This is hidden from generated documentation because it is an implementation hook for
42+
/// codec's container types, not part of the intended user-facing API. Most users should
43+
/// implement [EncodeSize::encode_size] or [FixedSize] instead.
44+
#[doc(hidden)]
45+
#[inline]
46+
fn encode_size_slice(values: &[Self]) -> usize
47+
where
48+
Self: Sized,
49+
{
50+
values.iter().map(EncodeSize::encode_size).sum()
51+
}
52+
2653
/// Returns the encoded size excluding bytes passed to [`BufsMut::push`]
2754
/// during [`Write::write_bufs`]. Used to size the working buffer for inline
2855
/// writes. Override alongside [`Write::write_bufs`] for types where large
2956
/// [`Bytes`] fields go via push; failing to do so will over-allocate.
57+
#[inline]
3058
fn encode_inline_size(&self) -> usize {
3159
self.encode_size()
3260
}
61+
62+
/// Returns the total inline encoded size of a sequence, excluding any container-specific
63+
/// length prefix.
64+
///
65+
/// This hidden hook is the slice equivalent of [EncodeSize::encode_inline_size]. The
66+
/// default preserves normal element-by-element sizing. Fixed-size implementations override
67+
/// this to compute `SIZE * len`, matching [EncodeSize::encode_size_slice] for the
68+
/// [`Write::write_bufs`] path.
69+
///
70+
/// This hook exists because stable Rust cannot express the overlapping specialized
71+
/// container impls that would otherwise provide this aggregate path directly.
72+
///
73+
/// This is hidden from generated documentation for the same reason as
74+
/// [EncodeSize::encode_size_slice].
75+
#[doc(hidden)]
76+
#[inline]
77+
fn encode_inline_size_slice(values: &[Self]) -> usize
78+
where
79+
Self: Sized,
80+
{
81+
values
82+
.iter()
83+
.map(EncodeSize::encode_inline_size)
84+
.sum::<usize>()
85+
}
3386
}
3487

3588
// Automatically implement `EncodeSize` for types that are `FixedSize`.
3689
impl<T: FixedSize> EncodeSize for T {
90+
#[inline]
3791
fn encode_size(&self) -> usize {
3892
Self::SIZE
3993
}
94+
95+
#[inline]
96+
fn encode_size_slice(values: &[Self]) -> usize
97+
where
98+
Self: Sized,
99+
{
100+
Self::SIZE * values.len()
101+
}
102+
103+
#[inline]
104+
fn encode_inline_size_slice(values: &[Self]) -> usize
105+
where
106+
Self: Sized,
107+
{
108+
Self::encode_size_slice(values)
109+
}
40110
}
41111

42112
/// Trait for types that can be written (encoded) to a byte buffer.
@@ -46,12 +116,57 @@ pub trait Write {
46116
/// Implementations should panic if the buffer doesn't have enough capacity.
47117
fn write(&self, buf: &mut impl BufMut);
48118

119+
/// Writes the encoded payload for a sequence, excluding any container-specific length
120+
/// prefix.
121+
///
122+
/// Container implementations call this hook so element types can provide a more efficient
123+
/// aggregate write path. The default preserves normal element-by-element encoding.
124+
///
125+
/// This hook exists because stable Rust cannot express the overlapping specialized
126+
/// container impls that would otherwise provide this aggregate path directly.
127+
///
128+
/// This is hidden from generated documentation because it is an implementation hook for
129+
/// codec's container types, not part of the intended user-facing API. Most users should
130+
/// implement [Write::write] instead.
131+
#[doc(hidden)]
132+
#[inline]
133+
fn write_slice(values: &[Self], buf: &mut impl BufMut)
134+
where
135+
Self: Sized,
136+
{
137+
for item in values {
138+
item.write(buf);
139+
}
140+
}
141+
49142
/// Writes to a [`BufsMut`], allowing existing [`Bytes`] chunks to be
50143
/// appended via [`BufsMut::push`] instead of written inline. Must encode
51144
/// to the same format as [`Write::write`]. Defaults to [`Write::write`].
145+
#[inline]
52146
fn write_bufs(&self, buf: &mut impl BufsMut) {
53147
self.write(buf);
54148
}
149+
150+
/// Writes the encoded payload for a sequence to a [`BufsMut`], excluding any
151+
/// container-specific length prefix.
152+
///
153+
/// This hidden hook is the slice equivalent of [Write::write_bufs]. The default preserves
154+
/// normal element-by-element encoding.
155+
///
156+
/// This hook exists because stable Rust cannot express the overlapping specialized
157+
/// container impls that would otherwise provide this aggregate path directly.
158+
///
159+
/// This is hidden from generated documentation for the same reason as [Write::write_slice].
160+
#[doc(hidden)]
161+
#[inline]
162+
fn write_slice_bufs(values: &[Self], buf: &mut impl BufsMut)
163+
where
164+
Self: Sized,
165+
{
166+
for item in values {
167+
item.write_bufs(buf);
168+
}
169+
}
55170
}
56171

57172
/// Trait for types that can be read (decoded) from a byte buffer.
@@ -71,6 +186,44 @@ pub trait Read: Sized {
71186
/// Returns [Error] if decoding fails due to invalid data, insufficient bytes in the buffer,
72187
/// or violation of constraints imposed by the `cfg`.
73188
fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error>;
189+
190+
/// Reads `len` values from the buffer into a vector.
191+
///
192+
/// Container implementations call this hook so element types can provide a more efficient
193+
/// vector read path. The default preserves normal element-by-element decoding.
194+
///
195+
/// This hook exists because stable Rust cannot express the overlapping specialized
196+
/// container impls that would otherwise provide this aggregate path directly.
197+
///
198+
/// This is hidden from generated documentation because it is an implementation hook for
199+
/// codec's container types, not part of the intended user-facing API. Most users should
200+
/// implement [Read::read_cfg] instead.
201+
#[doc(hidden)]
202+
#[inline]
203+
fn read_vec(buf: &mut impl Buf, len: usize, cfg: &Self::Cfg) -> Result<Vec<Self>, Error> {
204+
let mut values = Vec::with_capacity(len);
205+
for _ in 0..len {
206+
values.push(Self::read_cfg(buf, cfg)?);
207+
}
208+
Ok(values)
209+
}
210+
211+
/// Reads exactly `N` values from the buffer into an array.
212+
///
213+
/// This hidden hook is the array equivalent of [Read::read_vec]. The default preserves
214+
/// normal element-by-element decoding.
215+
///
216+
/// This hook exists because stable Rust cannot express the overlapping specialized array
217+
/// impls that would otherwise provide this aggregate path directly.
218+
///
219+
/// This is hidden from generated documentation for the same reason as [Read::read_vec].
220+
#[doc(hidden)]
221+
#[inline]
222+
fn read_array<const N: usize>(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<[Self; N], Error> {
223+
Ok(Self::read_vec(buf, N, cfg)?
224+
.try_into()
225+
.unwrap_or_else(|_| unreachable!("array length should match capacity")))
226+
}
74227
}
75228

76229
/// Trait combining [Write] and [EncodeSize] for types that can be fully encoded.

codec/src/lib.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,32 @@
3232
//! that the entire buffer is consumed.
3333
//! - [Codec]: Combines [Encode] + [Decode].
3434
//!
35+
//! # Specialization
36+
//!
37+
//! Byte-oriented container paths use hidden trait hooks on [Write], [Read], and [EncodeSize] to
38+
//! select bulk-copy implementations while keeping generic fallbacks. Container implementations
39+
//! call hooks such as `T::write_slice`, `T::read_vec`, and `T::encode_size_slice`. The default
40+
//! methods preserve element-by-element behavior, while concrete element implementations can
41+
//! override only the paths they can make faster.
42+
//!
43+
//! Encoding specialization has two parts: aggregate sizing and aggregate writing. For example,
44+
//! `Vec<u8>::encode()` first asks for the output size, then writes the bytes. The [EncodeSize]
45+
//! slice hooks let fixed-size elements compute `SIZE * len` without scanning every element, while
46+
//! the [Write] slice hooks let byte containers write the payload with one bulk copy.
47+
//!
48+
//! These hooks keep the container code generic: a container like `Vec<T>` calls one element-level
49+
//! method for sizing, writing, or reading, and the element implementation decides whether the
50+
//! default element-by-element behavior or a bulk path applies.
51+
//!
3552
//! # Supported Types
3653
//!
3754
//! Natively supports encoding/decoding for:
3855
//! - Primitives: [bool],
3956
//! [u8], [u16], [u32], [u64], [u128],
4057
//! [i8], [i16], [i32], [i64], [i128],
41-
//! [f32], [f64], [u8; N],
42-
//! and [usize] (must fit within a [u32] for cross-platform compatibility).
58+
//! [f32], [f64], and [usize] (must fit within a [u32] for cross-platform compatibility).
59+
//! - Arrays: `[T; N]` supports [Write] and [Read] when `T` does, and supports [FixedSize],
60+
//! [Encode], [Codec], [EncodeFixed], and [CodecFixed] when `T: FixedSize`.
4361
//! - Collections: [`Vec`], [`Option`], `BTreeMap`, `BTreeSet`
4462
//! - Tuples: `(T1, T2, ...)` (up to 12 elements)
4563
//! - Common External Types: [::bytes::Bytes]

codec/src/types/mod.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,145 @@ where
105105

106106
Ok(())
107107
}
108+
109+
#[cfg(test)]
110+
pub(crate) mod tests {
111+
use crate::{BufsMut, Error, Read, Write};
112+
use bytes::{buf::UninitSlice, Buf, BufMut, Bytes, BytesMut};
113+
114+
/// One-byte test type that uses the default aggregate hooks.
115+
///
116+
/// This lets tests distinguish the generic per-element path from the
117+
/// specialized `u8` path while keeping the same encoded representation.
118+
#[derive(Debug, PartialEq, Eq)]
119+
pub struct Byte(pub u8);
120+
121+
impl Write for Byte {
122+
fn write(&self, buf: &mut impl BufMut) {
123+
buf.put_u8(self.0);
124+
}
125+
}
126+
127+
impl Read for Byte {
128+
type Cfg = ();
129+
130+
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, Error> {
131+
Ok(Self(<u8 as Read>::read_cfg(buf, &())?))
132+
}
133+
}
134+
135+
/// Test [`BufMut`] implementation that records how values are written.
136+
///
137+
/// Specialization-selection tests use this to assert whether a container
138+
/// wrote its payload with one aggregate [`BufMut::put_slice`] call or with
139+
/// per-element [`BufMut::put_u8`] calls.
140+
pub struct TrackingWriteBuf {
141+
inner: BytesMut,
142+
/// Number of aggregate slice writes.
143+
pub put_slice_calls: usize,
144+
/// Number of single-byte writes.
145+
pub put_u8_calls: usize,
146+
/// Number of externally pushed chunks.
147+
pub push_calls: usize,
148+
}
149+
150+
impl TrackingWriteBuf {
151+
pub fn new() -> Self {
152+
Self {
153+
inner: BytesMut::new(),
154+
put_slice_calls: 0,
155+
put_u8_calls: 0,
156+
push_calls: 0,
157+
}
158+
}
159+
160+
pub fn freeze(self) -> Bytes {
161+
self.inner.freeze()
162+
}
163+
}
164+
165+
// SAFETY: `TrackingWriteBuf` delegates storage and cursor management to
166+
// `BytesMut`, which upholds the `BufMut` invariants. The overridden write
167+
// methods only count calls before forwarding.
168+
unsafe impl BufMut for TrackingWriteBuf {
169+
fn remaining_mut(&self) -> usize {
170+
self.inner.remaining_mut()
171+
}
172+
173+
fn chunk_mut(&mut self) -> &mut UninitSlice {
174+
self.inner.chunk_mut()
175+
}
176+
177+
unsafe fn advance_mut(&mut self, cnt: usize) {
178+
// SAFETY: The caller guarantees that `cnt` bytes in the current
179+
// chunk were initialized. `BytesMut` owns the cursor state and
180+
// enforces the remaining invariants.
181+
unsafe { self.inner.advance_mut(cnt) }
182+
}
183+
184+
fn put_slice(&mut self, src: &[u8]) {
185+
self.put_slice_calls += 1;
186+
self.inner.put_slice(src);
187+
}
188+
189+
fn put_u8(&mut self, n: u8) {
190+
self.put_u8_calls += 1;
191+
self.inner.put_u8(n);
192+
}
193+
}
194+
195+
impl BufsMut for TrackingWriteBuf {
196+
fn push(&mut self, bytes: impl Into<Bytes>) {
197+
let bytes = bytes.into();
198+
self.push_calls += 1;
199+
self.inner.extend_from_slice(&bytes);
200+
}
201+
}
202+
203+
/// Test [`Buf`] implementation that records how values are read.
204+
///
205+
/// Specialization-selection tests use this to assert whether a container
206+
/// read its payload with one aggregate [`Buf::copy_to_slice`] call or with
207+
/// per-element [`Buf::get_u8`] calls.
208+
pub struct TrackingReadBuf {
209+
inner: Bytes,
210+
/// Number of aggregate slice reads.
211+
pub copy_to_slice_calls: usize,
212+
/// Number of single-byte reads.
213+
pub get_u8_calls: usize,
214+
}
215+
216+
impl TrackingReadBuf {
217+
pub fn new(bytes: &'static [u8]) -> Self {
218+
Self {
219+
inner: Bytes::from_static(bytes),
220+
copy_to_slice_calls: 0,
221+
get_u8_calls: 0,
222+
}
223+
}
224+
}
225+
226+
impl Buf for TrackingReadBuf {
227+
fn remaining(&self) -> usize {
228+
self.inner.remaining()
229+
}
230+
231+
fn chunk(&self) -> &[u8] {
232+
self.inner.chunk()
233+
}
234+
235+
fn advance(&mut self, cnt: usize) {
236+
self.inner.advance(cnt)
237+
}
238+
239+
fn copy_to_slice(&mut self, dst: &mut [u8]) {
240+
self.copy_to_slice_calls += 1;
241+
self.inner.copy_to_slice(dst);
242+
}
243+
244+
fn get_u8(&mut self) -> u8 {
245+
self.get_u8_calls += 1;
246+
self.inner.get_u8()
247+
}
248+
}
249+
}

0 commit comments

Comments
 (0)