[DebugInfo][SimplifyCFG] PHI block duplication assigns the same debug value to both branches
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Description
SimplifyCFG removes a block containing a PHI node by copying its remaining operations into the two predecessor branches. The program values are specialized correctly for each branch, but the debug value for the PHI result is specialized to `false` in both copies.
In the true branch, the optimized IR calls `use_bool(true)` and follows the control-flow path for a true value, but the adjacent debug record describes the source variable as `false`. LLDB consequently reports the wrong value when execution takes that branch.
## Reproducer
`case.c`:
```c
#include
__attribute__((noinline)) void foo() {}
__attribute__((noinline)) void bar() {}
__attribute__((noinline)) void use_bool(bool value) {}
__attribute__((noinline))
void phi_extra_use(bool c1) {
unsigned _BitInt(1) c2 =
c1 ? (foo(), (unsigned _BitInt(1))1)
: (bar(), (unsigned _BitInt(1))0);
use_bool(c2);
if (c2)
foo();
else
bar();
}
int main() {
phi_extra_use(true);
return 0;
}
```
Build commands:
```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=simplifycfg -S src.ll -o tgt.ll
clang src.ll -o src.out
clang tgt.ll -o tgt.out
```
`lldb-commands.txt`:
```text
settings set target.disable-aslr true
breakpoint set --source-pattern-regexp 'use_bool\(c2\);'
run
frame variable c2
quit
```
Run LLDB with:
```sh
lldb --batch src.out -s lldb-commands.txt
lldb --batch tgt.out -s lldb-commands.txt
```
The complete `src.ll` and `tgt.ll` are available in this [Godbolt link](https://godbolt.org/z/8oobEz8nE).
## IR Debug Info
Before SimplifyCFG, `%cond` selects `true` on the `cond.true` path and `false` on the `cond.false` path. The debug record describes `c2` using the PHI result:
```llvm
cond.true:
call void @foo(), !dbg !29
br label %cond.end, !dbg !28
cond.false:
call void @bar(), !dbg !30
br label %cond.end, !dbg !28
cond.end:
%cond = phi i1 [ true, %cond.true ], [ false, %cond.false ], !dbg !28
#dbg_value(i1 %cond, !31, !DIExpression(DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !27)
call void @use_bool(i1 noundef zeroext %cond), !dbg !32
br i1 %cond, label %if.then, label %if.else, !dbg !33
```
After SimplifyCFG, the PHI block and its conditional branch are folded into the predecessors. The ordinary IR correctly uses `true` in `cond.true` and `false` in `cond.false`, but both copies of the debug record use `false`:
```llvm
cond.true:
call void @foo(), !dbg !29
#dbg_value(i1 false, !30, !DIExpression(DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !27)
call void @use_bool(i1 noundef zeroext true), !dbg !31
call void @foo(), !dbg !32
br label %if.end, !dbg !32
cond.false:
call void @bar(), !dbg !34
#dbg_value(i1 false, !30, !DIExpression(DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !27)
call void @use_bool(i1 noundef zeroext false), !dbg !31
call void @bar(), !dbg !35
br label %if.end
```
`!30` is the local variable `c2` in `tgt.ll`:
```llvm
!30 = !DILocalVariable(name: "c2", scope: !25, file: !1, line: 9, type: !3)
```
## Observed Behavior
`main` calls `phi_extra_use(true)`, so execution takes `cond.true` and the source-level value of `c2` is true.
Before SimplifyCFG, LLDB reports the expected value when stopped at `use_bool(c2);`:
```text
== src.out ==
(lldb) frame variable c2
(unsigned _BitInt(1)) c2 = 1
```
After SimplifyCFG, LLDB reports the value from the incorrect debug record:
```text
== tgt.out ==
(lldb) frame variable c2
(unsigned _BitInt(1)) c2 = 0
```
## Expected Behavior
When the PHI block is copied and its value is specialized for each predecessor, the associated debug value should be specialized in the same way. The true branch should describe `c2` as `true`, while the false branch should describe it as `false`:
```llvm
cond.true:
#dbg_value(i1 true, !30, !DIExpression(DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !27)
cond.false:
#dbg_value(i1 false, !30, !DIExpression(DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !27)
```
LLDB should report `c2 = 1` at `use_bool(c2);` when `phi_extra_use(true)` is executed.
## 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 reproduce the transformation using the listed opt -passes=simplifycfg command, comparing the debug records in src.ll and tgt.ll. Inspect SimplifyCFG's PHI-block duplication and debug-value handling; done when the true and false branches carry matching specialized values and LLDB reports c2 = 1 on the true path.
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
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100