[DebugInfo] Clang emits incorrect DWARF expression for unsigned integer comparison
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
Clang can emit an incorrect DWARF location expression for an optimized-out unsigned integer comparison.
For an expression such as `int x = a < b;` where `a` and `b` are `uint64_t`, Clang emits a DWARF expression that applies `DW_OP_lt` directly to two generic type stack values and only converts the result afterwards. Under [DWARF v5](https://dwarfstd.org/doc/DWARF5.pdf), generic type stack values have an address-sized integral type with unspecified signedness (Section 2.5.1), and relational operations on generic operands are performed as signed comparisons (Section 2.5.1.5). Because Clang does not convert the operands to an unsigned type before `DW_OP_lt`, the comparison defaults to signed and does not preserve the unsigned source-language semantics.
For comparison, GCC emits DWARF for this example that fully conforms to the specification.
## Reproducer
```C
#include
volatile uint64_t sink64;
__attribute__((noinline))
int unsigned_dead_cmp_live_operands(uint64_t a, uint64_t b) {
int x = a < b;
sink64 += a;
sink64 += b;
return 0;
}
int main(void) {
return unsigned_dead_cmp_live_operands(0x8000000000000000ULL, 10ULL);
}
```
Build with:
```bash
clang -O2 -g -gdwarf-5 -fno-pie -no-pie demo.c -o demo
```
The generated DWARF for `x` is:
```
DW_AT_location (
DW_OP_breg5 RDI+0,
DW_OP_breg4 RSI+0,
DW_OP_lt,
DW_OP_convert (...) "DW_ATE_unsigned_1",
DW_OP_convert (...) "DW_ATE_unsigned_32",
DW_OP_stack_value
)
```
The `DW_OP_convert` operations occur after `DW_OP_lt`, so they only convert the `boolean` result. They do not make the two operands unsigned before the comparison.
## Expected Behavior
The DWARF expression should preserve the source-language unsigned comparison. For example, the operands should be converted to an unsigned base type before `DW_OP_lt`, or the producer should otherwise emit an equivalent expression whose comparison is unsigned.
For the call `unsigned_dead_cmp_live_operands(0x8000000000000000ULL, 10ULL)`, the source-language value of `x` is `0`, because `0x8000000000000000ULL > 10ULL`.
## Actual Behavior on GDB/LLDB
A DWARF consumer that follows the generic-type comparison rule evaluates the current expression as a signed comparison:
```
(int64_t)0x8000000000000000 < (int64_t)10
```
which is true, so it reports `x = 1`, whereas the correct source-language value is `x = 0`.
GDB does exactly this: its evaluation is correct per the DWARF specification, but it produces the wrong value because the debug info emitted by Clang is incorrect.
LLDB may appear to print the correct value for this particular unsigned case, but only because of a separate LLDB consumer bug in which generic/register integer comparison is not handled according to the DWARF signed-comparison rule. That issue is tracked separately in [#202878](https://github.com/llvm/llvm-project/issues/202878). A signed-comparison variant demonstrates that LLDB's behavior is not reliable and that the apparent correctness in this unsigned case is accidental.
Contributor guide
Research direction
Start with the demo.c reproducer and run the provided clang -O2 -g -gdwarf-5 command, then inspect the generated DWARF location expression for x. Trace the Clang debug-info emission path for optimized-out comparisons and compare the result with GCC's expression. Done means the emitted expression preserves unsigned comparison semantics and GDB evaluates the call with x = 0.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100