[DebugInfo][Reassociate] reassociate makes a variable update visible too early
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Description
After running `reassociate`, LLDB prints a different value for a source local variable at line 6.
The breakpoint resolves to `case.c:6:13`, which is the use of `tmp` on the right-hand side of:
```c
tmp = tmp + 10 + 20;
```
At that source point, the assignment has not completed yet, so `tmp` should still have its old value. With the call `example(1000, 1, 0)`, that value is `0`.
Before `reassociate`, LLDB reports `tmp = 0`. After `reassociate`, LLDB reports `tmp = 30`, which is the value after the assignment.
## Reproducer
`case.c`:
```c
static const int g[2] = {9, 4};
__attribute__((noinline))
int example(int a, int b, int c) {
int tmp = c;
tmp = tmp + 10 + 20;
return a + g[b] + tmp;
}
int main() {
return example(1000, 1, 0);
}
```
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 -S case.ll -o src.ll
opt -passes=reassociate -S src.ll -o tgt.ll
clang src.ll -o src.out
clang -mllvm -fast-isel=false 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/jzxbaWErK).
## Debugging Script
```sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
clang -g -O0 -Xclang -disable-O0-optnone -fno-discard-value-names -S -emit-llvm case.c -o case.ll
opt -passes=mem2reg -S case.ll -o src.ll
opt -passes=reassociate -S src.ll -o tgt.ll
clang src.ll -o src.out
clang -mllvm -fast-isel=false tgt.ll -o tgt.out
for exe in src.out tgt.out; do
echo "== $exe =="
lldb --batch \
-o 'breakpoint set --file case.c --line 6' \
-o run \
-o 'frame variable tmp' \
-- "./$exe"
echo
done
```
## Actual Behavior
Before `reassociate`, LLDB reports the expected old value at `case.c:6:13`:
```text
== src.out ==
Breakpoint 1: where = src.out`example + 8 at case.c:6:13
frame #0: ... src.out`example(a=1000, b=1, c=0) at case.c:6:13
(lldb) frame variable tmp
(int) tmp = 0
```
After `reassociate`, LLDB reports the value after the assignment:
```text
== tgt.out ==
Breakpoint 1: where = tgt.out`example + 19 at case.c:6:13
frame #0: ... tgt.out`example(a=1000, b=1, c=0) at case.c:6:13
(lldb) frame variable tmp
(int) tmp = 30
```
## Expected Behavior
At `case.c:6:13`, LLDB should report `tmp = 0`, matching the source-level value before `tmp = tmp + 10 + 20` has completed.
If the exact source value cannot be preserved at that point after reassociation, `tmp` should be reported as unavailable/optimized out for that range. It should not show the future value `30`.
## Observed Directly in `.ll`
Before `reassociate`, `src.ll` keeps the debug value update for `tmp` after both additions:
```llvm
define dso_local i64 @example(i32 noundef %a, i32 noundef %b, i32 noundef %c) #0 !dbg !17 {
entry:
#dbg_value(i32 %a, !22, !DIExpression(), !23)
#dbg_value(i32 %b, !24, !DIExpression(), !23)
#dbg_value(i32 %c, !25, !DIExpression(), !23)
#dbg_value(i32 %c, !26, !DIExpression(), !23)
%add = add nsw i32 %c, 10, !dbg !27
%add1 = add nsw i32 %add, 20, !dbg !28
#dbg_value(i32 %add1, !26, !DIExpression(), !23)
...
}
```
Here `!26` is the `DILocalVariable` for `tmp`:
```llvm
!26 = !DILocalVariable(name: "tmp", scope: !17, file: !3, line: 5, type: !7)
```
After `reassociate`, `tgt.ll` represents `tmp` as `c + 30` at function entry:
```llvm
define dso_local i64 @example(i32 noundef %a, i32 noundef %b, i32 noundef %c) #0 !dbg !17 {
entry:
#dbg_value(i32 %a, !22, !DIExpression(), !23)
#dbg_value(i32 %b, !24, !DIExpression(), !23)
#dbg_value(i32 %c, !25, !DIExpression(), !23)
#dbg_value(i32 %c, !26, !DIExpression(), !23)
#dbg_value(i32 %c, !26, !DIExpression(DW_OP_plus_uconst, 30, DW_OP_stack_value), !23)
...
}
```
That second `#dbg_value` for `tmp` is visible before the source assignment on line 6 has completed. As a result, the debugger reports the post-assignment value while stopped at the right-hand-side use of `tmp`.
## Environment
```text
clang version 24.0.0git
llvm-project revision: b443896c13aded8b40d7bae4a5a9adbc96fd0d31
LLVM version 24.0.0git
LLDB version 24.0.0git
```
Contributor guide
Research direction
Start with case.c and the provided debugging script, then inspect how the reassociate pass transforms the debug values in src.ll and tgt.ll. Trace why the second tmp value appears at function entry; done means LLDB reports 0 at case.c:6:13, or reports tmp as unavailable rather than 30, after running the reproducer.
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
- Clearly specified
- Newbie friendliness
- 55/100