[DebugInfo][GVN Sink] gvn-sink places #dbg_value before the SSA value it references
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Description
After running `gvn-sink`, a `#dbg_value` intrinsic is placed before the SSA value it references:
```llvm
#dbg_value(i32 %8, !24, !DIExpression(), !26)
...
%8 = add nsw i32 %1, 1, !dbg !27
```
The backend appears to drop this debug info later, so this may not directly show up as an incorrect debugger value in the final binary. However, I think this is still a middle-end debug info bug: `gvn-sink` produces IR where the debug value for a source variable refers to a value that is not available at that program point.
The debug info should either move together with the sunk instruction, be rewritten to a valid expression/value, or be marked unavailable. It should not temporarily describe a source variable using an SSA value before that value is defined.
## Reproducer
`case.c`:
```c
#include
__attribute__((noinline))
int func(bool flag, int x) {
if (flag) {
int a = x + 1;
} else {
int b = x + 1;
}
return 1;
}
int main() {
return func(true, 1);
}
```
Build pipeline:
```sh
clang -g -O0 -Xclang -disable-O0-optnone -S -emit-llvm case.c -o case.ll
opt -passes="mem2reg,gvn-sink" -S case.ll -o opt.ll
```
I will provide a link to the full `case.ll` and `opt.ll` in the issue instead of embedding the complete IR here [Full IR](https://godbolt.org/z/396o4Gden).
## Actual Behavior
In `opt.ll`, both branch-local debug values for `a` and `b` refer to `%8`, but `%8` is defined later in the merge block:
```llvm
5:
#dbg_value(i32 %8, !20, !DIExpression(), !22)
br label %7, !dbg !23
6:
#dbg_value(i32 %8, !24, !DIExpression(), !26)
br label %7
7:
%8 = add nsw i32 %1, 1, !dbg !27
ret i32 1, !dbg !28
```
`opt -passes=verify opt.ll -disable-output` accepts the IR, but the debug value is not meaningful at the point where it appears.
Compiling this IR to an executable succeeds, but the backend drops the affected local variable debug info. In the final DWARF for `func`, I only see the parameter `flag`; the debug entries for the branch-local `a` and `b` variables are gone.
## Expected Behavior
`gvn-sink` should preserve valid debug info when it sinks the common computation.
Possible acceptable outcomes:
- Move the relevant `#dbg_value` so it appears at a point where `%8` is available.
- Rewrite the debug value in the branches to use a valid expression, such as `x + 1`, if that is supported and appropriate.
- Drop or undef the affected `#dbg_value` if the source variable value cannot be represented correctly after sinking.
In my opinion, the affected branch-local debug values should be made unavailable at their original locations, for example:
```llvm
#dbg_value(i32 poison, !20, !DIExpression(), !22)
#dbg_value(i32 poison, !24, !DIExpression(), !26)
```
The middle-end should not leave a `#dbg_value` that references `%8` before `%8` is defined.
## Environment
```text
clang version 23.0.0git
llvm-project revision: 58203500b5bd1628401c1a103eb264b7d78ea3bb
LLVM version 23.0.0git
```
## Notes
This may not produce a visible wrong value in LLDB because the backend discards the bad debug info. I still consider it a middle-end bug because `gvn-sink` creates invalid or unusable debug-location semantics before code generation.
Contributor guide
Research direction
Start with the case.c reproducer and run the clang and opt pipeline shown in the issue, focusing on the gvn-sink pass and the resulting opt.ll. Compare each #dbg_value with the definition of its referenced SSA value. Done means gvn-sink no longer leaves debug values referring to values unavailable at their locations, with the resulting IR accepted by opt -passes=verify.
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