Bug type: miscompilation
Component: InstCombine
Bug location: llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp: InstCombinerImpl::sinkNotIntoLogicalOp
Issue title: InstCombine miscompilation
Labels: miscompilation
llvm:instcombine
generated by fuzzer
Reported by: saethlin

Like https://github.com/llvm/llvm-project/issues/139866 this is from a fuzzer that generates Rust MIR: https://github.com/cbeuw/rustlantis.

I've tried to reduce the MIR, but I think there's a pretty complicated structure required because I can't get the MIR very small.
https://godbolt.org/z/G6qsWP57T

The program seems to miscompile starting at `rustc -Zmir-opt-level=0 -Copt-level=1 -Cllvm-args=-opt-bisect-limit=159`

Comments: author: dtcxzyw
body: Reproducer: https://alive2.llvm.org/ce/z/l2ArgJ

> I've tried to reduce the MIR, but I think there's a pretty complicated structure required because I can't get the MIR very small.

FYI you can use https://github.com/dtcxzyw/llvm-ub-aware-interpreter?tab=readme-ov-file#automatic-ub-free-test-case-reduction-for-middle-end-miscompilation-bugs to reduce csmith/rustlantics-generated miscompilation reproducers.

author: saethlin
body: I'd love to use llubi but it doesn't compile against the tip of master or the llvm submodule in rust-lang/rust. So I'm not really sure how to get a working build on my system.
author: dtcxzyw
body: > I'd love to use llubi but it doesn't compile against the tip of master or the llvm submodule in rust-lang/rust. So I'm not really sure how to get a working build on my system.

Fixed. It was broken by recent changes to ValueTracking: https://github.com/llvm/llvm-project/pull/142384

Fix patch:
commit e2c698c7e836306f1a25c67597ae9e25a1fcc575
Author: Yingwei Zheng <dtcxzyw2333@gmail.com>
Date:   Wed Jun 4 17:48:01 2025 +0800

    [InstCombine] Fix miscompilation in `sinkNotIntoLogicalOp` (#142727)
    
    Consider the following case:
    ```
    define i1 @src(i8 %x) {
      %cmp = icmp slt i8 %x, -1
      %not1 = xor i1 %cmp, true
      %or = or i1 %cmp, %not1
      %not2 = xor i1 %or, true
      ret i1 %not2
    }
    ```
    `sinkNotIntoLogicalOp(%or)` calls `freelyInvert(%cmp,
    /*IgnoredUser=*/%or)` first. However, as `%cmp` is also used by `Op1 =
    %not1`, the RHS of `%or` is set to `%cmp.not = xor i1 %cmp, true`. Thus
    `Op1` is out of date in the second call to `freelyInvert`. Similarly,
    the second call may change `Op0`. According to the analysis above, I
    decided to avoid this fold when one of the operands is also a user of
    the other.
    
    Closes https://github.com/llvm/llvm-project/issues/142518.

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 2fb4bfecda8a..c6c231f81c4a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -4513,6 +4513,12 @@ bool InstCombinerImpl::sinkNotIntoLogicalOp(Instruction &I) {
   if (Op0 == Op1)
     return false;
 
+  // If one of the operands is a user of the other,
+  // freelyInvert->freelyInvertAllUsersOf will change the operands of I, which
+  // may cause miscompilation.
+  if (match(Op0, m_Not(m_Specific(Op1))) || match(Op1, m_Not(m_Specific(Op0))))
+    return false;
+
   Instruction::BinaryOps NewOpc =
       match(&I, m_LogicalAnd()) ? Instruction::Or : Instruction::And;
   bool IsBinaryOp = isa<BinaryOperator>(I);


Regression tests:
file: llvm/test/Transforms/InstCombine/pr142518.ll
commands: opt -S -passes=instcombine < %s
tests: test_name: pr142518
test_body: define i8 @pr142518(ptr %p, i8 %x, i1 %c) #0 {
entry:
  %flag = alloca i8, align 1
  %cmp = icmp slt i8 %x, -1
  br label %loop

loop:                                             ; preds = %loop, %entry
  %phi = phi i1 [ %cmp, %entry ], [ %c, %loop ]
  %not1 = xor i1 %phi, true
  %or = or i1 %cmp, %not1
  %not2 = xor i1 %or, true
  %ext2 = zext i1 %not2 to i8
  store i8 %ext2, ptr %p, align 1
  store i8 1, ptr %flag, align 1
  %flagv = load i8, ptr %flag, align 1
  %cond = icmp eq i8 %flagv, 0
  br i1 %cond, label %loop, label %exit

exit:                                             ; preds = %loop
  %not3 = xor i1 %or, true
  %ext3 = zext i1 %not3 to i8
  ret i8 %ext3
}

attributes #0 = { "instcombine-no-verify-fixpoint" }