[DebugInfo][SimplifyCFG] Salvaged `ge`/`gt`/`le`/`lt` expressions zero-extend signed integer operands
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Description
SimplifyCFG folds a signed `icmp sge i32` instruction into the `DIExpression` for the source variable `cond2`. In the generated DWARF, the two signed `int` operands are read from registers as address-sized values with their upper 32 bits cleared. They are not sign-extended before `DW_OP_ge` evaluates the comparison.
With `a = -1` and `b = 0`, the source comparison `a >= b` is false. However, the DWARF expression evaluates `a` as the 64-bit value `0x00000000ffffffff`, so LLDB reports `cond2` as true at line 12 while the program is executing the false branch.
I believe this exposes two separate bugs. First, in this example, SimplifyCFG should replace the location operand of the `#dbg_value` for `cond2` with a constant `false` rather than invoking `salvageDebugInfo`, because `cond2` is known to be false on the surviving path. Second, when `salvageDebugInfo` is used, the 32-bit values of `a` and `b` are represented incorrectly in the final DWARF expression.
I am not sure whether the latter issue belongs to the middle end or the backend.
## Reproducer
Godbolt link containing the complete `src.ll` and `tgt.ll`: [Godbolt](https://godbolt.org/z/W7zoj6Yfj).
`case.c`:
```c
#include
volatile bool sink;
__attribute__((noinline)) void is(bool value) { sink = value; }
__attribute__((always_inline)) static inline void check_sge(int a, int b) {
bool cond2 = a >= b;
if (cond2)
is(true);
else
is(false);
}
__attribute__((noinline))
void test_sge_sge(int a, int b) {
bool cond1 = a >= b;
if (!cond1)
check_sge(a, b);
}
int main(void) {
test_sge_sge(-1, 0);
return 0;
}
```
Build pipeline:
```sh
clang -g -O1 -Xclang -disable-llvm-passes -fno-discard-value-names -S -emit-llvm case.c -o case.ll
opt -passes='function(mem2reg,instsimplify),inline' -S case.ll -o src.ll
opt -passes=simplifycfg -S src.ll -o tgt.ll
llc -O2 --dwarf-version=5 -relocation-model=pic -filetype=obj tgt.ll -o tgt.o
clang tgt.o -o tgt.out
```
`lldb-commands.txt`:
```text
breakpoint set --file case.c --line 12
run
frame variable cond2
quit
```
Run LLDB on the optimized binary:
```sh
lldb tgt.out -s lldb-commands.txt
```
## DWARF Debug Information
The DIEs for `cond2` can be inspected with:
```sh
llvm-dwarfdump --debug-info --name=cond2 tgt.out
```
The generated location expression is:
```text
0x0000006b: DW_TAG_variable
DW_AT_name ("cond2")
DW_AT_decl_file ("case.c")
DW_AT_decl_line (8)
DW_AT_type (0x00000078 "_Bool")
0x000000ba: DW_TAG_variable
DW_AT_location (indexed (0x4) loclist = 0x00000048:
[0x0000000000001145, 0x0000000000001147): DW_OP_breg5 RDI+0, DW_OP_constu 0xffffffff, DW_OP_and, DW_OP_breg4 RSI+0, DW_OP_constu 0xffffffff, DW_OP_and, DW_OP_ge, DW_OP_convert (0x00000026) "DW_ATE_unsigned_1", DW_OP_convert (0x0000002a) "DW_ATE_unsigned_8", DW_OP_stack_value)
DW_AT_abstract_origin (0x0000006b "cond2")
```
`RDI` and `RSI` hold the `int` arguments `a` and `b`. The `DW_OP_and 0xffffffff` operations clear the upper 32 bits, producing address-sized values without sign-extending the original signed 32-bit operands. Consequently, `a = -1` is evaluated as `0x00000000ffffffff`, which compares greater than or equal to zero.
## Observed Behavior
Before SimplifyCFG, `cond2` is represented by a signed `icmp` and a direct debug value:
```llvm
%cmp.i = icmp sge i32 %a, %b, !dbg !48
#dbg_value(i1 %cmp.i, !45, !DIExpression(DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !46)
```
After SimplifyCFG, the comparison is removed and reconstructed with `DW_OP_ge`:
```llvm
#dbg_value(!DIArgList(i32 %a, i32 %b), !45, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_ge, DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !46)
call void @is(i1 noundef zeroext false), !dbg !48
```
The call to `is(false)` confirms that the source-level value of `cond2` is false. LLDB instead reports:
```text
(lldb) frame variable cond2
(bool) cond2 = true
```
## Expected Behavior
The generated debug information should preserve the signed 32-bit semantics of the original `icmp sge i32`. At line 12, LLDB should report:
```text
(bool) cond2 = false
```
## Environment
```text
clang version 24.0.0git
llvm-project revision: d35d0e69980f11c2acbd3670c65fec3cf574224a
LLVM version 24.0.0git
lldb version 24.0.0git
```
Contributor guide
Research direction
Start with case.c and run the listed clang, opt, and llc pipeline, comparing src.ll with tgt.ll around SimplifyCFG. Inspect tgt.o using llvm-dwarfdump and the provided LLDB commands; done means the generated debug information preserves signed 32-bit comparison semantics and LLDB reports cond2 as false.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- compilers, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100