llvm / llvm/llvm-project

[DebugInfo][IndVars] indvars produces incorrect variable debug info after rewriting a negative-stride loop

Open
#212,422 1 comment 0 reactions 0 assignees View on GitHub
debuginfo llvm:optimizations
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.