[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