Repository: sharksforarms/deku
Pull request #481 (2024-09-18 04:10:08)

Add count Vec<u8> Specializations
* For Vec<u8> when using count, specialize into reading the bytes all
at once

See #462

github-actions[bot]: ## Benchmark for 88e3bbc
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.1±0.02µs | **5.0±0.01µs** | **-1.96%** |
| deku_read_bits | **1180.2±16.27ns** | 1208.0±17.87ns | **+2.36%** |
| deku_read_byte | 5.8±0.08ns | 5.8±0.04ns | 0.00% |
| deku_read_enum | 5.3±0.13ns | **5.2±0.05ns** | **-1.89%** |
| deku_read_vec | 42.6±0.35ns | 42.6±0.22ns | 0.00% |
| deku_write_bits | 185.6±3.56ns | **170.0±2.39ns** | **-8.41%** |
| deku_write_byte | 24.9±0.52ns | **24.6±0.29ns** | **-1.20%** |
| deku_write_enum | **22.6±0.35ns** | 23.0±0.47ns | **+1.77%** |
| deku_write_vec | **423.8±3.75ns** | 437.0±10.41ns | **+3.11%** |
| read_all | 5.5±0.07µs | 5.5±0.02µs | 0.00% |
| read_all_bytes | 5.6±0.04µs | 5.6±0.05µs | 0.00% |
| read_exact | 97.4±1.49ns | N/A | N/A |

  </details>
  
github-actions[bot]: ## Benchmark for 78781ca
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | **5.0±0.02µs** | 5.1±0.02µs | **+2.00%** |
| deku_read_bits | 1165.9±11.41ns | **1130.9±16.18ns** | **-3.00%** |
| deku_read_byte | 5.9±0.04ns | 5.9±0.09ns | 0.00% |
| deku_read_enum | 4.7±0.04ns | 4.7±0.03ns | 0.00% |
| deku_read_vec | 42.5±0.36ns | 42.5±0.24ns | 0.00% |
| deku_write_bits | 186.4±3.90ns | **169.7±1.77ns** | **-8.96%** |
| deku_write_byte | 25.0±0.31ns | **24.7±0.25ns** | **-1.20%** |
| deku_write_enum | 22.8±0.34ns | 23.1±0.51ns | +1.32% |
| deku_write_vec | **412.7±4.85ns** | 418.4±8.71ns | **+1.38%** |
| read_all | 3.9±0.55µs | **3.8±0.03µs** | **-2.56%** |
| read_all_bytes | 3.8±0.07µs | 3.8±0.05µs | 0.00% |
| read_exact | 99.0±0.93ns | N/A | N/A |

  </details>
  
github-actions[bot]: ## Benchmark for 3149e3f
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.1±0.02µs | N/A | N/A |
| count_from_u8_specialize | 96.4±1.09ns | N/A | N/A |
| count_no_specialize | 2.4±0.03µs | N/A | N/A |
| count_specialize | 96.0±1.02ns | N/A | N/A |
| deku_read_bits | 1185.3±14.65ns | 1196.7±16.53ns | +0.96% |
| deku_read_byte | **5.3±0.07ns** | 5.9±0.09ns | **+11.32%** |
| deku_read_enum | **4.5±0.11ns** | 4.7±0.04ns | **+4.44%** |
| deku_read_vec | 42.5±0.46ns | **30.4±0.78ns** | **-28.47%** |
| deku_write_bits | 186.5±1.46ns | **184.7±7.33ns** | **-0.97%** |
| deku_write_byte | 25.1±1.05ns | 25.0±0.36ns | -0.40% |
| deku_write_enum | 22.8±0.41ns | 22.9±0.79ns | +0.44% |
| deku_write_vec | 427.4±5.02ns | **415.6±7.32ns** | **-2.76%** |
| read_all | 5.7±0.03µs | **3.7±0.02µs** | **-35.09%** |
| read_all_bytes | 5.7±0.02µs | **4.0±0.22µs** | **-29.82%** |

  </details>
  
github-actions[bot]: ## Benchmark for 079a65c
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.0±0.02µs | N/A | N/A |
| count_from_u8_specialize | 99.9±0.58ns | N/A | N/A |
| count_no_specialize | 2.4±0.03µs | N/A | N/A |
| count_specialize | 96.6±0.90ns | N/A | N/A |
| deku_read_bits | **1166.0±14.69ns** | 1202.0±26.40ns | **+3.09%** |
| deku_read_byte | 5.9±0.05ns | 5.9±0.02ns | 0.00% |
| deku_read_enum | 4.7±0.06ns | 4.7±0.10ns | 0.00% |
| deku_read_vec | 42.8±0.58ns | **30.3±0.33ns** | **-29.21%** |
| deku_write_bits | 186.6±4.89ns | 185.8±6.89ns | -0.43% |
| deku_write_byte | 25.0±0.34ns | 25.2±1.18ns | +0.80% |
| deku_write_enum | 22.9±1.28ns | 22.8±0.28ns | -0.44% |
| deku_write_vec | **403.4±6.66ns** | 423.1±7.59ns | **+4.88%** |
| read_all | **3.8±0.09µs** | 3.9±0.03µs | **+2.63%** |
| read_all_bytes | 3.8±0.05µs | 3.8±0.05µs | 0.00% |

  </details>
  
github-actions[bot]: ## Benchmark for 7fcef63
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.0±0.03µs | N/A | N/A |
| count_from_u8_specialize | 89.7±1.33ns | N/A | N/A |
| count_no_specialize | 2.7±0.02µs | N/A | N/A |
| count_specialize | 97.9±2.97ns | N/A | N/A |
| deku_read_bits | **1166.6±19.82ns** | 1197.2±13.91ns | **+2.62%** |
| deku_read_byte | 5.3±0.04ns | 5.3±0.08ns | 0.00% |
| deku_read_enum | 4.5±0.05ns | **4.4±0.06ns** | **-2.22%** |
| deku_read_vec | 42.5±0.30ns | **30.4±0.60ns** | **-28.47%** |
| deku_write_bits | 187.8±4.02ns | 185.1±7.55ns | -1.44% |
| deku_write_byte | 25.0±0.45ns | 25.1±0.57ns | +0.40% |
| deku_write_enum | 22.8±0.58ns | 22.8±0.20ns | 0.00% |
| deku_write_vec | **409.2±6.31ns** | 430.3±6.83ns | **+5.16%** |
| read_all | **5.6±0.04µs** | 6.5±0.03µs | **+16.07%** |
| read_all_bytes | 5.6±0.04µs | 5.6±0.04µs | 0.00% |

  </details>
  
github-actions[bot]: ## Benchmark for b0370d3
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| count | 5.0±0.01µs | N/A | N/A |
| count_from_u8_specialize | 99.1±1.12ns | N/A | N/A |
| count_no_specialize | 2.4±0.02µs | N/A | N/A |
| count_specialize | 94.3±0.97ns | N/A | N/A |
| deku_read_bits | **1188.8±6.48ns** | 1217.7±35.32ns | **+2.43%** |
| deku_read_byte | 5.9±0.03ns | 5.9±0.11ns | 0.00% |
| deku_read_enum | 4.7±0.05ns | 4.7±0.05ns | 0.00% |
| deku_read_vec | 42.8±0.30ns | **30.3±0.57ns** | **-29.21%** |
| deku_write_bits | 195.6±2.25ns | **185.0±5.83ns** | **-5.42%** |
| deku_write_byte | 25.0±0.42ns | 25.0±0.23ns | 0.00% |
| deku_write_enum | 22.7±0.26ns | 22.8±0.62ns | +0.44% |
| deku_write_vec | **425.6±3.79ns** | 434.8±7.41ns | **+2.16%** |
| read_all | 3.8±0.05µs | 3.8±0.05µs | 0.00% |
| read_all_bytes | 3.8±0.03µs | 3.8±0.06µs | 0.00% |

  </details>
  

Patch:
diff --git a/benches/deku.rs b/benches/deku.rs
index 7951433..80dd55a 100644
--- a/benches/deku.rs
+++ b/benches/deku.rs
@@ -111,7 +111,7 @@ fn criterion_benchmark(c: &mut Criterion) {
     });
 }
 
-pub fn read_all_vs_count(c: &mut Criterion) {
+pub fn read_all_vs_count_vs_read_exact(c: &mut Criterion) {
     #[derive(DekuRead, DekuWrite)]
     pub struct AllWrapper {
         #[deku(read_all)]
@@ -119,8 +119,20 @@ pub fn read_all_vs_count(c: &mut Criterion) {
     }
 
     #[derive(DekuRead, DekuWrite)]
-    #[deku(ctx = "len: usize")]
     pub struct CountWrapper {
+        #[deku(count = "1500")]
+        pub data: Vec<u8>,
+    }
+
+    #[derive(DekuRead, DekuWrite)]
+    pub struct CountNonSpecialize {
+        #[deku(count = "(1500/2)")]
+        pub data: Vec<u16>,
+    }
+
+    #[derive(DekuRead, DekuWrite)]
+    #[deku(ctx = "len: usize")]
+    pub struct CountFromCtxWrapper {
         #[deku(count = "len")]
         pub data: Vec<u8>,
     }
@@ -137,14 +149,34 @@ pub fn read_all_vs_count(c: &mut Criterion) {
         })
     });
 
-    c.bench_function("count", |b| {
+    c.bench_function("count_specialize", |b| {
+        b.iter(|| {
+            let mut cursor = Cursor::new([1u8; 1500].as_ref());
+            let mut reader = Reader::new(&mut cursor);
+            CountWrapper::from_reader_with_ctx(black_box(&mut reader), ())
+        })
+    });
+
+    c.bench_function("count_from_u8_specialize", |b| {
+        b.iter(|| {
+            let mut cursor = Cursor::new([1u8; 1500].as_ref());
+            let mut reader = Reader::new(&mut cursor);
+            CountWrapper::from_reader_with_ctx(black_box(&mut reader), ())
+        })
+    });
+
+    c.bench_function("count_no_specialize", |b| {
         b.iter(|| {
             let mut cursor = Cursor::new([1u8; 1500].as_ref());
             let mut reader = Reader::new(&mut cursor);
-            CountWrapper::from_reader_with_ctx(black_box(&mut reader), 1500)
+            CountNonSpecialize::from_reader_with_ctx(black_box(&mut reader), ())
         })
     });
 }
 
-criterion_group!(benches, criterion_benchmark, read_all_vs_count);
+criterion_group!(
+    benches,
+    criterion_benchmark,
+    read_all_vs_count_vs_read_exact
+);
 criterion_main!(benches);
diff --git a/deku-derive/src/lib.rs b/deku-derive/src/lib.rs
index 7859ab8..3d1a425 100644
--- a/deku-derive/src/lib.rs
+++ b/deku-derive/src/lib.rs
@@ -8,6 +8,7 @@ extern crate alloc;
 use alloc::borrow::Cow;
 use std::convert::TryFrom;
 use std::fmt::Display;
+use syn::Type;
 
 use darling::{ast, FromDeriveInput, FromField, FromMeta, FromVariant, ToTokens};
 use proc_macro2::TokenStream;
@@ -70,38 +71,41 @@ impl FromMeta for Id {
 }
 
 #[derive(Debug)]
-struct Num(syn::LitInt);
+enum Num {
+    LitInt(syn::LitInt),
+    TokenStream(TokenStream),
+}
 
 impl Num {
     fn new(n: syn::LitInt) -> Self {
-        Self(n)
+        Self::LitInt(n)
     }
 }
 
 impl Display for Num {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(&self.0.to_token_stream().to_string())
+        f.write_str(&self.to_token_stream().to_string())
     }
 }
 
 impl ToTokens for Num {
     fn to_tokens(&self, tokens: &mut TokenStream) {
-        self.0.to_tokens(tokens)
+        match self {
+            Num::LitInt(v) => v.to_tokens(tokens),
+            Num::TokenStream(v) => v.to_tokens(tokens),
+        }
     }
 }
 
 impl FromMeta for Num {
     fn from_value(value: &syn::Lit) -> darling::Result<Self> {
         (match *value {
-            syn::Lit::Str(ref s) => Ok(Num::new(syn::LitInt::new(
-                s.value()
-                    .as_str()
-                    .parse::<usize>()
-                    .map_err(|_| darling::Error::unknown_value(&s.value()))?
-                    .to_string()
-                    .as_str(),
-                s.span(),
-            ))),
+            syn::Lit::Str(ref s) => Ok(Num::TokenStream(
+                apply_replacements(s)
+                    .map_err(darling::Error::custom)?
+                    .parse::<TokenStream>()
+                    .expect("could not parse token stream"),
+            )),
             syn::Lit::Int(ref s) => Ok(Num::new(s.clone())),
             _ => Err(darling::Error::unexpected_lit_type(value)),
         })
@@ -409,7 +413,7 @@ impl<'a> TryFrom<&'a DekuData> for DekuDataStruct<'a> {
 #[derive(Debug)]
 struct FieldData {
     ident: Option<syn::Ident>,
-    ty: syn::Type,
+    ty: Type,
 
     /// endianness for the field
     endian: Option<syn::LitStr>,
@@ -856,7 +860,7 @@ fn default_res_opt<T, E>() -> Result<Option<T>, E> {
 #[darling(attributes(deku))]
 struct DekuFieldReceiver {
     ident: Option<syn::Ident>,
-    ty: syn::Type,
+    ty: Type,
 
     /// Endianness for the field
     #[darling(default)]
diff --git a/deku-derive/src/macros/deku_read.rs b/deku-derive/src/macros/deku_read.rs
index 5208633..4331ad4 100644
--- a/deku-derive/src/macros/deku_read.rs
+++ b/deku-derive/src/macros/deku_read.rs
@@ -777,14 +777,44 @@ fn emit_field_read(
                 }
             }
         } else if let Some(field_count) = &f.count {
-            quote! {
-                {
-                    use core::borrow::Borrow;
-                    #type_as_deku_read::from_reader_with_ctx
-                    (
-                        __deku_reader,
-                        (::#crate_::ctx::Limit::new_count(usize::try_from(*((#field_count).borrow()))?), (#read_args))
-                    )?
+            use syn::{GenericArgument, PathArguments, Type};
+            let mut is_vec_u8 = false;
+            if let Type::Path(type_path) = &f.ty {
+                if type_path.path.segments.len() == 1 && type_path.path.segments[0].ident == "Vec" {
+                    if let PathArguments::AngleBracketed(ref generic_args) =
+                        type_path.path.segments[0].arguments
+                    {
+                        if generic_args.args.len() == 1 {
+                            if let GenericArgument::Type(Type::Path(ref arg_path)) =
+                                generic_args.args[0]
+                            {
+                                is_vec_u8 = arg_path.path.is_ident("u8");
+                            }
+                        }
+                    }
+                }
+            }
+            if is_vec_u8 {
+                quote! {
+                    {
+                        use core::borrow::Borrow;
+                        #type_as_deku_read::from_reader_with_ctx
+                        (
+                            __deku_reader,
+                            ::#crate_::ctx::ReadExact(usize::try_from(*((#field_count).borrow()))?)
+                        )?
+                    }
+                }
+            } else {
+                quote! {
+                    {
+                        use core::borrow::Borrow;
+                        #type_as_deku_read::from_reader_with_ctx
+                        (
+                            __deku_reader,
+                            (::#crate_::ctx::Limit::new_count(usize::try_from(*((#field_count).borrow()))?), (#read_args))
+                        )?
+                    }
                 }
             }
         } else if let Some(field_bytes) = &f.bytes_read {
diff --git a/ensure_no_std/src/bin/main.rs b/ensure_no_std/src/bin/main.rs
index 3c8ac7f..6772955 100644
--- a/ensure_no_std/src/bin/main.rs
+++ b/ensure_no_std/src/bin/main.rs
@@ -24,6 +24,8 @@ struct DekuTest {
     count: u8,
     #[deku(count = "count", pad_bytes_after = "8")]
     data: Vec<u8>,
+    #[deku(count = "1")]
+    after: Vec<u8>,
 }
 
 #[entry]
@@ -39,7 +41,7 @@ fn main() -> ! {
     // now the allocator is ready types like Box, Vec can be used.
 
     #[allow(clippy::unusual_byte_groupings)]
-    let test_data: &[u8] = &[0b10101_101, 0x02, 0xBE, 0xEF, 0xff];
+    let test_data: &[u8] = &[0b10101_101, 0x02, 0xBE, 0xEF, 0xff, 0xaa];
     let mut cursor = deku::no_std_io::Cursor::new(test_data);
 
     // Test reading
@@ -49,7 +51,8 @@ fn main() -> ! {
             field_a: 0b10101,
             field_b: 0b101,
             count: 0x02,
-            data: vec![0xBE, 0xEF]
+            data: vec![0xBE, 0xEF],
+            after: vec![0xaa],
         },
         val
     );
diff --git a/src/attributes.rs b/src/attributes.rs
index f73ebde..e9b6146 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -520,6 +520,39 @@ let value: Vec<u8> = value.try_into().unwrap();
 assert_eq!(&*data, value);
 ```
 
+This attribute can also be set from a previous read:
+
+Example:
+```rust
+# use deku::prelude::*;
+# use std::convert::{TryInto, TryFrom};
+# #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
+struct DekuTest {
+    field_a_len: u8,
+    #[deku(bits = "*field_a_len as usize")]
+    field_a: u8,
+    #[deku(bits = 6)]
+    field_b: u8,
+}
+
+let data: &[u8] = &[0x02, 0b11_101010];
+
+let value = DekuTest::try_from(data).unwrap();
+
+assert_eq!(
+    DekuTest {
+       field_a_len: 2,
+       field_a: 0b11,
+       field_b: 0b101010,
+    },
+    value
+);
+
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(&*data, value);
+```
+
+
 # bytes
 
 Set the byte-size of the field
@@ -553,6 +586,35 @@ let value: Vec<u8> = value.try_into().unwrap();
 assert_eq!(data, value);
 ```
 
+This attribute can also be set from a previous read:
+
+Example:
+```rust
+# use deku::prelude::*;
+# use std::convert::{TryInto, TryFrom};
+# #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
+struct DekuTest {
+    field_a_size: u8,
+    #[deku(bytes = "*field_a_size as usize")]
+    field_a: u32,
+}
+
+let data: &[u8] = &[0x03, 0x01, 0x02, 0x03];
+
+let value = DekuTest::try_from(data).unwrap();
+
+assert_eq!(
+    DekuTest {
+       field_a_size: 0x03,
+       field_a: 0x030201,
+    },
+    value
+);
+
+let value: Vec<u8> = value.try_into().unwrap();
+assert_eq!(data, value);
+```
+
 # count
 
 Specify the field representing the length of the container, i.e. a Vec
@@ -587,6 +649,8 @@ assert_eq!(data, value);
 
 **Note**: See [update](#update) for more information on the attribute!
 
+## Specializations
+- `Vec<u8>`: `count` used with a byte vector will result in one invocation to `read_bytes`, thus improving performance.
 
 # bytes_read
 
@@ -707,7 +771,6 @@ let value: Vec<u8> = value.try_into().unwrap();
 assert_eq!(&*data, value);
 ```
 
-
 # update
 
 Specify custom code to run on the field when `.update()` is called on the struct/enum
diff --git a/src/ctx.rs b/src/ctx.rs
index 5565be2..393a1a9 100644
--- a/src/ctx.rs
+++ b/src/ctx.rs
@@ -201,3 +201,6 @@ impl BitSize {
         Self::bits_from_reader(core::mem::size_of_val(val))
     }
 }
+
+/// Amount of bytes to read_exact
+pub struct ReadExact(pub usize);
diff --git a/src/impls/vec.rs b/src/impls/vec.rs
index 33c89c9..fa93bcb 100644
--- a/src/impls/vec.rs
+++ b/src/impls/vec.rs
@@ -8,6 +8,20 @@ use crate::writer::Writer;
 use crate::{ctx::*, DekuReader};
 use crate::{DekuError, DekuWriter};
 
+impl<'a> DekuReader<'a, ReadExact> for Vec<u8> {
+    fn from_reader_with_ctx<R: Read + Seek>(
+        reader: &mut Reader<R>,
+        exact: ReadExact,
+    ) -> Result<Self, DekuError>
+    where
+        Self: Sized,
+    {
+        let mut bytes = alloc::vec![0x00; exact.0];
+        let _ = reader.read_bytes(exact.0, &mut bytes)?;
+        Ok(bytes)
+    }
+}
+
 /// Read `T`s into a vec until a given predicate returns true
 /// * `capacity` - an optional capacity to pre-allocate the vector with
 /// * `ctx` - The context required by `T`. It will be passed to every `T` when constructing.


Test patch:
diff --git a/tests/test_attributes/test_limits/test_count.rs b/tests/test_attributes/test_limits/test_count.rs
index c2e7871..544711e 100644
--- a/tests/test_attributes/test_limits/test_count.rs
+++ b/tests/test_attributes/test_limits/test_count.rs
@@ -27,6 +27,28 @@ mod test_slice {
         assert_eq!(test_data, ret_write);
     }
 
+    #[test]
+    fn test_count_static_non_u8() {
+        #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
+        struct TestStruct {
+            #[deku(count = "1")]
+            data: Vec<(u8, u8)>,
+        }
+
+        let test_data: Vec<u8> = [0xaa, 0xbb].to_vec();
+
+        let ret_read = TestStruct::try_from(test_data.as_slice()).unwrap();
+        assert_eq!(
+            TestStruct {
+                data: vec![(0xaa, 0xbb)],
+            },
+            ret_read
+        );
+
+        let ret_write: Vec<u8> = ret_read.try_into().unwrap();
+        assert_eq!(test_data, ret_write);
+    }
+
     #[test]
     fn test_count_from_field() {
         #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
@@ -74,7 +96,7 @@ mod test_slice {
     }
 
     #[test]
-    #[should_panic(expected = "Incomplete(NeedSize { bits: 8 })")]
+    #[should_panic(expected = "Incomplete(NeedSize { bits: 24 })")]
     fn test_count_error() {
         #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
         struct TestStruct {
@@ -156,7 +178,7 @@ mod test_vec {
     }
 
     #[test]
-    #[should_panic(expected = "Incomplete(NeedSize { bits: 8 })")]
+    #[should_panic(expected = "Incomplete(NeedSize { bits: 24 })")]
     fn test_count_error() {
         #[derive(PartialEq, Debug, DekuRead, DekuWrite)]
         struct TestStruct {