[DebugInfo][InstCombine] Folding a `bcmp` equality test into `icmp` gives opposite source variables the same negated debug expression
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Description
`bcmp` compares two byte sequences and returns zero when they are equal and a nonzero value otherwise. In this reproducer, `not_equal` stores the return value and `equal` stores the result of comparing `not_equal` with zero, so the variable names directly reflect their opposite conditions.
InstCombine recognizes the fixed-size `bcmp` call followed by an equality comparison and replaces both operations with a direct integer `icmp eq`. This preserves the program behavior, but the optimized IR describes both `not_equal` and `equal` with the same `icmp` result and a `DIExpression` beginning with `DW_OP_not`. The inversion is appropriate when reconstructing the zero/nonzero condition represented by `not_equal`, but `equal` should directly follow the equality comparison without the inversion.
Consequently, LLDB reports `not_equal = 255` and `equal = true` for unequal inputs after InstCombine. These values contradict the source relationship between the variables: a nonzero `not_equal` requires `equal` to be false.
## Reproducer
`case.c`:
```c
#include
#include
#include
bool bcmp_to_icmp(uint64_t x, uint64_t y) {
int not_equal = bcmp(&x, &y, sizeof(x));
bool equal = (not_equal == 0);
return equal;
}
int main(void) { return bcmp_to_icmp(0x12345678, 0x12345679); }
```
Build pipeline:
```sh
clang -g -O0 -Xclang -disable-O0-optnone -fno-discard-value-names -S -emit-llvm case.c -o case.ll
opt -passes="mem2reg,instsimplify" -S case.ll -o src.ll
opt -passes=instcombine -S src.ll -o tgt.ll
clang src.ll -o src.out
clang tgt.ll -o tgt.out
```
Here are the complete [src.ll and tgt.ll](https://godbolt.org/z/oMWqh3Ydb).
`lldb-src-commands.txt`:
```text
breakpoint set --file case.c --line 7
run
frame variable not_equal
next
frame variable equal
quit
```
`lldb-tgt-commands.txt`:
```text
breakpoint set --file case.c --line 8
run
frame variable not_equal
frame variable equal
quit
```
Run LLDB on the binaries before and after InstCombine:
```sh
lldb src.out -s lldb-src-commands.txt
lldb tgt.out -s lldb-tgt-commands.txt
```
## Observed Behavior
Before InstCombine, the `bcmp` result and its equality comparison have separate debug records:
```llvm
%call = call i32 @bcmp(ptr noundef %x.addr, ptr noundef %y.addr, i64 noundef 8) #2, !dbg !23
#dbg_value(i32 %call, !24, !DIExpression(), !26)
%cmp = icmp eq i32 %call, 0, !dbg !27
#dbg_value(i1 %cmp, !28, !DIExpression(DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !26)
```
After InstCombine, both source variables are described using `%.not` and a negated expression:
```llvm
%.not = icmp eq i64 %x, %y, !dbg !22
#dbg_value(i1 %.not, !23, !DIExpression(DW_OP_not, DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 32, DW_ATE_unsigned, DW_OP_stack_value), !20)
#dbg_value(i1 %.not, !25, !DIExpression(DW_OP_not, DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !20)
```
In the optimized IR, `!23` is `not_equal` and `!25` is `equal`:
```llvm
!23 = !DILocalVariable(name: "not_equal", scope: !9, file: !1, line: 6, type: !24)
!25 = !DILocalVariable(name: "equal", scope: !9, file: !1, line: 7, type: !12)
```
For the unequal inputs in the reproducer, LLDB reports a nonzero `not_equal` on line 7 and `equal = false` on line 8 before InstCombine:
```text
== src.out ==
(int) not_equal = -1
(bool) equal = false
```
After InstCombine, LLDB reports values that contradict each other:
```text
== tgt.out ==
(int) not_equal = 255
(bool) equal = true
```
## Expected Behavior
The debug record for `equal` should describe the direct result of `%.not` rather than applying `DW_OP_not`. LLDB should report a nonzero `not_equal` on line 7 and `equal = false` on line 8. If either source value cannot be represented correctly after the transformation, that variable should be unavailable instead of being reported with a contradictory value.
## Environment
This optimization requires `src.ll` to carry a concrete target architecture. I reproduced it on both x86_64 and AArch64.
```text
clang version 24.0.0git
llvm-project revision: f6ea145aa8e89631ae04f72df32580b20256d40c
LLVM version 24.0.0git
lldb version 24.0.0git
```
Contributor guide
Research direction
Start with case.c and reproduce the difference by running the listed opt and clang commands, then compare src.ll with tgt.ll around the debug records for not_equal and equal. Trace the InstCombine transformation that turns bcmp into the direct icmp. Done means the optimized debug information reports not_equal as nonzero and equal as false, or leaves an unrepresentable value unavailable.
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
- 52/100