Skip to content

Commit 10242db

Browse files
wcampbell0x2asharksforarms
authored andcommitted
Add DekuSize impl
* Add new derive DekuSize, which emits SIZE_BYTES and SIZE_BITS. This can be use to get the max bit/byte size of the parsed enum/struct so that your stack/heap can be properly allocated before usage. * Generated for all primitive and std types we have, others such as Vec are impossible. Closees #599, #307
1 parent 4fb3527 commit 10242db

File tree

17 files changed

+905
-43
lines changed

17 files changed

+905
-43
lines changed

deku-derive/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,17 @@ impl DekuData {
462462
fn emit_writer_checked(&self) -> Result<TokenStream, syn::Error> {
463463
emit_deku_write(self)
464464
}
465+
466+
/// Emit a size implementation
467+
fn emit_size(&self) -> TokenStream {
468+
self.emit_size_checked()
469+
.unwrap_or_else(|e| e.to_compile_error())
470+
}
471+
472+
/// Emit a size implementation, no compile_error
473+
fn emit_size_checked(&self) -> Result<TokenStream, syn::Error> {
474+
macros::deku_size::emit_deku_size(self)
475+
}
465476
}
466477

467478
/// Common variables from `DekuData` for `emit_enum` read/write functions
@@ -1241,6 +1252,15 @@ pub fn proc_deku_write(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
12411252
}
12421253
}
12431254

1255+
/// Entry function for `DekuSize` proc-macro
1256+
#[proc_macro_derive(DekuSize, attributes(deku))]
1257+
pub fn proc_deku_size(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1258+
match DekuData::from_input(input.into()) {
1259+
Ok(data) => data.emit_size().into(),
1260+
Err(err) => err.into(),
1261+
}
1262+
}
1263+
12441264
fn is_not_deku(attr: &syn::Attribute) -> bool {
12451265
attr.path()
12461266
.get_ident()
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use std::convert::TryFrom;
2+
3+
use darling::ast::Data;
4+
use proc_macro2::TokenStream;
5+
use quote::quote;
6+
7+
use crate::{DekuData, DekuDataEnum, DekuDataStruct, FieldData};
8+
9+
pub(crate) fn emit_deku_size(input: &DekuData) -> Result<TokenStream, syn::Error> {
10+
// NOTE: This is overkill, but keeps the same validating requirements of DekuRead. This ensures
11+
// the required values are set so we calculate the correct size.
12+
// TODO: We only care about validating and not the codegen. Split the two.
13+
let _ = super::deku_read::emit_deku_read(input)?;
14+
15+
match &input.data {
16+
Data::Enum(_) => emit_enum(input),
17+
Data::Struct(_) => emit_struct(input),
18+
}
19+
}
20+
21+
/// Calculate the size of a collection of fields
22+
fn calculate_fields_size<'a>(
23+
fields: impl IntoIterator<Item = &'a FieldData>,
24+
crate_: &syn::Ident,
25+
) -> TokenStream {
26+
let field_sizes = fields.into_iter().filter_map(|f| {
27+
if !f.temp {
28+
let field_type = &f.ty;
29+
30+
#[cfg(feature = "bits")]
31+
if let Some(bits) = &f.bits {
32+
return Some(quote! { (#bits) });
33+
}
34+
35+
if let Some(bytes) = &f.bytes {
36+
return Some(quote! { (#bytes) * 8 });
37+
}
38+
39+
Some(quote! { <#field_type as ::#crate_::DekuSize>::SIZE_BITS })
40+
} else {
41+
None
42+
}
43+
});
44+
45+
quote! { 0 #(+ #field_sizes)* }
46+
}
47+
48+
/// Add DekuSize trait bounds to where clause for fields that need them
49+
fn add_field_bounds<'a>(
50+
where_clause: &mut Option<syn::WhereClause>,
51+
fields: impl IntoIterator<Item = &'a FieldData>,
52+
crate_: &syn::Ident,
53+
) {
54+
for field in fields {
55+
if !field.temp {
56+
let field_type = &field.ty;
57+
#[cfg(feature = "bits")]
58+
let needs_bound = field.bits.is_none() && field.bytes.is_none();
59+
#[cfg(not(feature = "bits"))]
60+
let needs_bound = field.bytes.is_none();
61+
62+
if needs_bound {
63+
let where_clause = where_clause.get_or_insert_with(|| syn::parse_quote! { where });
64+
where_clause.predicates.push(syn::parse_quote! {
65+
#field_type: ::#crate_::DekuSize
66+
});
67+
}
68+
}
69+
}
70+
}
71+
72+
/// Calculate the discriminant size for an enum
73+
fn calculate_discriminant_size(
74+
input: &DekuData,
75+
id: Option<&crate::Id>,
76+
id_type: Option<&TokenStream>,
77+
crate_: &syn::Ident,
78+
) -> TokenStream {
79+
#[cfg(feature = "bits")]
80+
if let Some(bits) = &input.bits {
81+
return quote! { (#bits) };
82+
}
83+
84+
if let Some(bytes) = &input.bytes {
85+
return quote! { (#bytes) * 8 };
86+
}
87+
88+
if id.is_some() {
89+
return quote! { 0 };
90+
}
91+
92+
if let Some(id_type) = id_type {
93+
return quote! { <#id_type as ::#crate_::DekuSize>::SIZE_BITS };
94+
}
95+
96+
// This unwrap is ensured by us calling DekuRead validation
97+
let repr = &input.repr.unwrap();
98+
let repr_type = TokenStream::from(*repr);
99+
quote! { <#repr_type as ::#crate_::DekuSize>::SIZE_BITS }
100+
}
101+
102+
fn emit_struct(input: &DekuData) -> Result<TokenStream, syn::Error> {
103+
let crate_ = super::get_crate_name();
104+
105+
let DekuDataStruct {
106+
imp: _,
107+
wher: _,
108+
ident,
109+
fields,
110+
} = DekuDataStruct::try_from(input)?;
111+
112+
let size_calculation = calculate_fields_size(fields.iter().copied(), &crate_);
113+
114+
let (imp_generics, ty_generics, where_clause) = input.generics.split_for_impl();
115+
116+
let mut where_clause = where_clause.cloned();
117+
add_field_bounds(&mut where_clause, fields.iter().copied(), &crate_);
118+
119+
let tokens = quote! {
120+
impl #imp_generics ::#crate_::DekuSize for #ident #ty_generics #where_clause {
121+
const SIZE_BITS: usize = #size_calculation;
122+
}
123+
};
124+
125+
Ok(tokens)
126+
}
127+
128+
fn emit_enum(input: &DekuData) -> Result<TokenStream, syn::Error> {
129+
let crate_ = super::get_crate_name();
130+
131+
let DekuDataEnum {
132+
imp: _,
133+
wher: _,
134+
variants,
135+
ident,
136+
id,
137+
id_type,
138+
id_args: _,
139+
} = DekuDataEnum::try_from(input)?;
140+
141+
let discriminant_size = calculate_discriminant_size(input, id, id_type, &crate_);
142+
143+
let variant_sizes = variants
144+
.iter()
145+
.map(|variant| calculate_fields_size(variant.fields.iter(), &crate_));
146+
147+
let max_variant_size = quote! {
148+
{
149+
const fn const_max(a: usize, b: usize) -> usize {
150+
if a > b { a } else { b }
151+
}
152+
153+
let mut max = 0;
154+
#(
155+
max = const_max(max, #variant_sizes);
156+
)*
157+
max
158+
}
159+
};
160+
161+
let (imp_generics, ty_generics, where_clause) = input.generics.split_for_impl();
162+
163+
let mut where_clause = where_clause.cloned();
164+
for variant in variants.iter() {
165+
add_field_bounds(&mut where_clause, variant.fields.iter(), &crate_);
166+
}
167+
168+
let tokens = quote! {
169+
impl #imp_generics ::#crate_::DekuSize for #ident #ty_generics #where_clause {
170+
const SIZE_BITS: usize = #discriminant_size + #max_variant_size;
171+
}
172+
};
173+
174+
Ok(tokens)
175+
}

deku-derive/src/macros/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use syn::LitStr;
1111
use crate::Num;
1212

1313
pub(crate) mod deku_read;
14+
pub(crate) mod deku_size;
1415
pub(crate) mod deku_write;
1516

1617
#[cfg(feature = "proc-macro-crate")]

ensure_no_std/src/no_alloc_imports.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use deku::prelude::*;
22

3-
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
3+
#[derive(Debug, PartialEq, DekuRead, DekuWrite, DekuSize)]
44
struct DekuTest {
55
field_a: u8,
66
field_b: u8,
@@ -23,6 +23,7 @@ pub fn rw() {
2323
);
2424

2525
// Test writing
26-
let mut buf = [0; 20];
26+
const BUF_SIZE: usize = DekuTest::SIZE_BYTES.unwrap();
27+
let mut buf = [0; BUF_SIZE];
2728
let _val = val.to_slice(&mut buf).unwrap();
2829
}

src/impls/bool.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ where
4747
}
4848
}
4949

50+
impl crate::DekuSize for bool {
51+
const SIZE_BITS: usize = 8;
52+
}
53+
5054
#[cfg(test)]
5155
mod tests {
5256
use hexlit::hex;

src/impls/primitive.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,33 @@ ImplDekuTraitsBytesUnsigned!(f32, u32);
12591259
ImplDekuTraitsUnsigned!(f64, u64);
12601260
ImplDekuTraitsBytesUnsigned!(f64, u64);
12611261

1262+
use crate::DekuSize;
1263+
1264+
macro_rules! ImplDekuSize {
1265+
($typ:ty) => {
1266+
impl DekuSize for $typ {
1267+
const SIZE_BITS: usize = core::mem::size_of::<$typ>() * 8;
1268+
}
1269+
};
1270+
}
1271+
1272+
ImplDekuSize!(u8);
1273+
ImplDekuSize!(u16);
1274+
ImplDekuSize!(u32);
1275+
ImplDekuSize!(u64);
1276+
ImplDekuSize!(u128);
1277+
ImplDekuSize!(usize);
1278+
1279+
ImplDekuSize!(i8);
1280+
ImplDekuSize!(i16);
1281+
ImplDekuSize!(i32);
1282+
ImplDekuSize!(i64);
1283+
ImplDekuSize!(i128);
1284+
ImplDekuSize!(isize);
1285+
1286+
ImplDekuSize!(f32);
1287+
ImplDekuSize!(f64);
1288+
12621289
#[cfg(feature = "std")]
12631290
#[cfg(test)]
12641291
mod tests {

src/impls/slice.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ where
9494
}
9595
}
9696

97+
impl<T: crate::DekuSize, const N: usize> crate::DekuSize for [T; N] {
98+
const SIZE_BITS: usize = T::SIZE_BITS * N;
99+
}
100+
97101
#[cfg(feature = "std")]
98102
#[cfg(test)]
99103
mod tests {

src/impls/tuple.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ ImplDekuTupleTraits! { A, B, C, D, E, F, G, H, I, }
8181
ImplDekuTupleTraits! { A, B, C, D, E, F, G, H, I, J, }
8282
ImplDekuTupleTraits! { A, B, C, D, E, F, G, H, I, J, K, }
8383

84+
macro_rules! ImplDekuSizeTuple {
85+
( $($T:ident,)+ ) => {
86+
impl<$($T: crate::DekuSize),+> crate::DekuSize for ($($T,)+) {
87+
const SIZE_BITS: usize = 0 $(+ $T::SIZE_BITS)+;
88+
}
89+
};
90+
}
91+
92+
ImplDekuSizeTuple! { A, }
93+
ImplDekuSizeTuple! { A, B, }
94+
ImplDekuSizeTuple! { A, B, C, }
95+
ImplDekuSizeTuple! { A, B, C, D, }
96+
ImplDekuSizeTuple! { A, B, C, D, E, }
97+
ImplDekuSizeTuple! { A, B, C, D, E, F, }
98+
ImplDekuSizeTuple! { A, B, C, D, E, F, G, }
99+
ImplDekuSizeTuple! { A, B, C, D, E, F, G, H, }
100+
ImplDekuSizeTuple! { A, B, C, D, E, F, G, H, I, }
101+
ImplDekuSizeTuple! { A, B, C, D, E, F, G, H, I, J, }
102+
ImplDekuSizeTuple! { A, B, C, D, E, F, G, H, I, J, K, }
103+
84104
#[cfg(all(feature = "alloc", feature = "std"))]
85105
#[cfg(test)]
86106
mod tests {

0 commit comments

Comments
 (0)