dump-c: sign-extend signed bit-field temporaries#9013
Conversation
When goto-conversion lowers a chained assignment `outer = bf = expr` through a bit-field lvalue, it introduces a temporary symbol whose declared type is the bit-field type. `goto_program2code` rewrites that temporary's type to the bit-field's underlying integer type and previously masked the initialiser with `& ((1 << N) - 1)` to model the bit-field truncation. For unsigned bit-fields the AND-mask is enough, but for signed bit-fields the value of `bf = expr` is the value of `bf` after the assignment - i.e. the masked value sign-extended to the underlying type (per C11 6.5.16.1p3). Without the sign extension, the temporary held an unsigned-truncated value, so subsequent uses of it (here: the chained assignment to `outer`) observed the wrong value. The original program continued to verify correctly under CBMC, but the C code emitted by `--dump-c` no longer agreed with the source - which CSmith picks up as a checksum mismatch. Apply branchless sign extension `((v & mask) ^ sign_bit) - sign_bit` for the signed-and-narrower case. This expression stays within the bit-field's value range and so triggers neither signed overflow nor implementation-defined shift behaviour in the dumped C. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes --dump-c output semantics for chained assignments through signed bit-fields by ensuring that temporaries introduced during lowering observe the C-standard-required sign-extended value of the bit-field after assignment (C11 6.5.16.1p3). This aligns dumped C with source behavior and prevents checksum mismatches (e.g., from CSmith) while leaving verification behavior unchanged.
Changes:
- Update
goto_program2codebit-field temporary rewriting to apply branchless sign extension for signed, narrower-than-underlying bit-fields. - Keep existing masking behavior for unsigned bit-fields and for signed bit-fields that are not narrower than their underlying type.
- Add a regression test that exercises
outer = s.r = vwheres.ris a 2-bit signed bit-field and checks the dumped-C path preserves sign-extension semantics.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/goto-instrument/goto_program2code.cpp | Sign-extends masked initializer for signed, narrow bit-field temporaries when rewriting their declared type to the underlying integer type. |
| regression/goto-instrument/dump-bitfield-chained-assign/test.desc | Adds a --dump-c regression test description to ensure dumped C remains verifiable and preserves correct chained-assignment semantics. |
| regression/goto-instrument/dump-bitfield-chained-assign/main.c | Minimal C program reproducing the signed bit-field chained-assignment sign-extension case (2 stored into 2-bit signed bit-field yields -2). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #9013 +/- ##
========================================
Coverage 80.55% 80.55%
========================================
Files 1707 1707
Lines 189051 189058 +7
Branches 73 73
========================================
+ Hits 152299 152305 +6
- Misses 36752 36753 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
When goto-conversion lowers a chained assignment
outer = bf = exprthrough a bit-field lvalue, it introduces a temporary symbol whose declared type is the bit-field type.goto_program2coderewrites that temporary's type to the bit-field's underlying integer type and previously masked the initialiser with& ((1 << N) - 1)to model the bit-field truncation.For unsigned bit-fields the AND-mask is enough, but for signed bit-fields the value of
bf = expris the value ofbfafter the assignment - i.e. the masked value sign-extended to the underlying type (per C11 6.5.16.1p3). Without the sign extension, the temporary held an unsigned-truncated value, so subsequent uses of it (here: the chained assignment toouter) observed the wrong value. The original program continued to verify correctly under CBMC, but the C code emitted by--dump-cno longer agreed with the source - which CSmith picks up as a checksum mismatch.Apply branchless sign extension
((v & mask) ^ sign_bit) - sign_bitfor the signed-and-narrower case. This expression stays within the bit-field's value range and so triggers neither signed overflow nor implementation-defined shift behaviour in the dumped C.