Skip to content

Fix bitfields spanning nine storage bytes - #3490

Merged
emilio merged 1 commit into
rust-lang:mainfrom
tamird:fix-bitfield-nine-byte-access
Sep 23, 2026
Merged

emilio merged 1 commit into
rust-lang:mainfrom
tamird:fix-bitfield-nine-byte-access

Conversation

@tamird

@tamird tamird commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

496817f replaced bit-at-a-time access with byte loops that assemble
the field in a u64. A valid 64-bit field starting one bit into storage
spans nine bytes, so get(1, 64) and set(1, 64, value) shift by 64 on the
last iteration. The setter also discards the high bit when aligning the
value. The existing full-word regression test covered aligned fields and
fields ending at bit 64, leaving this crossing case untested.

Handle the ninth byte separately, after shifting the first word on reads
and before shifting away the high bits on writes. Share the read and
write implementations across runtime, raw-pointer, and const-generic
accessors instead of maintaining separate usize and u64 algorithms. The
const-generic wrappers pass their constants into the inlineable helpers,
and the getters remain usable in constants.

Store byte-aligned 64-bit fields directly with write_unaligned. Such
fields use native byte order and need no read-modify-write. This keeps
AArch64 code generation to a single store instead of vectorizing eight
byte extractions into shifts, shuffles, and constant-pool loads.

Use byte pointers without creating references to the whole storage, so
raw access can leave neighboring bytes uninitialized. Check the field's
bounds before accessing storage, including in release builds, to
preserve the safe accessors' bounds checking and reject invalid writes
before modifying any bytes.

Replace comparisons between accessors sharing the same algorithm with
bit-at-a-time expected values. Cover narrow and full-width fields at
every intra-byte offset, preservation of neighboring bits, constant
evaluation, indirect storage, and bounds failures. Enable this coverage
on both endiannesses while retaining the older little-endian-specific
cases behind their existing condition.

496817f replaced bit-at-a-time access with byte loops that assemble
the field in a u64. A valid 64-bit field starting one bit into storage
spans nine bytes, so get(1, 64) and set(1, 64, value) shift by 64 on the
last iteration. The setter also discards the high bit when aligning the
value. The existing full-word regression test covered aligned fields and
fields ending at bit 64, leaving this crossing case untested.

Handle the ninth byte separately, after shifting the first word on reads
and before shifting away the high bits on writes. Share the read and
write implementations across runtime, raw-pointer, and const-generic
accessors instead of maintaining separate usize and u64 algorithms. The
const-generic wrappers pass their constants into the inlineable helpers,
and the getters remain usable in constants.

Store byte-aligned 64-bit fields directly with write_unaligned. Such
fields use native byte order and need no read-modify-write. This keeps
AArch64 code generation to a single store instead of vectorizing eight
byte extractions into shifts, shuffles, and constant-pool loads.

Use byte pointers without creating references to the whole storage, so
raw access can leave neighboring bytes uninitialized. Check the field's
bounds before accessing storage, including in release builds, to
preserve the safe accessors' bounds checking and reject invalid writes
before modifying any bytes.

Replace comparisons between accessors sharing the same algorithm with
bit-at-a-time expected values. Cover narrow and full-width fields at
every intra-byte offset, preservation of neighboring bits, constant
evaluation, indirect storage, and bounds failures. Enable this coverage
on both endiannesses while retaining the older little-endian-specific
cases behind their existing condition.
@tamird

tamird commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

r? @jsgf @emilio

This was written by LLM and reviewed by me. The bug was also spotted by multiple LLMs in aya-rs/aya#1730.

@rustbot

rustbot commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Error: Failed to set assignee to jsgf: invalid assignee

Note: Only org members with at least the repository "read" role, users with write permissions, or people who have commented on the PR may be assigned.

Please file an issue on GitHub at triagebot if there's a problem with this bot, or reach out on #triagebot on Zulip.

@tamird

tamird commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

r? @emilio

@tamird

tamird commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Another note: this diff looks much cleaner with --diff-algorithm=histogram.

@emilio emilio left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this generally looks like an improvement. Some questions tho.

View changes since this review

@@ -45,6 +201,7 @@ fn bitfield_unit_get_bit() {
);
}

#[cfg(target_endian = "little")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why these are needed? It seems to me like it should work on big endian too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the whole module was previously little-endian-only. The restrictions were moved onto the existing tests so the new regression tests could run on both endiannesses. Several old tests have little-endian-specific expectations, but you're right about this set_bit round-trip test specifically: it doesn't need the restriction.

Would you like a small follow-up removing that unnecessary restriction?

Comment thread bindgen/codegen/mod.rs
@@ -11,7 +11,7 @@ pub(crate) mod struct_layout;
#[cfg(test)]
#[allow(warnings)]
pub(crate) mod bitfield_unit;
#[cfg(all(test, target_endian = "little"))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, ok, I guess we weren't testing that in big endian before.

impl<const N: usize> __BindgenBitfieldUnit<[u8; N]> {
/// Get a field using const generics for compile-time optimization.
/// Uses native word size operations when the field fits in usize.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we're just giving this away or at least hoping LLVM optimizes it out? Might be worth checking the codegen if you haven't yet?

@emilio emilio left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked codegen and it looks not-terrible, thanks!

View changes since this review

@emilio
emilio added this pull request to the merge queue Sep 23, 2026
Merged via the queue into rust-lang:main with commit d26242b Sep 23, 2026
52 checks passed
@tamird
tamird deleted the fix-bitfield-nine-byte-access branch September 23, 2026 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants