Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ethercrab-wire-derive/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ Derives for `ethercrab`.

## [Unreleased] - ReleaseDate

## Added

- [#343](https://github.com/ethercrab-rs/ethercrab/pull/343) (@marcfir) Added support for
`#[repr(C, packed)]` on structs

### Changed

- **(breaking)** [#230](https://github.com/ethercrab-rs/ethercrab/pull/230) Increase MSRV from 1.77
Expand Down
58 changes: 57 additions & 1 deletion ethercrab-wire-derive/benches/derive-struct.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,56 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ethercrab_wire::{EtherCrabWireRead, EtherCrabWireWrite, EtherCrabWireWriteSized};

pub fn normal_types_repr_packed(c: &mut Criterion) {
#[derive(ethercrab_wire::EtherCrabWireReadWrite)]
#[wire(bytes = 16)]
#[repr(C, packed)]
struct NormalTypes {
#[wire(bytes = 4)]
foo: u32,

#[wire(bytes = 2)]
bar: u16,

#[wire(bytes = 2)]
baz: u16,

#[wire(bytes = 8)]
huge: u64,
}

let input_data = [
0xbf, 0x7e, 0xc0, 0x07, 0xab, 0xa1, 0xc2, 0x22, 0x45, 0x23, 0xaa, 0x68, 0x47, 0xbf, 0xff,
0xea,
];

c.bench_function("u* [repr(C,packed)] struct unpack", |b| {
b.iter(|| NormalTypes::unpack_from_slice(black_box(&input_data)))
});

let instance = NormalTypes::unpack_from_slice(&input_data).unwrap();

c.bench_function("u* [repr(C,packed)] struct pack array", |b| {
b.iter(|| black_box(instance.pack()))
});

c.bench_function("u* [repr(C,packed)] struct pack slice unchecked", |b| {
b.iter(|| {
let mut buf = [0u8; 16];

instance.pack_to_slice_unchecked(black_box(&mut buf));
})
});

c.bench_function("u* [repr(C,packed)] struct pack slice checked", |b| {
b.iter(|| {
let mut buf = [0u8; 16];

let _ = instance.pack_to_slice(black_box(&mut buf));
})
});
}

pub fn normal_types(c: &mut Criterion) {
#[derive(ethercrab_wire::EtherCrabWireReadWrite)]
#[wire(bytes = 16)]
Expand Down Expand Up @@ -157,5 +207,11 @@ pub fn packed(c: &mut Criterion) {
});
}

criterion_group!(structs, normal_types, packed_and_normal, packed);
criterion_group!(
structs,
normal_types,
normal_types_repr_packed,
packed_and_normal,
packed
);
criterion_main!(structs);
15 changes: 11 additions & 4 deletions ethercrab-wire-derive/src/generate_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,21 @@ pub fn generate_struct_write(parsed: &StructMeta, input: &DeriveInput) -> proc_m
.ty_name
.unwrap_or_else(|| Ident::new("UnknownTypeStopLookingAtMe", Span::call_site()));

let field_access = if parsed.repr_packed {
quote! {{
unsafe { core::ptr::read_unaligned(&raw const self.#name) }
}}
} else {
quote! {self.#name}
};

// Small optimisation
if ty_name == "u8" || ty_name == "bool" {
let mask = (2u16.pow(field.bits.len() as u32) - 1) << bit_start;
let mask = proc_macro2::TokenStream::from_str(&format!("{:#010b}", mask)).unwrap();

quote! {
buf[#byte_start] |= ((self.#name as u8) << #bit_start) & #mask;
buf[#byte_start] |= ((#field_access as u8) << #bit_start) & #mask;
}
}
// Single byte fields need merging into the other data
Expand All @@ -38,17 +46,16 @@ pub fn generate_struct_write(parsed: &StructMeta, input: &DeriveInput) -> proc_m

quote! {
let mut field_buf = [0u8; 1];
let res = <#field_ty as ::ethercrab_wire::EtherCrabWireWrite>::pack_to_slice_unchecked(&self.#name, &mut field_buf)[0];
let res = <#field_ty as ::ethercrab_wire::EtherCrabWireWrite>::pack_to_slice_unchecked(&#field_access, &mut field_buf)[0];

buf[#byte_start] |= (res << #bit_start) & #mask;
}
}
// Assumption: multi-byte fields are byte-aligned. This should be validated during parse.
else {
let byte_end = field.bytes.end;

quote! {
<#field_ty as ::ethercrab_wire::EtherCrabWireWrite>::pack_to_slice_unchecked(&self.#name, &mut buf[#byte_start..#byte_end]);
<#field_ty as ::ethercrab_wire::EtherCrabWireWrite>::pack_to_slice_unchecked(&#field_access, &mut buf[#byte_start..#byte_end]);
}
}
});
Expand Down
21 changes: 21 additions & 0 deletions ethercrab-wire-derive/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ pub fn attr_exists(attrs: &[syn::Attribute], search: &str) -> bool {
false
}

pub fn has_repr_packed(attrs: &[syn::Attribute]) -> bool {
for attr in attrs {
match attr.meta.clone() {
Meta::List(l) if l.path.is_ident("repr") => {
let mut has_packed = false;
let _ = l.parse_nested_meta(|meta| {
if meta.path.is_ident("packed") {
has_packed = true;
}
Ok(())
});
if has_packed {
return true;
}
}
_ => (),
}
}
false
}

// pub fn field_is_enum_attr(attrs: &[syn::Attribute]) -> Result<bool, syn::Error> {
// for attr in my_attributes(attrs) {
// let Ok(nested) = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
Expand Down
5 changes: 4 additions & 1 deletion ethercrab-wire-derive/src/parse_struct.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::help::{all_valid_attrs, attr_exists, bit_width_attr, usize_attr};
use crate::help::{all_valid_attrs, attr_exists, bit_width_attr, has_repr_packed, usize_attr};
use std::ops::Range;
use syn::{DataStruct, DeriveInput, Fields, FieldsNamed, Ident, Type, Visibility};

#[derive(Clone)]
pub struct StructMeta {
/// Width in bits on the wire.
pub width_bits: usize,
pub repr_packed: bool,

pub fields: Vec<FieldMeta>,
}
Expand Down Expand Up @@ -47,6 +48,7 @@ pub fn parse_struct(
// --- Struct attributes

all_valid_attrs(&attrs, &["bits", "bytes"])?;
let repr_packed = has_repr_packed(&attrs);

let width = bit_width_attr(&attrs)?;

Expand Down Expand Up @@ -182,6 +184,7 @@ pub fn parse_struct(
}

Ok(StructMeta {
repr_packed,
width_bits: width,
fields: field_meta,
})
Expand Down