llvm / llvm/llvm-project

[DebugInfo][Reassociate] reassociate makes a variable update visible too early

Open
#212,714 6 comments 0 reactions 0 assignees View on GitHub
debuginfo llvm:transforms
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

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.