llvm / llvm/llvm-project

[DebugInfo][InstCombine] Folding a `bcmp` equality test into `icmp` gives opposite source variables the same negated debug expression

Open
#218,655 1 comment 0 reactions 0 assignees View on GitHub
debuginfo llvm:instcombine
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Description

`bcmp` compares two byte sequences and returns zero when they are equal and a nonzero value otherwise. In this reproducer, `not_equal` stores the return value and `equal` stores the result of comparing `not_equal` with zero, so the variable names directly reflect their opposite conditions.

InstCombine recognizes the fixed-size `bcmp` call followed by an equality comparison and replaces both operations with a direct integer `icmp eq`. This preserves the program behavior, but the optimized IR describes both `not_equal` and `equal` with the same `icmp` result and a `DIExpression` beginning with `DW_OP_not`. The inversion is appropriate when reconstructing the zero/nonzero condition represented by `not_equal`, but `equal` should directly follow the equality comparison without the inversion.

Consequently, LLDB reports `not_equal = 255` and `equal = true` for unequal inputs after InstCombine. These values contradict the source relationship between the variables: a nonzero `not_equal` requires `equal` to be false.

## Reproducer

`case.c`:

```c
#include
#include
#include

bool bcmp_to_icmp(uint64_t x, uint64_t y) {
int not_equal = bcmp(&x, &y, sizeof(x));
bool equal = (not_equal == 0);
return equal;
}

int main(void) { return bcmp_to_icmp(0x12345678, 0x12345679); }
```

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,instsimplify" -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
```

Here are the complete [src.ll and tgt.ll](https://godbolt.org/z/oMWqh3Ydb).

`lldb-src-commands.txt`:

```text
breakpoint set --file case.c --line 7
run
frame variable not_equal
next
frame variable equal
quit
```

`lldb-tgt-commands.txt`:

```text
breakpoint set --file case.c --line 8
run
frame variable not_equal
frame variable equal
quit
```

Run LLDB on the binaries before and after InstCombine:

```sh
lldb src.out -s lldb-src-commands.txt
lldb tgt.out -s lldb-tgt-commands.txt
```

## Observed Behavior

Before InstCombine, the `bcmp` result and its equality comparison have separate debug records:

```llvm
%call = call i32 @bcmp(ptr noundef %x.addr, ptr noundef %y.addr, i64 noundef 8) #2, !dbg !23
#dbg_value(i32 %call, !24, !DIExpression(), !26)
%cmp = icmp eq i32 %call, 0, !dbg !27
#dbg_value(i1 %cmp, !28, !DIExpression(DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !26)
```

After InstCombine, both source variables are described using `%.not` and a negated expression:

```llvm
%.not = icmp eq i64 %x, %y, !dbg !22
#dbg_value(i1 %.not, !23, !DIExpression(DW_OP_not, DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 32, DW_ATE_unsigned, DW_OP_stack_value), !20)
#dbg_value(i1 %.not, !25, !DIExpression(DW_OP_not, DW_OP_LLVM_convert, 1, DW_ATE_unsigned, DW_OP_LLVM_convert, 8, DW_ATE_unsigned, DW_OP_stack_value), !20)
```

In the optimized IR, `!23` is `not_equal` and `!25` is `equal`:

```llvm
!23 = !DILocalVariable(name: "not_equal", scope: !9, file: !1, line: 6, type: !24)
!25 = !DILocalVariable(name: "equal", scope: !9, file: !1, line: 7, type: !12)
```

For the unequal inputs in the reproducer, LLDB reports a nonzero `not_equal` on line 7 and `equal = false` on line 8 before InstCombine:

```text
== src.out ==
(int) not_equal = -1
(bool) equal = false
```

After InstCombine, LLDB reports values that contradict each other:

```text
== tgt.out ==
(int) not_equal = 255
(bool) equal = true
```

## Expected Behavior

The debug record for `equal` should describe the direct result of `%.not` rather than applying `DW_OP_not`. LLDB should report a nonzero `not_equal` on line 7 and `equal = false` on line 8. If either source value cannot be represented correctly after the transformation, that variable should be unavailable instead of being reported with a contradictory value.

## Environment

This optimization requires `src.ll` to carry a concrete target architecture. I reproduced it on both x86_64 and AArch64.

```text
clang version 24.0.0git
llvm-project revision: f6ea145aa8e89631ae04f72df32580b20256d40c

LLVM version 24.0.0git
lldb version 24.0.0git
```

Contributor guide

Open the contributing guide

Research direction

Start with case.c and reproduce the difference by running the listed opt and clang commands, then compare src.ll with tgt.ll around the debug records for not_equal and equal. Trace the InstCombine transformation that turns bcmp into the direct icmp. Done means the optimized debug information reports not_equal as nonzero and equal as false, or leaves an unrepresentable value unavailable.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
compilers, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.