[DebugInfo][InstCombine] instcombine produces an incorrect debug value after simplifying a masked computation
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Description
After running `instcombine`, LLDB prints a different value for a source local variable in a small bit-manipulation test case.
The source value of `tmp2` is computed as:
```c
uint64_t tmp1 = (uint64_t)b;
uint64_t tmp2 = tmp1 << 32;
```
With the call `func(0, 1)`, the source-level value of `tmp2` should be `0x0000000100000000` / `4294967296`. Before `instcombine`, LLDB reports that value. After `instcombine`, LLDB reports `tmp2 = 0x0000000000000000`.
I am not sure about the exact root cause. `instcombine` preserves `#dbg_value` correctly in many other cases, but this case seems to mishandle the debug value while simplifying a computation whose high bits are later masked away.
## Reproducer
`case.c`:
```c
#include
uint64_t func(uint64_t a, uint32_t b) {
uint64_t tmp1 = (uint64_t)b;
uint64_t tmp2 = tmp1 << 32;
uint64_t tmp3 = tmp2 + a;
uint64_t tmp4 = tmp3 & 0x123;
return tmp4;
}
int main() {
func(0, 1);
return 0;
}
```
Build pipeline:
```sh
clang -g -O0 -Xclang -disable-O0-optnone -emit-llvm -S case.c -o case.ll
opt -passes="mem2reg" -S case.ll -o src.ll
opt -passes="instcombine" -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/TxjPGv1zM).
## Debugging Script
```sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
clang -g -O0 -Xclang -disable-O0-optnone -emit-llvm -S case.c -o case.ll
opt -passes="mem2reg" -S case.ll -o src.ll
opt -passes="instcombine" -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 6' \
-o run \
-o 'frame variable --format hex tmp2' \
-- "./$exe"
echo
done
```
## Actual Behavior
Before `instcombine`, LLDB reports the expected value:
```text
== src.out ==
Breakpoint 1: where = src.out`func + 10 at case.c:6:24
frame #0: ... src.out`func(a=0, b=1) at case.c:6:24
(lldb) frame variable --format hex tmp2
(uint64_t) tmp2 = 0x0000000100000000
```
After `instcombine`, LLDB reports `0`:
```text
== tgt.out ==
Breakpoint 1: where = tgt.out`func + 7 at case.c:7:24
frame #0: ... tgt.out`func(a=0, b=1) at case.c:7:24
(lldb) frame variable --format hex tmp2
(uint64_t) tmp2 = 0x0000000000000000
```
Note that the breakpoint requested by the script is `case.c:6`; after optimization, LLDB resolves it to the next available location in the optimized code.
## Expected Behavior
LLDB should not report the wrong concrete value for `tmp2`.
At the source point after `tmp2` is initialized, `tmp2` should either:
- be shown as `0x0000000100000000`, matching `(uint64_t)1 << 32`, or
- be reported as unavailable/optimized out if the value cannot be preserved.
It should not be shown as `0x0000000000000000`.
## Observed Directly in `.ll`
This problem is visible directly in the optimized IR.
Before `instcombine`, `src.ll` computes `tmp2` from `b` and the debug value for `tmp2` follows the result of the shift:
```llvm
define dso_local i64 @func(i64 noundef %0, i32 noundef %1) #0 !dbg !15 {
#dbg_value(i64 %0, !22, !DIExpression(), !23)
#dbg_value(i32 %1, !24, !DIExpression(), !23)
%3 = zext i32 %1 to i64, !dbg !25
#dbg_value(i64 %3, !26, !DIExpression(), !23)
%4 = shl i64 %3, 32, !dbg !27
#dbg_value(i64 %4, !28, !DIExpression(), !23)
%5 = add i64 %4, %0, !dbg !29
#dbg_value(i64 %5, !30, !DIExpression(), !23)
%6 = and i64 %5, 291, !dbg !31
#dbg_value(i64 %6, !32, !DIExpression(), !23)
ret i64 %6, !dbg !33
}
...
!28 = !DILocalVariable(name: "tmp2", scope: !15, file: !1, line: 5, type: !3)
```
After `instcombine`, `tgt.ll` simplifies the program computation to the final masked result. That optimization is fine for the program value, because the high 32 bits from `tmp2` are not demanded by `and 0x123`. However, the debug value for `tmp2` is also rewritten to a constant zero shift:
```llvm
define dso_local i64 @func(i64 noundef %0, i32 noundef %1) #0 !dbg !15 {
#dbg_value(i64 %0, !22, !DIExpression(), !23)
#dbg_value(i32 %1, !24, !DIExpression(), !23)
#dbg_value(i32 undef, !25, !DIExpression(DW_OP_LLVM_convert, 32, DW_ATE_unsigned, DW_OP_LLVM_convert, 64, DW_ATE_unsigned, DW_OP_stack_value), !23)
#dbg_value(i64 0, !26, !DIExpression(DW_OP_constu, 32, DW_OP_shl, DW_OP_stack_value), !23)
#dbg_value(!DIArgList(i64 0, i64 %0), !27, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !23)
%3 = and i64 %0, 291, !dbg !28
#dbg_value(i64 %3, !29, !DIExpression(), !23)
ret i64 %3, !dbg !30
}
...
!26 = !DILocalVariable(name: "tmp2", scope: !15, file: !1, line: 5, type: !3)
```
The resulting debug expression describes `0 << 32`, which is `0`, rather than `(uint64_t)b << 32`.
The final `and 0x123` makes the high 32-bit contribution irrelevant to the optimized program result, so replacing that contribution with zero is valid for the program value. However, that replacement is not a correct source-level debug value for `tmp2` itself.
## Environment
```text
clang version 23.0.0git
llvm-project revision: 58203500b5bd1628401c1a103eb264b7d78ea3bb
LLVM version 23.0.0git
LLDB version 23.0.0git
```
## Notes
I am not sure which part of `instcombine` is responsible for this. My guess is that the issue is related to simplifying the high-bit part of the expression because those bits are not demanded by the later `and 0x123`. That is fine for the optimized program result, but the same replacement should not be reused as the source variable value for `tmp2`.
Contributor guide
Research direction
Start with case.c and run the provided debugging script to generate src.ll and tgt.ll through mem2reg and instcombine. Compare the #dbg_value entries for tmp2 around the simplified shift and inspect the instcombine pass entry point. Done means LLDB reports 0x0000000100000000 for tmp2, or marks it unavailable rather than reporting zero.
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