From 191f933e993afe4bb222e8fc1387b11fa95a2e42 Mon Sep 17 00:00:00 2001 From: Julia Hansbrough Date: Wed, 12 Aug 2026 21:27:01 +0000 Subject: [PATCH] Do not insert dummy fields into bitfields in cases where this would cause misalignment. Suppose you have the following C++ code: ``` // reproduce.h struct __attribute__((aligned(8))) StructWithBitfieldAndDouble { unsigned int bitfield: 32; double standard_field; }; // reproduce.cpp #include "reproduce.h" #include int main() { std::cout << "cpp_size=" << sizeof(SomeStruct) << std::endl; std::cout << "cpp_align=" << alignof(SomeStruct) << std::endl; return 0; } ``` As of right now, Bindgen will produce the following Rust code: ``` #[repr(C)] #[repr(align(8))] #[derive(Debug, Default, Copy, Clone)] pub struct StructWithBitfieldAndDouble { pub _bindgen_align: [u64; 0], pub _bitfield_1: __BindgenBitfieldUnit<[u8, 4usize]>, pub standard_field: f64, } ``` If Bindgen compiles this targeting a 32-bit architecture, the C++ struct will be 16 bytes in size (as one would expect, based on the `aligned(8)`), but the Rust struct will be only 12 bytes in size (because the alignment of u64 is 4, so _bindgen_align, as a zero-length struct, ends up occupying 0 bytes of space, and the struct's overall size will thus be 12). This occurs because of the way dummy fields are added to bitfields as per https://github.com/rust-lang/rust-bindgen/pull/3247. We should check that the target primitive's alignment is greater-than-or-equal-to the explicitly-defined alignment in these cases, and fall back to simply using the explicit alignment in those cases. --- .../tests/align_bitfields_32bit.rs | 562 ++++++++++++++++++ .../tests/headers/align_bitfields_32bit.h | 6 + bindgen/codegen/mod.rs | 11 +- 3 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 bindgen-tests/tests/expectations/tests/align_bitfields_32bit.rs create mode 100644 bindgen-tests/tests/headers/align_bitfields_32bit.h diff --git a/bindgen-tests/tests/expectations/tests/align_bitfields_32bit.rs b/bindgen-tests/tests/expectations/tests/align_bitfields_32bit.rs new file mode 100644 index 0000000000..1e7299e308 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/align_bitfields_32bit.rs @@ -0,0 +1,562 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[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)] +#[repr(align(8))] +#[derive(Debug, Default, Copy, Clone)] +pub struct StructWithBitfieldAndDouble { + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 4usize]>, + pub standard_field: f64, +} +impl StructWithBitfieldAndDouble { + #[inline] + pub fn bitfield(&self) -> ::std::os::raw::c_uint { + self._bitfield_1.get_const::<0usize, 32u8>() as u32 as _ + } + #[inline] + pub fn set_bitfield(&mut self, val: ::std::os::raw::c_uint) { + let val: u32 = val as _; + self._bitfield_1.set_const::<0usize, 32u8>(val as u64) + } + #[inline] + pub unsafe fn bitfield_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + <__BindgenBitfieldUnit< + [u8; 4usize], + >>::raw_get_const::<0usize, 32u8>(::std::ptr::addr_of!((*this)._bitfield_1)) + as u32 as _ + } + } + #[inline] + pub unsafe fn set_bitfield_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = val as _; + <__BindgenBitfieldUnit< + [u8; 4usize], + >>::raw_set_const::< + 0usize, + 32u8, + >(::std::ptr::addr_of_mut!((*this)._bitfield_1), val as u64) + } + } + #[inline] + pub fn new_bitfield_1( + bitfield: ::std::os::raw::c_uint, + ) -> __BindgenBitfieldUnit<[u8; 4usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 4usize]> = Default::default(); + __bindgen_bitfield_unit + .set_const::< + 0usize, + 32u8, + >({ + let bitfield: u32 = bitfield as _; + bitfield as u64 + }); + __bindgen_bitfield_unit + } +} diff --git a/bindgen-tests/tests/headers/align_bitfields_32bit.h b/bindgen-tests/tests/headers/align_bitfields_32bit.h new file mode 100644 index 0000000000..8099c1b5a9 --- /dev/null +++ b/bindgen-tests/tests/headers/align_bitfields_32bit.h @@ -0,0 +1,6 @@ +// bindgen-flags: --no-layout-tests -- --target=i686-unknown-linux-gnu + +struct __attribute__((aligned(8))) StructWithBitfieldAndDouble { + unsigned int bitfield : 32; + double standard_field; +}; diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index d61f87c901..53f1808554 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -2573,7 +2573,16 @@ impl CodeGenerator for CompInfo { // the beginning of the struct. This avoids hitting // https://github.com/rust-lang/rust-bindgen/issues/2179 // Do it for bitfields only for now for backwards compat. - if self.has_bitfields() && explicit <= 8 { + // + // (Note that, if the target's primitive type alignment is *not* sufficiently + // aligned, as in the case of u64 on a 32-bit system, we should not insert the dummy + // field, hence checking that `explicit` is less-than-or-equal-to the target primitive + // type's alignemnt.) + let target_primitive_align = Layout::for_size(ctx, explicit).align; + if self.has_bitfields() && + explicit <= 8 && + target_primitive_align >= explicit + { let align_ty = match explicit { 8 => quote! { u64 }, 4 => quote! { u32 },