[llvm] [LICM] Relax overflow flag requirements for eq/ne in hoistAdd/hoistSub (PR #183145)

Yunbo Ni via llvm-commits llvm-commits at lists.llvm.org
Thu Mar 5 07:53:00 PST 2026


cardigan1008 wrote:

Hi @SavchenkoValeriy , there is a related miscompilation case:

```llvm
define i32 @test(i32 %x) {
entry:
  br label %loop

loop:
  %iv = phi i32 [ -1, %entry ], [ %iv.next, %backedge ]
  %arith = add i32 %iv, %x
  %x_check = icmp samesign eq i32 %arith, 1
  br i1 %x_check, label %exit, label %backedge

backedge:
  %iv.next = add i32 %iv, 1
  br label %loop

exit:
  ret i32 1
}

define i32 @main(i32 %argc, ptr %argv) {
entry:
  %r = call i32 @test(i32 1)
  ret i32 %r
}
```

Transformed with opt built on this patch:

```llvm
; ModuleID = 'src.ll'
source_filename = "src.ll"

define i32 @test(i32 %x) {
entry:
  %invariant.op = sub i32 1, %x
  br label %loop

loop:                                             ; preds = %backedge, %entry
  %iv = phi i32 [ -1, %entry ], [ %iv.next, %backedge ]
  %x_check = icmp samesign eq i32 %iv, %invariant.op
  br i1 %x_check, label %exit, label %backedge

backedge:                                         ; preds = %loop
  %iv.next = add i32 %iv, 1
  br label %loop

exit:                                             ; preds = %loop
  ret i32 1
}

define i32 @main(i32 %argc, ptr %argv) {
entry:
  %r = call i32 @test(i32 1)
  ret i32 %r
}
```

Running them with llubi got different outputs:

```sh
$ llubi src.ll
$ echo $?
1
$ llubi tgt.ll
UB triggered: Branch on poison
Exited with immediate UB.
Stacktrace:
    br i1 %x_check, label %exit, label %backedge at @test
    %r = call i32 @test(i32 1) at @main
```

> Note: This is a review assisted with a self-built agent. The reproducer was validated manually. Please let me know if anything is wrong.

The patch relaxes the requirements for hoisting `add`/`sub` out of loops in LICM when the `icmp`
predicate is equality (`eq`/`ne`). It performs the rewrite:

- `LV + C1 == C2` → `LV == C2 - C1`

without requiring overflow flags (`nsw`/`nuw`), relying on the fact that wrapping add/sub are
bijections for equality under modular arithmetic.

However, it fails to drop the `samesign` flag on the transformed `icmp`. After rewriting, the
compare becomes:

- `icmp samesign eq i32 %iv, %invariant.op`

where `%invariant.op` is `1 - %x`.

In the first iteration (again with `%x = 1`), `%iv = -1` (negative) and `%invariant.op = 0`
(non-negative). The operands have different signs, so the `samesign` constraint is violated and
the result of the `icmp` becomes **poison**.

Branching on this poison value causes undefined behavior (“branch on poison”).

**Expected fix:** drop/clear the `samesign` flag when performing this transformation (or bail out
when `samesign` is present), because the transformed operands are not guaranteed to have the same
sign even when the original `icmp samesign` was valid.

https://github.com/llvm/llvm-project/pull/183145


More information about the llvm-commits mailing list