[DebugInfo][IndVars] indvars produces incorrect variable debug info after rewriting a negative-stride loop
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Description
After running `indvars` on a small negative-stride loop, LLDB prints a different value for the source variable `num` at the final `return` statement.
The source-level loop decrements `num` from `10` until the loop condition `num > 7` becomes false. At line 13, immediately after the loop, the source value of `num` should be `7`.
Before `indvars`, LLDB reports `num = 7`. After `indvars`, LLDB reports `num = 9`.
## Reproducer
`case.c`:
```c
#include
__attribute__((noinline))
int func() {
int sum = 0;
int num = 10;
do {
sum += num;
--num;
} while (num > 7);
return sum;
}
int main() {
return func();
}
```
Build pipeline:
```sh
clang -g -O0 -Xclang -disable-O0-optnone -S -emit-llvm case.c -o case.ll
opt -passes="mem2reg,simplifycfg,instsimplify" -S case.ll -o src.ll
opt -passes=indvars -S src.ll -o tgt.ll
clang src.ll -o src.out
clang tgt.ll -o tgt.out
```
I will provide links to the full `src.ll` and `tgt.ll` in the issue rather than embedding the full IR [here](https://godbolt.org/z/85e9Tx9d7).
## Debugging Script
```sh
#!/usr/bin/env bash
set -euo pipefail
clang -g -O0 -Xclang -disable-O0-optnone -S -emit-llvm case.c -o case.ll
opt -passes="mem2reg,simplifycfg,instsimplify" -S case.ll -o src.ll
opt -passes=indvars -S src.ll -o tgt.ll
clang src.ll -o src.out
clang tgt.ll -o tgt.out
for exe in src.out tgt.out; do
echo "== $exe =="
lldb --batch \
-o 'breakpoint set --file case.c --line 13' \
-o run \
-o 'frame variable num' \
-- "./$exe"
echo
done
```
## Actual Behavior
Before `indvars`, LLDB reports the expected value at line 13:
```text
== src.out ==
Breakpoint 1: where = src.out`func + 47 at case.c:13:3
frame #0: ... src.out`func at case.c:13:3
(lldb) frame variable num
(int) num = 7
```
After `indvars`, LLDB reports a different value:
```text
== tgt.out ==
Breakpoint 1: where = tgt.out`func + 30 at case.c:13:3
frame #0: ... tgt.out`func at case.c:13:3
(lldb) frame variable num
(int) num = 9
```
## Expected Behavior
At line 13, LLDB should report `num = 7`, matching the source-level value after the loop exits.
If `indvars` cannot preserve an exact source value for `num` after rewriting the negative-stride loop, the variable should be marked unavailable/optimized out for that source range. It should not show the wrong concrete value `9`.
In my opinion, since `indvars` rewrites the loop, it should explicitly terminate or poison affected variable locations when control leaves the loop if their exact source values are no longer available. For this test case, the debug value for `num` should be set to `poison` at the loop exit rather than allowing the loop-carried value to remain visible at the final `return`.
## Relevant Debug Info Clue
Before `indvars`, `num` is updated from the decremented value:
```llvm
%3 = add nsw i32 %.01, -1, !dbg !20
#dbg_value(i32 %3, !16, !DIExpression(), !15)
```
After `indvars`, the loop is folded to a constant result, but the remaining debug value for `num` no longer describes the value at the final `return`:
```llvm
%.01 = phi i32 [ 10, %0 ], [ %2, %1 ], !dbg !15
#dbg_value(i32 %.01, !16, !DIExpression(), !15)
%2 = add nsw i32 %.01, -1, !dbg !18
#dbg_value(i32 %2, !16, !DIExpression(), !15)
br i1 false, label %1, label %3, !dbg !22, !llvm.loop !23
3:
ret i32 27, !dbg !24
```
This leaves the final source location with debug info that makes LLDB print `num = 9` instead of the source-correct value `7`.
## Environment
```text
clang version 23.0.0git
llvm-project revision: 58203500b5bd1628401c1a103eb264b7d78ea3bb
LLVM version 23.0.0git
LLDB version 23.0.0git
```
## Notes
This appears to be a debug info correctness bug in the `indvars` transformation for a negative-stride loop. The optimized program result is correct, but the debugger-visible value of `num` at the source-level return statement changes from `7` to `9`.
Contributor guide
Research direction
Reproduce the mismatch using the provided case.c, opt passes, and LLDB debugging script, comparing src.ll with tgt.ll after indvars. Start at the indvars pass and its debug-value handling for the negative-stride loop. Done means LLDB reports num = 7 at line 13, or marks it unavailable rather than showing 9.
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
- 48/100