From 3fd372ece24a80cc91aea02cd9b9b3eb314985f5 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 15 Sep 2026 15:41:32 -0700 Subject: [PATCH] Add support for generating layout tests in a different file Add a new command line flag, `--separate-layout-tests-path`, and a new builder method, `separate_layout_tests_path()`, to set the path for layout tests. Unlike when wrapping static functions, an explicit path is required for the layout tests to be put in a different file, with no fallback to a temporary file. Separate layout tests are expected to be useful in projects where the bindings are included in version control, in which case the `include!` of the generated file would break if that file wasn't also present. Fixes #1655 --- .../expectations/tests/generated/README.md | 2 +- .../generated/layout_align_layout_tests.rs | 26 + .../layout_array_too_long_layout_tests.rs | 56 ++ .../layout_large_align_field_layout_tests.rs | 114 +++ .../tests/generated/namespace_layout_tests.rs | 1 + .../tests/layout_align_separate_tests.rs | 733 ++++++++++++++++++ .../layout_array_too_long_separate_tests.rs | 101 +++ ...layout_large_align_field_separate_tests.rs | 210 +++++ .../headers/layout_align_separate_tests.h | 22 + .../layout_array_too_long_separate_tests.h | 61 ++ .../layout_large_align_field_separate_tests.h | 99 +++ bindgen-tests/tests/tests.rs | 170 +++- bindgen/codegen/mod.rs | 131 +++- bindgen/lib.rs | 28 +- bindgen/options/cli.rs | 6 + bindgen/options/mod.rs | 15 + 16 files changed, 1711 insertions(+), 64 deletions(-) create mode 100644 bindgen-tests/tests/expectations/tests/generated/layout_align_layout_tests.rs create mode 100644 bindgen-tests/tests/expectations/tests/generated/layout_array_too_long_layout_tests.rs create mode 100644 bindgen-tests/tests/expectations/tests/generated/layout_large_align_field_layout_tests.rs create mode 100644 bindgen-tests/tests/expectations/tests/generated/namespace_layout_tests.rs create mode 100644 bindgen-tests/tests/expectations/tests/layout_align_separate_tests.rs create mode 100644 bindgen-tests/tests/expectations/tests/layout_array_too_long_separate_tests.rs create mode 100644 bindgen-tests/tests/expectations/tests/layout_large_align_field_separate_tests.rs create mode 100644 bindgen-tests/tests/headers/layout_align_separate_tests.h create mode 100644 bindgen-tests/tests/headers/layout_array_too_long_separate_tests.h create mode 100644 bindgen-tests/tests/headers/layout_large_align_field_separate_tests.h diff --git a/bindgen-tests/tests/expectations/tests/generated/README.md b/bindgen-tests/tests/expectations/tests/generated/README.md index b4e8cabbf6..04563d1b22 100644 --- a/bindgen-tests/tests/expectations/tests/generated/README.md +++ b/bindgen-tests/tests/expectations/tests/generated/README.md @@ -1,4 +1,4 @@ -# Generated C, C++, Header files +# Generated C, C++, Rust, and Header files This directory contains files for features where extra files are generated as a part of the feature. For example, `--wrap-static-fns`. diff --git a/bindgen-tests/tests/expectations/tests/generated/layout_align_layout_tests.rs b/bindgen-tests/tests/expectations/tests/generated/layout_align_layout_tests.rs new file mode 100644 index 0000000000..ba3222b48c --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/generated/layout_align_layout_tests.rs @@ -0,0 +1,26 @@ +/* layout tests automatically generated by rust-bindgen 0.73.2 */ + +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of rte_kni_fifo"][::std::mem::size_of::() - 16usize]; + ["Alignment of rte_kni_fifo"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: rte_kni_fifo::write"] + [::std::mem::offset_of!(rte_kni_fifo, write) - 0usize]; + ["Offset of field: rte_kni_fifo::read"] + [::std::mem::offset_of!(rte_kni_fifo, read) - 4usize]; + ["Offset of field: rte_kni_fifo::len"] + [::std::mem::offset_of!(rte_kni_fifo, len) - 8usize]; + ["Offset of field: rte_kni_fifo::elem_size"] + [::std::mem::offset_of!(rte_kni_fifo, elem_size) - 12usize]; + ["Offset of field: rte_kni_fifo::buffer"] + [::std::mem::offset_of!(rte_kni_fifo, buffer) - 16usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of rte_eth_link"][::std::mem::size_of::() - 8usize]; + ["Alignment of rte_eth_link"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: rte_eth_link::link_speed"] + [::std::mem::offset_of!(rte_eth_link, link_speed) - 0usize]; +}; diff --git a/bindgen-tests/tests/expectations/tests/generated/layout_array_too_long_layout_tests.rs b/bindgen-tests/tests/expectations/tests/generated/layout_array_too_long_layout_tests.rs new file mode 100644 index 0000000000..5ce31931ff --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/generated/layout_array_too_long_layout_tests.rs @@ -0,0 +1,56 @@ +/* layout tests automatically generated by rust-bindgen 0.73.2 */ + +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag"][::std::mem::size_of::() - 16usize]; + ["Alignment of ip_frag"][::std::mem::align_of::() - 8usize]; + ["Offset of field: ip_frag::ofs"] + [::std::mem::offset_of!(ip_frag, ofs) - 0usize]; + ["Offset of field: ip_frag::len"] + [::std::mem::offset_of!(ip_frag, len) - 2usize]; + ["Offset of field: ip_frag::mb"] + [::std::mem::offset_of!(ip_frag, mb) - 8usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag_key"][::std::mem::size_of::() - 40usize]; + ["Alignment of ip_frag_key"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: ip_frag_key::src_dst"] + [::std::mem::offset_of!(ip_frag_key, src_dst) - 0usize]; + ["Offset of field: ip_frag_key::id"] + [::std::mem::offset_of!(ip_frag_key, id) - 32usize]; + ["Offset of field: ip_frag_key::key_len"] + [::std::mem::offset_of!(ip_frag_key, key_len) - 36usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag_pkt__bindgen_ty_1"] + [::std::mem::size_of::() - 16usize]; + ["Alignment of ip_frag_pkt__bindgen_ty_1"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: ip_frag_pkt__bindgen_ty_1::tqe_next"] + [::std::mem::offset_of!(ip_frag_pkt__bindgen_ty_1, tqe_next) - 0usize]; + ["Offset of field: ip_frag_pkt__bindgen_ty_1::tqe_prev"] + [::std::mem::offset_of!(ip_frag_pkt__bindgen_ty_1, tqe_prev) - 8usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag_pkt"][::std::mem::size_of::() - 192usize]; + ["Alignment of ip_frag_pkt"] + [::std::mem::align_of::() - 64usize]; + ["Offset of field: ip_frag_pkt::lru"] + [::std::mem::offset_of!(ip_frag_pkt, lru) - 0usize]; + ["Offset of field: ip_frag_pkt::key"] + [::std::mem::offset_of!(ip_frag_pkt, key) - 16usize]; + ["Offset of field: ip_frag_pkt::start"] + [::std::mem::offset_of!(ip_frag_pkt, start) - 56usize]; + ["Offset of field: ip_frag_pkt::total_size"] + [::std::mem::offset_of!(ip_frag_pkt, total_size) - 64usize]; + ["Offset of field: ip_frag_pkt::frag_size"] + [::std::mem::offset_of!(ip_frag_pkt, frag_size) - 68usize]; + ["Offset of field: ip_frag_pkt::last_idx"] + [::std::mem::offset_of!(ip_frag_pkt, last_idx) - 72usize]; + ["Offset of field: ip_frag_pkt::frags"] + [::std::mem::offset_of!(ip_frag_pkt, frags) - 80usize]; +}; diff --git a/bindgen-tests/tests/expectations/tests/generated/layout_large_align_field_layout_tests.rs b/bindgen-tests/tests/expectations/tests/generated/layout_large_align_field_layout_tests.rs new file mode 100644 index 0000000000..b42839d035 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/generated/layout_large_align_field_layout_tests.rs @@ -0,0 +1,114 @@ +/* layout tests automatically generated by rust-bindgen 0.73.2 */ + +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag"][::std::mem::size_of::() - 16usize]; + ["Alignment of ip_frag"][::std::mem::align_of::() - 8usize]; + ["Offset of field: ip_frag::ofs"] + [::std::mem::offset_of!(ip_frag, ofs) - 0usize]; + ["Offset of field: ip_frag::len"] + [::std::mem::offset_of!(ip_frag, len) - 2usize]; + ["Offset of field: ip_frag::mb"] + [::std::mem::offset_of!(ip_frag, mb) - 8usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag_key"][::std::mem::size_of::() - 40usize]; + ["Alignment of ip_frag_key"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: ip_frag_key::src_dst"] + [::std::mem::offset_of!(ip_frag_key, src_dst) - 0usize]; + ["Offset of field: ip_frag_key::id"] + [::std::mem::offset_of!(ip_frag_key, id) - 32usize]; + ["Offset of field: ip_frag_key::key_len"] + [::std::mem::offset_of!(ip_frag_key, key_len) - 36usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag_pkt__bindgen_ty_1"] + [::std::mem::size_of::() - 16usize]; + ["Alignment of ip_frag_pkt__bindgen_ty_1"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: ip_frag_pkt__bindgen_ty_1::tqe_next"] + [::std::mem::offset_of!(ip_frag_pkt__bindgen_ty_1, tqe_next) - 0usize]; + ["Offset of field: ip_frag_pkt__bindgen_ty_1::tqe_prev"] + [::std::mem::offset_of!(ip_frag_pkt__bindgen_ty_1, tqe_prev) - 8usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag_pkt"][::std::mem::size_of::() - 192usize]; + ["Alignment of ip_frag_pkt"] + [::std::mem::align_of::() - 64usize]; + ["Offset of field: ip_frag_pkt::lru"] + [::std::mem::offset_of!(ip_frag_pkt, lru) - 0usize]; + ["Offset of field: ip_frag_pkt::key"] + [::std::mem::offset_of!(ip_frag_pkt, key) - 16usize]; + ["Offset of field: ip_frag_pkt::start"] + [::std::mem::offset_of!(ip_frag_pkt, start) - 56usize]; + ["Offset of field: ip_frag_pkt::total_size"] + [::std::mem::offset_of!(ip_frag_pkt, total_size) - 64usize]; + ["Offset of field: ip_frag_pkt::frag_size"] + [::std::mem::offset_of!(ip_frag_pkt, frag_size) - 68usize]; + ["Offset of field: ip_frag_pkt::last_idx"] + [::std::mem::offset_of!(ip_frag_pkt, last_idx) - 72usize]; + ["Offset of field: ip_frag_pkt::frags"] + [::std::mem::offset_of!(ip_frag_pkt, frags) - 80usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_pkt_list"][::std::mem::size_of::() - 16usize]; + ["Alignment of ip_pkt_list"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: ip_pkt_list::tqh_first"] + [::std::mem::offset_of!(ip_pkt_list, tqh_first) - 0usize]; + ["Offset of field: ip_pkt_list::tqh_last"] + [::std::mem::offset_of!(ip_pkt_list, tqh_last) - 8usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of ip_frag_tbl_stat"] + [::std::mem::size_of::() - 64usize]; + ["Alignment of ip_frag_tbl_stat"] + [::std::mem::align_of::() - 64usize]; + ["Offset of field: ip_frag_tbl_stat::find_num"] + [::std::mem::offset_of!(ip_frag_tbl_stat, find_num) - 0usize]; + ["Offset of field: ip_frag_tbl_stat::add_num"] + [::std::mem::offset_of!(ip_frag_tbl_stat, add_num) - 8usize]; + ["Offset of field: ip_frag_tbl_stat::del_num"] + [::std::mem::offset_of!(ip_frag_tbl_stat, del_num) - 16usize]; + ["Offset of field: ip_frag_tbl_stat::reuse_num"] + [::std::mem::offset_of!(ip_frag_tbl_stat, reuse_num) - 24usize]; + ["Offset of field: ip_frag_tbl_stat::fail_total"] + [::std::mem::offset_of!(ip_frag_tbl_stat, fail_total) - 32usize]; + ["Offset of field: ip_frag_tbl_stat::fail_nospace"] + [::std::mem::offset_of!(ip_frag_tbl_stat, fail_nospace) - 40usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of rte_ip_frag_tbl"] + [::std::mem::size_of::() - 128usize]; + ["Alignment of rte_ip_frag_tbl"] + [::std::mem::align_of::() - 64usize]; + ["Offset of field: rte_ip_frag_tbl::max_cycles"] + [::std::mem::offset_of!(rte_ip_frag_tbl, max_cycles) - 0usize]; + ["Offset of field: rte_ip_frag_tbl::entry_mask"] + [::std::mem::offset_of!(rte_ip_frag_tbl, entry_mask) - 8usize]; + ["Offset of field: rte_ip_frag_tbl::max_entries"] + [::std::mem::offset_of!(rte_ip_frag_tbl, max_entries) - 12usize]; + ["Offset of field: rte_ip_frag_tbl::use_entries"] + [::std::mem::offset_of!(rte_ip_frag_tbl, use_entries) - 16usize]; + ["Offset of field: rte_ip_frag_tbl::bucket_entries"] + [::std::mem::offset_of!(rte_ip_frag_tbl, bucket_entries) - 20usize]; + ["Offset of field: rte_ip_frag_tbl::nb_entries"] + [::std::mem::offset_of!(rte_ip_frag_tbl, nb_entries) - 24usize]; + ["Offset of field: rte_ip_frag_tbl::nb_buckets"] + [::std::mem::offset_of!(rte_ip_frag_tbl, nb_buckets) - 28usize]; + ["Offset of field: rte_ip_frag_tbl::last"] + [::std::mem::offset_of!(rte_ip_frag_tbl, last) - 32usize]; + ["Offset of field: rte_ip_frag_tbl::lru"] + [::std::mem::offset_of!(rte_ip_frag_tbl, lru) - 40usize]; + ["Offset of field: rte_ip_frag_tbl::stat"] + [::std::mem::offset_of!(rte_ip_frag_tbl, stat) - 64usize]; + ["Offset of field: rte_ip_frag_tbl::pkt"] + [::std::mem::offset_of!(rte_ip_frag_tbl, pkt) - 128usize]; +}; diff --git a/bindgen-tests/tests/expectations/tests/generated/namespace_layout_tests.rs b/bindgen-tests/tests/expectations/tests/generated/namespace_layout_tests.rs new file mode 100644 index 0000000000..d8a526831a --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/generated/namespace_layout_tests.rs @@ -0,0 +1 @@ +Expected to be empty diff --git a/bindgen-tests/tests/expectations/tests/layout_align_separate_tests.rs b/bindgen-tests/tests/expectations/tests/layout_align_separate_tests.rs new file mode 100644 index 0000000000..ac0e9728e0 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/layout_align_separate_tests.rs @@ -0,0 +1,733 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +include!("generated/layout_align_layout_tests.rs"); +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct __BindgenBitfieldUnit { + storage: Storage, +} +impl __BindgenBitfieldUnit { + #[inline] + pub const fn new(storage: Storage) -> Self { + Self { storage } + } +} +impl __BindgenBitfieldUnit +where + Storage: AsRef<[u8]> + AsMut<[u8]>, +{ + #[inline] + fn extract_bit(byte: u8, index: usize) -> bool { + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + byte & mask == mask + } + #[inline] + pub fn get_bit(&self, index: usize) -> bool { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = self.storage.as_ref()[byte_index]; + Self::extract_bit(byte, index) + } + #[inline] + pub unsafe fn raw_get_bit(this: *const Self, index: usize) -> bool { + debug_assert!(index / 8 < core::mem::size_of::()); + let byte_index = index / 8; + let byte = unsafe { + *(core::ptr::addr_of!((*this).storage) as *const u8) + .offset(byte_index as isize) + }; + Self::extract_bit(byte, index) + } + #[inline] + fn change_bit(byte: u8, index: usize, val: bool) -> u8 { + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + if val { byte | mask } else { byte & !mask } + } + #[inline] + pub fn set_bit(&mut self, index: usize, val: bool) { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = &mut self.storage.as_mut()[byte_index]; + *byte = Self::change_bit(*byte, index, val); + } + #[inline] + pub unsafe fn raw_set_bit(this: *mut Self, index: usize, val: bool) { + debug_assert!(index / 8 < core::mem::size_of::()); + let byte_index = index / 8; + let byte = unsafe { + (core::ptr::addr_of_mut!((*this).storage) as *mut u8) + .offset(byte_index as isize) + }; + unsafe { *byte = Self::change_bit(*byte, index, val) }; + } + #[inline] + pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!( + (bit_offset + (bit_width as usize) + 7) / 8 <= self.storage.as_ref().len(), + ); + if bit_width == 0 { + return 0; + } + let mut val = 0u64; + let storage = self.storage.as_ref(); + let start_byte = bit_offset / 8; + let bit_shift = bit_offset % 8; + let bytes_needed = (bit_width as usize + bit_shift + 7) / 8; + if cfg!(target_endian = "big") { + for i in 0..bytes_needed { + val |= (storage[start_byte + i].reverse_bits() as u64) << (i * 8); + } + } else { + for i in 0..bytes_needed { + val |= (storage[start_byte + i] as u64) << (i * 8); + } + } + val >>= bit_shift; + if bit_width < 64 { + val &= (1u64 << bit_width) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (64 - bit_width as usize); + } + val + } + #[inline] + pub unsafe fn raw_get(this: *const Self, bit_offset: usize, bit_width: u8) -> u64 { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < core::mem::size_of::()); + debug_assert!( + (bit_offset + (bit_width as usize) + 7) / 8 + <= core::mem::size_of::(), + ); + if bit_width == 0 { + return 0; + } + let mut val = 0u64; + let start_byte = bit_offset / 8; + let bit_shift = bit_offset % 8; + let bytes_needed = (bit_width as usize + bit_shift + 7) / 8; + let storage_ptr = unsafe { core::ptr::addr_of!((*this).storage) as *const u8 }; + if cfg!(target_endian = "big") { + for i in 0..bytes_needed { + let byte = unsafe { *storage_ptr.add(start_byte + i) }; + val |= (byte.reverse_bits() as u64) << (i * 8); + } + } else { + for i in 0..bytes_needed { + let byte = unsafe { *storage_ptr.add(start_byte + i) }; + val |= (byte as u64) << (i * 8); + } + } + val >>= bit_shift; + if bit_width < 64 { + val &= (1u64 << bit_width) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (64 - bit_width as usize); + } + val + } + #[inline] + pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!( + (bit_offset + (bit_width as usize) + 7) / 8 <= self.storage.as_ref().len(), + ); + if bit_width == 0 { + return; + } + let mut val = val; + if bit_width < 64 { + val &= (1u64 << bit_width) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (64 - bit_width as usize); + } + let storage = self.storage.as_mut(); + let start_byte = bit_offset / 8; + let bit_shift = bit_offset % 8; + let bytes_needed = (bit_width as usize + bit_shift + 7) / 8; + val <<= bit_shift; + let field_mask = if bit_width as usize + bit_shift >= 64 { + !0u64 << bit_shift + } else { + ((1u64 << bit_width) - 1) << bit_shift + }; + for i in 0..bytes_needed { + let byte_val = (val >> (i * 8)) as u8; + let byte_mask = (field_mask >> (i * 8)) as u8; + if cfg!(target_endian = "big") { + let byte = storage[start_byte + i].reverse_bits(); + let new_byte = (byte & !byte_mask) | (byte_val & byte_mask); + storage[start_byte + i] = new_byte.reverse_bits(); + } else { + storage[start_byte + i] = (storage[start_byte + i] & !byte_mask) + | (byte_val & byte_mask); + } + } + } + #[inline] + pub unsafe fn raw_set(this: *mut Self, bit_offset: usize, bit_width: u8, val: u64) { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < core::mem::size_of::()); + debug_assert!( + (bit_offset + (bit_width as usize) + 7) / 8 + <= core::mem::size_of::(), + ); + if bit_width == 0 { + return; + } + let mut val = val; + if bit_width < 64 { + val &= (1u64 << bit_width) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (64 - bit_width as usize); + } + let start_byte = bit_offset / 8; + let bit_shift = bit_offset % 8; + let bytes_needed = (bit_width as usize + bit_shift + 7) / 8; + val <<= bit_shift; + let field_mask = if bit_width as usize + bit_shift >= 64 { + !0u64 << bit_shift + } else { + ((1u64 << bit_width) - 1) << bit_shift + }; + let storage_ptr = unsafe { core::ptr::addr_of_mut!((*this).storage) as *mut u8 }; + for i in 0..bytes_needed { + let byte_val = (val >> (i * 8)) as u8; + let byte_mask = (field_mask >> (i * 8)) as u8; + let byte_ptr = unsafe { storage_ptr.add(start_byte + i) }; + if cfg!(target_endian = "big") { + let byte = unsafe { (*byte_ptr).reverse_bits() }; + let new_byte = (byte & !byte_mask) | (byte_val & byte_mask); + unsafe { *byte_ptr = new_byte.reverse_bits() }; + } else { + unsafe { *byte_ptr = (*byte_ptr & !byte_mask) | (byte_val & byte_mask) }; + } + } + } +} +/// Const-generic methods for efficient bitfield access when offset and width +/// are known at compile time. +impl __BindgenBitfieldUnit<[u8; N]> { + /// Get a field using const generics for compile-time optimization. + /// Uses native word size operations when the field fits in usize. + #[inline] + pub const fn get_const(&self) -> u64 { + debug_assert!(BIT_WIDTH <= 64); + debug_assert!(BIT_OFFSET / 8 < N); + debug_assert!((BIT_OFFSET + (BIT_WIDTH as usize) + 7) / 8 <= N); + if BIT_WIDTH == 0 { + return 0; + } + let start_byte = BIT_OFFSET / 8; + let bit_shift = BIT_OFFSET % 8; + let bytes_needed = (BIT_WIDTH as usize + bit_shift + 7) / 8; + if BIT_WIDTH as usize + bit_shift <= usize::BITS as usize { + let mut val = 0usize; + if cfg!(target_endian = "big") { + let mut i = 0; + while i < bytes_needed { + val + |= (self.storage[start_byte + i].reverse_bits() as usize) + << (i * 8); + i += 1; + } + } else { + let mut i = 0; + while i < bytes_needed { + val |= (self.storage[start_byte + i] as usize) << (i * 8); + i += 1; + } + } + val >>= bit_shift; + if (BIT_WIDTH as u32) < usize::BITS { + val &= (1usize << BIT_WIDTH) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (usize::BITS as usize - BIT_WIDTH as usize); + } + val as u64 + } else { + let mut val = 0u64; + if cfg!(target_endian = "big") { + let mut i = 0; + while i < bytes_needed { + val + |= (self.storage[start_byte + i].reverse_bits() as u64) + << (i * 8); + i += 1; + } + } else { + let mut i = 0; + while i < bytes_needed { + val |= (self.storage[start_byte + i] as u64) << (i * 8); + i += 1; + } + } + val >>= bit_shift; + if BIT_WIDTH < 64 { + val &= (1u64 << BIT_WIDTH) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (64 - BIT_WIDTH as usize); + } + val + } + } + /// Set a field using const generics for compile-time optimization. + /// Uses native word size operations when the field fits in usize. + #[inline] + pub fn set_const(&mut self, val: u64) { + debug_assert!(BIT_WIDTH <= 64); + debug_assert!(BIT_OFFSET / 8 < N); + debug_assert!((BIT_OFFSET + (BIT_WIDTH as usize) + 7) / 8 <= N); + if BIT_WIDTH == 0 { + return; + } + let start_byte = BIT_OFFSET / 8; + let bit_shift = BIT_OFFSET % 8; + let bytes_needed = (BIT_WIDTH as usize + bit_shift + 7) / 8; + if BIT_WIDTH as usize + bit_shift <= usize::BITS as usize { + let mut val = val as usize; + if (BIT_WIDTH as u32) < usize::BITS { + val &= (1usize << BIT_WIDTH) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (usize::BITS as usize - BIT_WIDTH as usize); + } + val <<= bit_shift; + let field_mask = if BIT_WIDTH as usize + bit_shift >= usize::BITS as usize { + !0usize << bit_shift + } else { + ((1usize << BIT_WIDTH) - 1) << bit_shift + }; + let mut i = 0; + while i < bytes_needed { + let byte_val = (val >> (i * 8)) as u8; + let byte_mask = (field_mask >> (i * 8)) as u8; + if cfg!(target_endian = "big") { + let byte = self.storage[start_byte + i].reverse_bits(); + let new_byte = (byte & !byte_mask) | (byte_val & byte_mask); + self.storage[start_byte + i] = new_byte.reverse_bits(); + } else { + self.storage[start_byte + i] = (self.storage[start_byte + i] + & !byte_mask) | (byte_val & byte_mask); + } + i += 1; + } + } else { + let mut val = val; + if BIT_WIDTH < 64 { + val &= (1u64 << BIT_WIDTH) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (64 - BIT_WIDTH as usize); + } + val <<= bit_shift; + let field_mask = if BIT_WIDTH as usize + bit_shift >= 64 { + !0u64 << bit_shift + } else { + ((1u64 << BIT_WIDTH) - 1) << bit_shift + }; + let mut i = 0; + while i < bytes_needed { + let byte_val = (val >> (i * 8)) as u8; + let byte_mask = (field_mask >> (i * 8)) as u8; + if cfg!(target_endian = "big") { + let byte = self.storage[start_byte + i].reverse_bits(); + let new_byte = (byte & !byte_mask) | (byte_val & byte_mask); + self.storage[start_byte + i] = new_byte.reverse_bits(); + } else { + self.storage[start_byte + i] = (self.storage[start_byte + i] + & !byte_mask) | (byte_val & byte_mask); + } + i += 1; + } + } + } + /// Raw pointer get using const generics for compile-time optimization. + /// Uses native word size operations when the field fits in usize. + #[inline] + pub const unsafe fn raw_get_const( + this: *const Self, + ) -> u64 { + debug_assert!(BIT_WIDTH <= 64); + debug_assert!(BIT_OFFSET / 8 < N); + debug_assert!((BIT_OFFSET + (BIT_WIDTH as usize) + 7) / 8 <= N); + if BIT_WIDTH == 0 { + return 0; + } + let start_byte = BIT_OFFSET / 8; + let bit_shift = BIT_OFFSET % 8; + let bytes_needed = (BIT_WIDTH as usize + bit_shift + 7) / 8; + let storage_ptr = unsafe { core::ptr::addr_of!((*this).storage) as *const u8 }; + if BIT_WIDTH as usize + bit_shift <= usize::BITS as usize { + let mut val = 0usize; + if cfg!(target_endian = "big") { + let mut i = 0; + while i < bytes_needed { + let byte = unsafe { *storage_ptr.add(start_byte + i) }; + val |= (byte.reverse_bits() as usize) << (i * 8); + i += 1; + } + } else { + let mut i = 0; + while i < bytes_needed { + let byte = unsafe { *storage_ptr.add(start_byte + i) }; + val |= (byte as usize) << (i * 8); + i += 1; + } + } + val >>= bit_shift; + if (BIT_WIDTH as u32) < usize::BITS { + val &= (1usize << BIT_WIDTH) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (usize::BITS as usize - BIT_WIDTH as usize); + } + val as u64 + } else { + let mut val = 0u64; + if cfg!(target_endian = "big") { + let mut i = 0; + while i < bytes_needed { + let byte = unsafe { *storage_ptr.add(start_byte + i) }; + val |= (byte.reverse_bits() as u64) << (i * 8); + i += 1; + } + } else { + let mut i = 0; + while i < bytes_needed { + let byte = unsafe { *storage_ptr.add(start_byte + i) }; + val |= (byte as u64) << (i * 8); + i += 1; + } + } + val >>= bit_shift; + if BIT_WIDTH < 64 { + val &= (1u64 << BIT_WIDTH) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (64 - BIT_WIDTH as usize); + } + val + } + } + /// Raw pointer set using const generics for compile-time optimization. + /// Uses native word size operations when the field fits in usize. + #[inline] + pub unsafe fn raw_set_const( + this: *mut Self, + val: u64, + ) { + debug_assert!(BIT_WIDTH <= 64); + debug_assert!(BIT_OFFSET / 8 < N); + debug_assert!((BIT_OFFSET + (BIT_WIDTH as usize) + 7) / 8 <= N); + if BIT_WIDTH == 0 { + return; + } + let start_byte = BIT_OFFSET / 8; + let bit_shift = BIT_OFFSET % 8; + let bytes_needed = (BIT_WIDTH as usize + bit_shift + 7) / 8; + let storage_ptr = this.cast::<[u8; N]>().cast::(); + if BIT_WIDTH as usize + bit_shift <= usize::BITS as usize { + let mut val = val as usize; + if (BIT_WIDTH as u32) < usize::BITS { + val &= (1usize << BIT_WIDTH) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (usize::BITS as usize - BIT_WIDTH as usize); + } + val <<= bit_shift; + let field_mask = if BIT_WIDTH as usize + bit_shift >= usize::BITS as usize { + !0usize << bit_shift + } else { + ((1usize << BIT_WIDTH) - 1) << bit_shift + }; + let mut i = 0; + while i < bytes_needed { + let byte_val = (val >> (i * 8)) as u8; + let byte_mask = (field_mask >> (i * 8)) as u8; + let byte_ptr = unsafe { storage_ptr.add(start_byte + i) }; + if cfg!(target_endian = "big") { + let byte = unsafe { (*byte_ptr).reverse_bits() }; + let new_byte = (byte & !byte_mask) | (byte_val & byte_mask); + unsafe { *byte_ptr = new_byte.reverse_bits() }; + } else { + unsafe { + *byte_ptr = (*byte_ptr & !byte_mask) | (byte_val & byte_mask) + }; + } + i += 1; + } + } else { + let mut val = val; + if BIT_WIDTH < 64 { + val &= (1u64 << BIT_WIDTH) - 1; + } + if cfg!(target_endian = "big") { + val = val.reverse_bits() >> (64 - BIT_WIDTH as usize); + } + val <<= bit_shift; + let field_mask = if BIT_WIDTH as usize + bit_shift >= 64 { + !0u64 << bit_shift + } else { + ((1u64 << BIT_WIDTH) - 1) << bit_shift + }; + let mut i = 0; + while i < bytes_needed { + let byte_val = (val >> (i * 8)) as u8; + let byte_mask = (field_mask >> (i * 8)) as u8; + let byte_ptr = unsafe { storage_ptr.add(start_byte + i) }; + if cfg!(target_endian = "big") { + let byte = unsafe { (*byte_ptr).reverse_bits() }; + let new_byte = (byte & !byte_mask) | (byte_val & byte_mask); + unsafe { *byte_ptr = new_byte.reverse_bits() }; + } else { + unsafe { + *byte_ptr = (*byte_ptr & !byte_mask) | (byte_val & byte_mask) + }; + } + i += 1; + } + } + } +} +#[repr(C)] +#[derive(Default)] +pub struct __IncompleteArrayField(::std::marker::PhantomData, [T; 0]); +impl __IncompleteArrayField { + #[inline] + pub const fn new() -> Self { + __IncompleteArrayField(::std::marker::PhantomData, []) + } + #[inline] + pub fn as_ptr(&self) -> *const T { + self as *const _ as *const T + } + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut T { + self as *mut _ as *mut T + } + #[inline] + pub unsafe fn as_slice(&self, len: usize) -> &[T] { + ::std::slice::from_raw_parts(self.as_ptr(), len) + } + #[inline] + pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { + ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) + } +} +impl ::std::fmt::Debug for __IncompleteArrayField { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + fmt.write_str("__IncompleteArrayField") + } +} +#[repr(C)] +#[derive(Debug)] +pub struct rte_kni_fifo { + ///< Next position to be written + pub write: ::std::os::raw::c_uint, + ///< Next position to be read + pub read: ::std::os::raw::c_uint, + ///< Circular buffer length + pub len: ::std::os::raw::c_uint, + ///< Pointer size - for 32/64 bit OS + pub elem_size: ::std::os::raw::c_uint, + ///< The buffer contains mbuf pointers + pub buffer: __IncompleteArrayField<*mut ::std::os::raw::c_void>, +} +impl Default for rte_kni_fifo { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct rte_eth_link { + pub _bindgen_align: [u64; 0], + ///< ETH_SPEED_NUM_ + pub link_speed: u32, + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, + pub __bindgen_padding_0: [u8; 3usize], +} +impl rte_eth_link { + #[inline] + #[allow(unnecessary_transmutes)] + pub fn link_duplex(&self) -> u16 { + unsafe { + ::std::mem::transmute(self._bitfield_1.get_const::<0usize, 1u8>() as u16) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub fn set_link_duplex(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set_const::<0usize, 1u8>(val as u64) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub unsafe fn link_duplex_raw(this: *const Self) -> u16 { + unsafe { + ::std::mem::transmute( + <__BindgenBitfieldUnit< + [u8; 1usize], + >>::raw_get_const::< + 0usize, + 1u8, + >(::std::ptr::addr_of!((*this)._bitfield_1)) as u16, + ) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub unsafe fn set_link_duplex_raw(this: *mut Self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit< + [u8; 1usize], + >>::raw_set_const::< + 0usize, + 1u8, + >(::std::ptr::addr_of_mut!((*this)._bitfield_1), val as u64) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub fn link_autoneg(&self) -> u16 { + unsafe { + ::std::mem::transmute(self._bitfield_1.get_const::<1usize, 1u8>() as u16) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub fn set_link_autoneg(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set_const::<1usize, 1u8>(val as u64) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub unsafe fn link_autoneg_raw(this: *const Self) -> u16 { + unsafe { + ::std::mem::transmute( + <__BindgenBitfieldUnit< + [u8; 1usize], + >>::raw_get_const::< + 1usize, + 1u8, + >(::std::ptr::addr_of!((*this)._bitfield_1)) as u16, + ) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub unsafe fn set_link_autoneg_raw(this: *mut Self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit< + [u8; 1usize], + >>::raw_set_const::< + 1usize, + 1u8, + >(::std::ptr::addr_of_mut!((*this)._bitfield_1), val as u64) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub fn link_status(&self) -> u16 { + unsafe { + ::std::mem::transmute(self._bitfield_1.get_const::<2usize, 1u8>() as u16) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub fn set_link_status(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set_const::<2usize, 1u8>(val as u64) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub unsafe fn link_status_raw(this: *const Self) -> u16 { + unsafe { + ::std::mem::transmute( + <__BindgenBitfieldUnit< + [u8; 1usize], + >>::raw_get_const::< + 2usize, + 1u8, + >(::std::ptr::addr_of!((*this)._bitfield_1)) as u16, + ) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub unsafe fn set_link_status_raw(this: *mut Self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit< + [u8; 1usize], + >>::raw_set_const::< + 2usize, + 1u8, + >(::std::ptr::addr_of_mut!((*this)._bitfield_1), val as u64) + } + } + #[inline] + #[allow(unnecessary_transmutes)] + pub fn new_bitfield_1( + link_duplex: u16, + link_autoneg: u16, + link_status: u16, + ) -> __BindgenBitfieldUnit<[u8; 1usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default(); + __bindgen_bitfield_unit + .set_const::< + 0usize, + 1u8, + >({ + let link_duplex: u16 = unsafe { ::std::mem::transmute(link_duplex) }; + link_duplex as u64 + }); + __bindgen_bitfield_unit + .set_const::< + 1usize, + 1u8, + >({ + let link_autoneg: u16 = unsafe { ::std::mem::transmute(link_autoneg) }; + link_autoneg as u64 + }); + __bindgen_bitfield_unit + .set_const::< + 2usize, + 1u8, + >({ + let link_status: u16 = unsafe { ::std::mem::transmute(link_status) }; + link_status as u64 + }); + __bindgen_bitfield_unit + } +} diff --git a/bindgen-tests/tests/expectations/tests/layout_array_too_long_separate_tests.rs b/bindgen-tests/tests/expectations/tests/layout_array_too_long_separate_tests.rs new file mode 100644 index 0000000000..29272cee64 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/layout_array_too_long_separate_tests.rs @@ -0,0 +1,101 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +include!("generated/layout_array_too_long_layout_tests.rs"); +pub const RTE_CACHE_LINE_SIZE: u32 = 64; +pub const RTE_LIBRTE_IP_FRAG_MAX_FRAG: u32 = 4; +pub const IP_LAST_FRAG_IDX: _bindgen_ty_1 = _bindgen_ty_1::IP_LAST_FRAG_IDX; +pub const IP_FIRST_FRAG_IDX: _bindgen_ty_1 = _bindgen_ty_1::IP_FIRST_FRAG_IDX; +pub const IP_MIN_FRAG_NUM: _bindgen_ty_1 = _bindgen_ty_1::IP_MIN_FRAG_NUM; +pub const IP_MAX_FRAG_NUM: _bindgen_ty_1 = _bindgen_ty_1::IP_MAX_FRAG_NUM; +#[repr(u32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum _bindgen_ty_1 { + ///< index of last fragment + IP_LAST_FRAG_IDX = 0, + ///< index of first fragment + IP_FIRST_FRAG_IDX = 1, + ///< minimum number of fragments + IP_MIN_FRAG_NUM = 2, + IP_MAX_FRAG_NUM = 4, +} +/// @internal fragmented mbuf +#[repr(C)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct ip_frag { + ///< offset into the packet + pub ofs: u16, + ///< length of fragment + pub len: u16, + ///< fragment mbuf + pub mb: *mut rte_mbuf, +} +impl Default for ip_frag { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +/// @internal to uniquely indetify fragmented datagram. +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)] +pub struct ip_frag_key { + ///< src address, first 8 bytes used for IPv4 + pub src_dst: [u64; 4usize], + ///< dst address + pub id: u32, + ///< src/dst key length + pub key_len: u32, +} +/** @internal Fragmented packet to reassemble. + First two entries in the frags[] array are for the last and first fragments.*/ +#[repr(C)] +#[repr(align(64))] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct ip_frag_pkt { + ///< LRU list + pub lru: ip_frag_pkt__bindgen_ty_1, + ///< fragmentation key + pub key: ip_frag_key, + ///< creation timestamp + pub start: u64, + ///< expected reassembled size + pub total_size: u32, + ///< size of fragments received + pub frag_size: u32, + ///< index of next entry to fill + pub last_idx: u32, + ///< fragments + pub frags: [ip_frag; 4usize], +} +#[repr(C)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct ip_frag_pkt__bindgen_ty_1 { + pub tqe_next: *mut ip_frag_pkt, + pub tqe_prev: *mut *mut ip_frag_pkt, +} +impl Default for ip_frag_pkt__bindgen_ty_1 { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +impl Default for ip_frag_pkt { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +///< fragment mbuf +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)] +pub struct rte_mbuf { + pub _address: u8, +} diff --git a/bindgen-tests/tests/expectations/tests/layout_large_align_field_separate_tests.rs b/bindgen-tests/tests/expectations/tests/layout_large_align_field_separate_tests.rs new file mode 100644 index 0000000000..53171b84a8 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/layout_large_align_field_separate_tests.rs @@ -0,0 +1,210 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +include!("generated/layout_large_align_field_layout_tests.rs"); +#[repr(C)] +#[derive(Default)] +pub struct __IncompleteArrayField(::std::marker::PhantomData, [T; 0]); +impl __IncompleteArrayField { + #[inline] + pub const fn new() -> Self { + __IncompleteArrayField(::std::marker::PhantomData, []) + } + #[inline] + pub fn as_ptr(&self) -> *const T { + self as *const _ as *const T + } + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut T { + self as *mut _ as *mut T + } + #[inline] + pub unsafe fn as_slice(&self, len: usize) -> &[T] { + ::std::slice::from_raw_parts(self.as_ptr(), len) + } + #[inline] + pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { + ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) + } +} +impl ::std::fmt::Debug for __IncompleteArrayField { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + fmt.write_str("__IncompleteArrayField") + } +} +pub const RTE_CACHE_LINE_SIZE: u32 = 64; +pub const RTE_LIBRTE_IP_FRAG_MAX_FRAG: u32 = 4; +pub const IP_LAST_FRAG_IDX: _bindgen_ty_1 = _bindgen_ty_1::IP_LAST_FRAG_IDX; +pub const IP_FIRST_FRAG_IDX: _bindgen_ty_1 = _bindgen_ty_1::IP_FIRST_FRAG_IDX; +pub const IP_MIN_FRAG_NUM: _bindgen_ty_1 = _bindgen_ty_1::IP_MIN_FRAG_NUM; +pub const IP_MAX_FRAG_NUM: _bindgen_ty_1 = _bindgen_ty_1::IP_MAX_FRAG_NUM; +#[repr(u32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum _bindgen_ty_1 { + ///< index of last fragment + IP_LAST_FRAG_IDX = 0, + ///< index of first fragment + IP_FIRST_FRAG_IDX = 1, + ///< minimum number of fragments + IP_MIN_FRAG_NUM = 2, + IP_MAX_FRAG_NUM = 4, +} +/// @internal fragmented mbuf +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ip_frag { + ///< offset into the packet + pub ofs: u16, + ///< length of fragment + pub len: u16, + ///< fragment mbuf + pub mb: *mut rte_mbuf, +} +impl Default for ip_frag { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +/// @internal to uniquely indetify fragmented datagram. +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct ip_frag_key { + ///< src address, first 8 bytes used for IPv4 + pub src_dst: [u64; 4usize], + ///< dst address + pub id: u32, + ///< src/dst key length + pub key_len: u32, +} +/** @internal Fragmented packet to reassemble. + First two entries in the frags[] array are for the last and first fragments.*/ +#[repr(C)] +#[repr(align(64))] +#[derive(Debug, Copy, Clone)] +pub struct ip_frag_pkt { + ///< LRU list + pub lru: ip_frag_pkt__bindgen_ty_1, + ///< fragmentation key + pub key: ip_frag_key, + ///< creation timestamp + pub start: u64, + ///< expected reassembled size + pub total_size: u32, + ///< size of fragments received + pub frag_size: u32, + ///< index of next entry to fill + pub last_idx: u32, + ///< fragments + pub frags: [ip_frag; 4usize], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ip_frag_pkt__bindgen_ty_1 { + pub tqe_next: *mut ip_frag_pkt, + pub tqe_prev: *mut *mut ip_frag_pkt, +} +impl Default for ip_frag_pkt__bindgen_ty_1 { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +impl Default for ip_frag_pkt { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ip_pkt_list { + pub tqh_first: *mut ip_frag_pkt, + pub tqh_last: *mut *mut ip_frag_pkt, +} +impl Default for ip_pkt_list { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +/// fragmentation table statistics +#[repr(C)] +#[repr(align(64))] +#[derive(Debug, Copy, Clone)] +pub struct ip_frag_tbl_stat { + ///< total # of find/insert attempts. + pub find_num: u64, + ///< # of add ops. + pub add_num: u64, + ///< # of del ops. + pub del_num: u64, + ///< # of reuse (del/add) ops. + pub reuse_num: u64, + ///< total # of add failures. + pub fail_total: u64, + ///< # of 'no space' add failures. + pub fail_nospace: u64, +} +impl Default for ip_frag_tbl_stat { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +/// fragmentation table +#[repr(C)] +#[repr(align(64))] +#[derive(Debug)] +pub struct rte_ip_frag_tbl { + ///< ttl for table entries. + pub max_cycles: u64, + ///< hash value mask. + pub entry_mask: u32, + ///< max entries allowed. + pub max_entries: u32, + ///< entries in use. + pub use_entries: u32, + ///< hash assocaitivity. + pub bucket_entries: u32, + ///< total size of the table. + pub nb_entries: u32, + ///< num of associativity lines. + pub nb_buckets: u32, + ///< last used entry. + pub last: *mut ip_frag_pkt, + ///< LRU list for table entries. + pub lru: ip_pkt_list, + ///< statistics counters. + pub stat: ip_frag_tbl_stat, + ///< hash table. + pub pkt: __IncompleteArrayField, +} +impl Default for rte_ip_frag_tbl { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +///< fragment mbuf +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct rte_mbuf { + pub _address: u8, +} diff --git a/bindgen-tests/tests/headers/layout_align_separate_tests.h b/bindgen-tests/tests/headers/layout_align_separate_tests.h new file mode 100644 index 0000000000..52ac5d5bab --- /dev/null +++ b/bindgen-tests/tests/headers/layout_align_separate_tests.h @@ -0,0 +1,22 @@ +// bindgen-flags: --separate-layout-tests-path generated/layout_align_layout_tests.rs + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +struct rte_kni_fifo { + volatile unsigned write; /**< Next position to be written*/ + volatile unsigned read; /**< Next position to be read */ + unsigned len; /**< Circular buffer length */ + unsigned elem_size; /**< Pointer size - for 32/64 bit OS */ + void *volatile buffer[]; /**< The buffer contains mbuf pointers */ +}; + +__extension__ +struct rte_eth_link { + uint32_t link_speed; /**< ETH_SPEED_NUM_ */ + uint16_t link_duplex : 1; /**< ETH_LINK_[HALF/FULL]_DUPLEX */ + uint16_t link_autoneg : 1; /**< ETH_LINK_SPEED_[AUTONEG/FIXED] */ + uint16_t link_status : 1; /**< ETH_LINK_[DOWN/UP] */ +} __attribute__((aligned(8))); /**< aligned for atomic64 read/write */ \ No newline at end of file diff --git a/bindgen-tests/tests/headers/layout_array_too_long_separate_tests.h b/bindgen-tests/tests/headers/layout_array_too_long_separate_tests.h new file mode 100644 index 0000000000..b1f2d9351a --- /dev/null +++ b/bindgen-tests/tests/headers/layout_array_too_long_separate_tests.h @@ -0,0 +1,61 @@ +// bindgen-flags: --with-derive-hash --with-derive-partialeq --with-derive-eq --impl-partialeq --rustified-enum ".*" --separate-layout-tests-path generated/layout_array_too_long_layout_tests.rs +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +#define RTE_CACHE_LINE_SIZE 64 + +/** + * Force alignment + */ +#define __rte_aligned(a) __attribute__((__aligned__(a))) + +/** + * Force alignment to cache line. + */ +#define __rte_cache_aligned __rte_aligned(RTE_CACHE_LINE_SIZE) + +#define RTE_LIBRTE_IP_FRAG_MAX_FRAG 4 + +enum { + IP_LAST_FRAG_IDX, /**< index of last fragment */ + IP_FIRST_FRAG_IDX, /**< index of first fragment */ + IP_MIN_FRAG_NUM, /**< minimum number of fragments */ + IP_MAX_FRAG_NUM = RTE_LIBRTE_IP_FRAG_MAX_FRAG, + /**< maximum number of fragments per packet */ +}; + +/** @internal fragmented mbuf */ +struct ip_frag { + uint16_t ofs; /**< offset into the packet */ + uint16_t len; /**< length of fragment */ + struct rte_mbuf *mb; /**< fragment mbuf */ +}; + +/** @internal to uniquely indetify fragmented datagram. */ +struct ip_frag_key { + uint64_t src_dst[4]; /**< src address, first 8 bytes used for IPv4 */ + uint32_t id; /**< dst address */ + uint32_t key_len; /**< src/dst key length */ +}; + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/** + * @internal Fragmented packet to reassemble. + * First two entries in the frags[] array are for the last and first fragments. + */ +struct ip_frag_pkt { + TAILQ_ENTRY(ip_frag_pkt) lru; /**< LRU list */ + struct ip_frag_key key; /**< fragmentation key */ + uint64_t start; /**< creation timestamp */ + uint32_t total_size; /**< expected reassembled size */ + uint32_t frag_size; /**< size of fragments received */ + uint32_t last_idx; /**< index of next entry to fill */ + struct ip_frag frags[IP_MAX_FRAG_NUM]; /**< fragments */ +} __rte_cache_aligned; diff --git a/bindgen-tests/tests/headers/layout_large_align_field_separate_tests.h b/bindgen-tests/tests/headers/layout_large_align_field_separate_tests.h new file mode 100644 index 0000000000..90fcd06b1d --- /dev/null +++ b/bindgen-tests/tests/headers/layout_large_align_field_separate_tests.h @@ -0,0 +1,99 @@ +// bindgen-flags: --rustified-enum ".*" --separate-layout-tests-path generated/layout_large_align_field_layout_tests.rs + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +#define RTE_CACHE_LINE_SIZE 64 + +/** + * Force alignment + */ +#define __rte_aligned(a) __attribute__((__aligned__(a))) + +/** + * Force alignment to cache line. + */ +#define __rte_cache_aligned __rte_aligned(RTE_CACHE_LINE_SIZE) + +#define RTE_LIBRTE_IP_FRAG_MAX_FRAG 4 + +enum { + IP_LAST_FRAG_IDX, /**< index of last fragment */ + IP_FIRST_FRAG_IDX, /**< index of first fragment */ + IP_MIN_FRAG_NUM, /**< minimum number of fragments */ + IP_MAX_FRAG_NUM = RTE_LIBRTE_IP_FRAG_MAX_FRAG, + /**< maximum number of fragments per packet */ +}; + +/** @internal fragmented mbuf */ +struct ip_frag { + uint16_t ofs; /**< offset into the packet */ + uint16_t len; /**< length of fragment */ + struct rte_mbuf *mb; /**< fragment mbuf */ +}; + +/** @internal to uniquely indetify fragmented datagram. */ +struct ip_frag_key { + uint64_t src_dst[4]; /**< src address, first 8 bytes used for IPv4 */ + uint32_t id; /**< dst address */ + uint32_t key_len; /**< src/dst key length */ +}; + +/* + * Tail queue declarations. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/** + * @internal Fragmented packet to reassemble. + * First two entries in the frags[] array are for the last and first fragments. + */ +struct ip_frag_pkt { + TAILQ_ENTRY(ip_frag_pkt) lru; /**< LRU list */ + struct ip_frag_key key; /**< fragmentation key */ + uint64_t start; /**< creation timestamp */ + uint32_t total_size; /**< expected reassembled size */ + uint32_t frag_size; /**< size of fragments received */ + uint32_t last_idx; /**< index of next entry to fill */ + struct ip_frag frags[IP_MAX_FRAG_NUM]; /**< fragments */ +} __rte_cache_aligned; + +TAILQ_HEAD(ip_pkt_list, ip_frag_pkt); /**< @internal fragments tailq */ + +/** fragmentation table statistics */ +struct ip_frag_tbl_stat { + uint64_t find_num; /**< total # of find/insert attempts. */ + uint64_t add_num; /**< # of add ops. */ + uint64_t del_num; /**< # of del ops. */ + uint64_t reuse_num; /**< # of reuse (del/add) ops. */ + uint64_t fail_total; /**< total # of add failures. */ + uint64_t fail_nospace; /**< # of 'no space' add failures. */ +} __rte_cache_aligned; + +/** fragmentation table */ +struct rte_ip_frag_tbl { + uint64_t max_cycles; /**< ttl for table entries. */ + uint32_t entry_mask; /**< hash value mask. */ + uint32_t max_entries; /**< max entries allowed. */ + uint32_t use_entries; /**< entries in use. */ + uint32_t bucket_entries; /**< hash assocaitivity. */ + uint32_t nb_entries; /**< total size of the table. */ + uint32_t nb_buckets; /**< num of associativity lines. */ + struct ip_frag_pkt *last; /**< last used entry. */ + struct ip_pkt_list lru; /**< LRU list for table entries. */ + struct ip_frag_tbl_stat stat; /**< statistics counters. */ + __extension__ struct ip_frag_pkt pkt[0]; /**< hash table. */ +}; diff --git a/bindgen-tests/tests/tests.rs b/bindgen-tests/tests/tests.rs index 2b9bf15a16..9011503d25 100644 --- a/bindgen-tests/tests/tests.rs +++ b/bindgen-tests/tests/tests.rs @@ -216,6 +216,19 @@ fn compare_generated_header( Err(_) => "/* error generating bindings */\n".into(), }; + // Generated layout tests are tested separately, for test cases that + // specify that the layout tests should be put in a different file they + // create this directory + if Path::new("generated/").exists() { + match fs::remove_dir_all("generated/") { + Ok(_) => (), + // Support race conditions when running `cargo test` and another + // thread already deleted it + Err(e) if e.kind() == ErrorKind::NotFound => (), + Err(e) => return Err(e), + } + } + if actual.is_empty() { return Err(Error::new( ErrorKind::Other, @@ -803,58 +816,137 @@ fn commandline_multiple_headers() { build_flags_output_helper(&bindings); } +fn test_with_extra_generated_file( + expect_file: &str, + generated_file: Option<&str>, + builder_cb: F, +) where + F: Fn(&PathBuf) -> Builder, +{ + let expect_path = + PathBuf::from("tests/expectations/tests/generated").join(expect_file); + println!("In path is ::: {}", expect_path.display()); + + let generated_path = PathBuf::from(env::var("OUT_DIR").unwrap()) + .join(generated_file.unwrap_or(expect_file)); + println!("Out path is ::: {}", generated_path.display()); + + let builder = builder_cb(&generated_path); + + builder.generate().expect("Failed to generate bindings"); + + let expected_code = fs::read_to_string(&expect_path) + .expect("Could not read generated file"); + + let actual_code = match generated_file { + Some(_) => fs::read_to_string(generated_path) + .expect("Could not read actual file"), + None => "Expected to be empty\n".into(), + }; + + if expected_code != actual_code { + error_diff_mismatch(&actual_code, &expected_code, None, &expect_path) + .unwrap(); + } +} + #[test] fn test_wrap_static_fns() { // This test is for testing diffs of the generated C source and header files - // TODO: If another such feature is added, convert this test into a more generic - // test that looks at `tests/headers/generated` directory. // aarch64-linux has a bug, remove custom source when it is solved: // https://github.com/rust-lang/rust-bindgen/issues/3234 let wrap_static_fns_c_name = if cfg!(all(target_arch = "aarch64", target_os = "linux")) { - "wrap_static_fns_aarch64_linux" + "wrap_static_fns_aarch64_linux.c" } else { - "wrap_static_fns" + "wrap_static_fns.c" }; - let expect_path = PathBuf::from("tests/expectations/tests/generated") - .join(wrap_static_fns_c_name); - println!("In path is ::: {}", expect_path.display()); - - let generated_path = - PathBuf::from(env::var("OUT_DIR").unwrap()).join("wrap_static_fns"); - println!("Out path is ::: {}", generated_path.display()); - - #[allow(unused_mut)] - let mut builder = Builder::default() - .header("tests/headers/wrap-static-fns.h") - .wrap_static_fns(true) - .wrap_static_fns_path(generated_path.display().to_string()) - .parse_callbacks(Box::new(parse_callbacks::WrapAsVariadicFn)); - - // aarch64-linux has a bug, remove when it is solved: - // https://github.com/rust-lang/rust-bindgen/issues/3234 - #[cfg(all(target_arch = "aarch64", target_os = "linux"))] - { - builder = builder.clang_arg("-DDISABLE_VA"); - } + test_with_extra_generated_file( + wrap_static_fns_c_name, + Some("wrap_static_fns.c"), + |generated_path| { + #[allow(unused_mut)] + let mut builder = Builder::default() + .header("tests/headers/wrap-static-fns.h") + .wrap_static_fns(true) + .wrap_static_fns_path(generated_path.display().to_string()) + .parse_callbacks(Box::new(parse_callbacks::WrapAsVariadicFn)); + + // aarch64-linux has a bug, remove when it is solved: + // https://github.com/rust-lang/rust-bindgen/issues/3234 + #[cfg(all(target_arch = "aarch64", target_os = "linux"))] + { + builder = builder.clang_arg("-DDISABLE_VA"); + } + builder + }, + ); +} - builder.generate().expect("Failed to generate bindings"); +#[test] +fn test_cpp_layout_tests() { + test_with_extra_generated_file( + "namespace_layout_tests.rs", + // None - C++ modules shouldn't create separate layout tests + None, + |generated_path| { + Builder::default() + .header("tests/headers/namespace.hpp") + .enable_cxx_namespaces() + .module_raw_line( + "root::whatever", + "pub type whatever_other_thing_t = whatever_int_t;", + ) + .separate_layout_tests_path(Some(generated_path)) + }, + ); +} - let expected_c = fs::read_to_string(expect_path.with_extension("c")) - .expect("Could not read generated wrap_static_fns.c"); +#[test] +fn test_layout_align_layout_tests() { + test_with_extra_generated_file( + "layout_align_layout_tests.rs", + Some("layout_align_layout_tests.rs"), + |generated_path| { + Builder::default() + .header("tests/headers/layout_align_separate_tests.h") + .separate_layout_tests_path(Some(generated_path)) + }, + ); +} - let actual_c = fs::read_to_string(generated_path.with_extension("c")) - .expect("Could not read actual wrap_static_fns.c"); +#[test] +fn test_layout_array_too_long_layout_tests() { + test_with_extra_generated_file( + "layout_array_too_long_layout_tests.rs", + Some("layout_array_too_long_layout_tests.rs"), + |generated_path| { + Builder::default() + .header("tests/headers/layout_array_too_long_separate_tests.h") + .derive_hash(true) + .derive_partialeq(true) + .derive_eq(true) + .impl_partialeq(true) + .rustified_enum(".*") + .separate_layout_tests_path(Some(generated_path)) + }, + ); +} - if expected_c != actual_c { - error_diff_mismatch( - &actual_c, - &expected_c, - None, - &expect_path.with_extension("c"), - ) - .unwrap(); - } +#[test] +fn test_layout_align_field_layout_tests() { + test_with_extra_generated_file( + "layout_large_align_field_layout_tests.rs", + Some("layout_large_align_field_layout_tests.rs"), + |generated_path| { + Builder::default() + .header( + "tests/headers/layout_large_align_field_separate_tests.h", + ) + .rustified_enum(".*") + .separate_layout_tests_path(Some(generated_path)) + }, + ); } diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 26a5de00c7..a953d78e53 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -323,10 +323,17 @@ struct CodegenResult<'a> { /// List of items to serialize. With optionally the argument for the wrap as /// variadic transformation to be applied. items_to_serialize: Vec<(ItemId, Option)>, + + /// Whether layout test items should be tracked separately + separate_layout_tests: bool, + + /// Items to add to layout tests, only used when `separate_layout_tests` is + /// true. + layout_test_items: Vec, } impl<'a> CodegenResult<'a> { - fn new(codegen_id: &'a Cell) -> Self { + fn new(codegen_id: &'a Cell, separate_layout_tests: bool) -> Self { CodegenResult { items: vec![], dynamic_items: DynamicItems::new(), @@ -341,6 +348,8 @@ impl<'a> CodegenResult<'a> { vars_seen: Default::default(), overload_counters: Default::default(), items_to_serialize: Default::default(), + separate_layout_tests, + layout_test_items: Default::default(), } } @@ -402,11 +411,15 @@ impl<'a> CodegenResult<'a> { self.vars_seen.insert(name.into()); } + /// For inner module codegen, layout tests are included in the same file, + /// so that we don't need to deal with resolving the type names in those + /// tests. Supporting separate layout tests for CPP modules is left for a + /// future improvement. fn inner(&mut self, cb: F) -> Vec where F: FnOnce(&mut Self), { - let mut new = Self::new(self.codegen_id); + let mut new = Self::new(self.codegen_id, false); cb(&mut new); @@ -416,8 +429,22 @@ impl<'a> CodegenResult<'a> { self.saw_bitfield_unit |= new.saw_bitfield_unit; self.saw_bindgen_union |= new.saw_bindgen_union; + assert!( + new.layout_test_items.is_empty(), + "Layout tests are in the main items" + ); + new.items } + + /// Add a new layout test, either to `self.items` or `self.layout_test_items` + fn push_layout_test(&mut self, test: proc_macro2::TokenStream) { + if self.separate_layout_tests { + self.layout_test_items.push(test); + } else { + self.items.push(test); + } + } } impl ops::Deref for CodegenResult<'_> { @@ -634,6 +661,9 @@ impl CodeGenerator for Module { if result.saw_bitfield_unit { utils::prepend_bitfield_unit_type(ctx, &mut *result); } + if !result.layout_test_items.is_empty() { + utils::prepend_layout_tests(ctx, &mut *result); + } } }; @@ -1453,7 +1483,7 @@ impl CodeGenerator for TemplateInstantiation { // If #size_of_expr > #size, this will index OOB, and if // #size_of_expr < #size, the subtraction will overflow, both // of which print enough information to see what has gone wrong. - result.push(quote! { + result.push_layout_test(quote! { #[allow(clippy::unnecessary_operation, clippy::identity_op)] const _: () = { [#size_of_err][#size_of_expr - #size]; @@ -1461,7 +1491,7 @@ impl CodeGenerator for TemplateInstantiation { }; }); } else { - result.push(quote! { + result.push_layout_test(quote! { #[test] fn #fn_name() { assert_eq!(#size_of_expr, #size, #size_of_err); @@ -2835,7 +2865,7 @@ impl CodeGenerator for CompInfo { }; if compile_time { - result.push(quote! { + result.push_layout_test(quote! { #[allow(clippy::unnecessary_operation, clippy::identity_op)] const _: () = { [#size_of_err][#size_of_expr - #size]; @@ -2844,7 +2874,7 @@ impl CodeGenerator for CompInfo { }; }); } else { - result.push(quote! { + result.push_layout_test(quote! { #[test] fn #fn_name() { #uninit_decl @@ -5332,7 +5362,10 @@ pub(crate) fn codegen( context.gen(|context| { let _t = context.timer("codegen"); let counter = Cell::new(0); - let mut result = CodegenResult::new(&counter); + let mut result = CodegenResult::new( + &counter, + context.options().separate_layout_tests_path.is_some(), + ); debug!("codegen: {:?}", context.options()); @@ -5378,6 +5411,7 @@ pub(crate) fn codegen( } utils::serialize_items(&result, context)?; + utils::write_layout_tests(&result, context)?; Ok(postprocessing::postprocessing( result.items, @@ -5389,14 +5423,16 @@ pub(crate) fn codegen( pub(crate) mod utils { use super::helpers::BITFIELD_UNIT; use super::serialize::CSerialize; - use super::{error, CodegenError, CodegenResult, ToRustTyOrOpaque}; + use super::{ + error, postprocessing, CodegenError, CodegenResult, ToRustTyOrOpaque, + }; use crate::callbacks::DiscoveredItemId; use crate::ir::context::BindgenContext; use crate::ir::context::TypeId; use crate::ir::function::{Abi, ClangAbi, FunctionSig}; use crate::ir::item::{Item, ItemCanonicalPath}; use crate::ir::ty::TypeKind; - use crate::{args_are_cpp, file_is_cpp}; + use crate::{args_are_cpp, file_is_cpp, Bindings}; use std::borrow::Cow; use std::io::Write; use std::mem; @@ -5461,6 +5497,66 @@ pub(crate) mod utils { Ok(()) } + pub(super) fn write_layout_tests( + result: &CodegenResult, + context: &BindgenContext, + ) -> Result<(), CodegenError> { + if result.layout_test_items.is_empty() { + return Ok(()); + } + + // The unwrap here is intentional, layout_test_items will be empty if + // the path is not set + let path = PathBuf::from( + context + .options() + .separate_layout_tests_path + .as_ref() + .unwrap(), + ); + + let dir = path.parent().unwrap(); + + if !dir.exists() { + std::fs::create_dir_all(dir)?; + } + + let tokens = postprocessing::postprocessing( + result.layout_test_items.clone(), + context.options(), + ); + + let mut writer = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(path)?; + const NL: &str = if cfg!(windows) { "\r\n" } else { "\n" }; + + if !context.options().disable_header_comment { + let version = + option_env!("CARGO_PKG_VERSION").unwrap_or("(unknown version)"); + write!( + writer, + "/* layout tests automatically generated by rust-bindgen {version} */{NL}{NL}", + )?; + } + + // Bindings is used to apply formatting + match Bindings::format_tokens(context.options(), &tokens) { + Ok(formatted_bindings) => { + writer.write_all(formatted_bindings.as_bytes())?; + } + Err(err) => { + eprintln!( + "Failed to run rustfmt: {err} (non-fatal, continuing)" + ); + writer.write_all(tokens.to_string().as_bytes())?; + } + } + Ok(()) + } + pub(super) fn wrap_as_variadic_fn( ctx: &BindgenContext, signature: &FunctionSig, @@ -5542,6 +5638,23 @@ pub(crate) mod utils { result.extend(old_items); } + pub(crate) fn prepend_layout_tests( + ctx: &BindgenContext, + result: &mut Vec, + ) { + let include_src = format!( + "include!(\"{}\");", + ctx.options().separate_layout_tests_path.as_ref().expect("Layout tests are only separate when there is a path to save them to").display() + ); + let include_code = + proc_macro2::TokenStream::from_str(&include_src).unwrap(); + let include_item = quote!(#include_code); + + let items = vec![include_item]; + let old_items = mem::replace(result, items); + result.extend(old_items); + } + pub(crate) fn prepend_objc_header( ctx: &BindgenContext, result: &mut Vec, diff --git a/bindgen/lib.rs b/bindgen/lib.rs index f5b7326e3e..ce35c51ce8 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -974,7 +974,7 @@ impl Bindings { writer.write_all(NL.as_bytes())?; } - match self.format_tokens(&self.module) { + match Self::format_tokens(&self.options, &self.module) { Ok(formatted_bindings) => { writer.write_all(formatted_bindings.as_bytes())?; } @@ -989,9 +989,9 @@ impl Bindings { } /// Gets the rustfmt path to rustfmt the generated bindings. - fn rustfmt_path(&self) -> Cow<'_, Path> { - debug_assert!(matches!(self.options.formatter, Formatter::Rustfmt)); - if let Some(ref p) = self.options.rustfmt_path { + fn rustfmt_path(options: &BindgenOptions) -> Cow<'_, Path> { + debug_assert!(matches!(options.formatter, Formatter::Rustfmt)); + if let Some(ref p) = options.rustfmt_path { Cow::Borrowed(p) } else if let Ok(rustfmt) = env::var("RUSTFMT") { Cow::Owned(rustfmt.into()) @@ -1003,14 +1003,14 @@ impl Bindings { } /// Formats a token stream with the formatter set up in `BindgenOptions`. - fn format_tokens( - &self, + pub(crate) fn format_tokens( + options: &BindgenOptions, tokens: &proc_macro2::TokenStream, ) -> io::Result { let _t = time::Timer::new("rustfmt_generated_string") - .with_output(self.options.time_phases); + .with_output(options.time_phases); - match self.options.formatter { + match options.formatter { Formatter::None => return Ok(tokens.to_string()), #[cfg(feature = "prettyplease")] Formatter::Prettyplease => { @@ -1019,13 +1019,12 @@ impl Bindings { Formatter::Rustfmt => (), } - let rustfmt = self.rustfmt_path(); + let rustfmt = Self::rustfmt_path(options); let mut cmd = Command::new(&*rustfmt); cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); - if let Some(path) = self - .options + if let Some(path) = options .rustfmt_configuration_file .as_ref() .and_then(|f| f.to_str()) @@ -1033,10 +1032,9 @@ impl Bindings { cmd.args(["--config-path", path]); } - let edition = self - .options + let edition = options .rust_edition - .unwrap_or_else(|| self.options.rust_target.latest_edition()); + .unwrap_or_else(|| options.rust_target.latest_edition()); cmd.args(["--edition", &format!("{edition}")]); let mut child = cmd.spawn()?; @@ -1072,7 +1070,7 @@ impl Bindings { Some(3) => { rustfmt_non_fatal_error_diagnostic( "Rustfmt could not format some lines", - &self.options, + options, ); Ok(bindings) } diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs index 18b16cfcba..2bc2d3981c 100644 --- a/bindgen/options/cli.rs +++ b/bindgen/options/cli.rs @@ -540,6 +540,10 @@ struct BindgenCommand { /// inline` functions. #[arg(long, value_name = "SUFFIX")] wrap_static_fns_suffix: Option, + /// Sets the PATH for the source file that must be created for layout tests; + /// otherwise, tests will be in the same file as the bindings. + #[arg(long, value_name = "PATH")] + separate_layout_tests_path: Option, /// Set the default VISIBILITY of fields, including bitfields and accessor methods for /// bitfields. This flag is ignored if the `--respect-cxx-access-specs` flag is used. #[arg(long, value_name = "VISIBILITY")] @@ -708,6 +712,7 @@ where wrap_static_fns, wrap_static_fns_path, wrap_static_fns_suffix, + separate_layout_tests_path, default_visibility, generate_deleted_functions, generate_pure_virtual_functions, @@ -1006,6 +1011,7 @@ where wrap_static_fns, wrap_static_fns_path, wrap_static_fns_suffix, + separate_layout_tests_path => |b, p| b.separate_layout_tests_path(Some(p)), default_visibility, generate_deleted_functions, generate_pure_virtual_functions, diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index bc0cb75a33..6a888ce686 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -2199,6 +2199,21 @@ options! { }, as_args: "--wrap-static-fns-path", }, + /// The path of the file where the layout tests are placed. + separate_layout_tests_path: Option { + methods: { + /// Set the path for the source code file that would be created if any + /// layout tests must be generated. + /// + /// If `None`, layout tests are generated in the same file as the + /// bindings. + pub fn separate_layout_tests_path>(mut self, path: Option) -> Self { + self.options.separate_layout_tests_path = path.map(|p| p.as_ref().to_owned()); + self + } + }, + as_args: "--separate-layout-tests-path", + }, /// Default visibility of fields. default_visibility: FieldVisibilityKind { methods: {