Bug type: miscompilation
Component: InstructionSimplify
Bug location: llvm/lib/Analysis/InstructionSimplify.cpp: llvm::simplifyWithOpReplaced
simplifyAndOrWithICmpEq
simplifySelectWithICmpEq
Issue title: Miscompilation with libstdc++'s std::optional<int> and -O1
Labels: regression
miscompilation
llvm:analysis
Reported by: cadubentzen

https://godbolt.org/z/KjEreWf57

With Clang 18.1 and libstdc++, we get different behavior when compared to Clang 17 in the code below.
EDIT: requires at least -O1.

```cpp
#include <optional>
#include <iostream>

// defined in a separate compilation unit
int takeIntRefAndReturn0(int&);

std::optional<int> shouldReturnEmptyOptional() {
  int v = 5;
  if (takeIntRefAndReturn0(v))
    return v;
  return std::nullopt;
}

int main() {
  auto opt = shouldReturnEmptyOptional();
  if (opt && *opt > 0) {
    std::cout << "SHOULD NOT BE PRINTED: *opt = " << *opt << std::endl;
    return 1;
  }
  std::cout << "SHOULD BE PRINTED" << std::endl;
}
```

With Clang 17, we get `SHOULD BE PRINTED`, while with with Clang 18.1 we get  `SHOULD NOT BE PRINTED: *opt = 5`.

With git-bisect, I found that this is caused by https://github.com/llvm/llvm-project/commit/060de415af335fdd82910f409e2be3b8457eaa43.

An isomorphic example to reproduce this (https://godbolt.org/z/9PsjY17sT):
```cpp
int takeIntRefReturn0(int &);
void assertNotReached(int);

static bool logicalAnd(bool a, bool b) { return a && b; }

int main() {
  int v4;
  bool v3;
  {
    int v1 = 5;
    int v2 = takeIntRefReturn0(v1);
    v3 = v2 != 0;
    if (v3)
      v4 = v1;
  }
  // Move  to a function so that && is not short cutted.
  // v4 will be undefined if v2 == 0, but v3 is false, so the branch shouldn't be entered.
  if (logicalAnd(v3, v4 > 0))
    assertNotReached(v4);

  return 0;
}
```
Note in the generated LLVM IR that
```llvm
%6 = icmp sgt i32 %5, 0
%7 = and i1 %3, %6
br i1 %7, label %8, label %9
``` 
was reduced to only
```llvm
%6 = icmp sgt i32 %5, 0
br i1 %6, label %7, label %8
```

Comments: author: dtcxzyw
body: Reproducer: https://alive2.llvm.org/ce/z/p4ZGxe
```
; bin/opt -passes=instcombine test.ll -S
define i1 @src(i32 noundef %x, i32 %y) {
  %3 = icmp ne i32 %x, 0
  %5 = select i1 %3, i32 %y, i32 undef
  %6 = icmp sgt i32 %5, 0
  %7 = and i1 %3, %6
  br i1 %7, label %if.then, label %if.else

if.then:
  call void @use(i32 noundef %5)
  ret i1 true

if.else:
  ret i1 false
}

define i1 @tgt(i32 noundef %x, i32 %y) {
  %.not = icmp eq i32 %x, 0
  %1 = select i1 %.not, i32 undef, i32 %y
  %2 = icmp sgt i32 %1, 0
  br i1 %2, label %if.then, label %if.else

if.then:                                          ; preds = %0
  call void @use(i32 noundef %1)
  ret i1 true

if.else:                                          ; preds = %0
  ret i1 false
}

declare void @use(i32 %x)
```
cc @nikic
author: nikic
body: A bit simpler using just instsimplify: https://alive2.llvm.org/ce/z/tnkf6A
author: nikic
body: I expect the fix here is to perform replacements with `Q.getWithoutUndef()`. But probably not just here, but also in other places using simplifyWithOpReplaced(). (Possibly making it always use that mode.)
author: cadubentzen
body: Thanks a lot for fixing this so quickly! I wonder if this should be included in 18.1.9?
author: nikic
body: The LLVM 18 branch is already closed, there will be no further releases.
author: thesamesam
body: I'll add the milestone anyway as it's easier to track for downstream backports then.

Fix patch:
commit de29b850f03092195bf21f3a39402adb4ed3c216
Author: Nikita Popov <npopov@redhat.com>
Date:   Tue Jul 16 11:40:04 2024 +0200

    [InstSimplify] Fix simplifyAndOrWithICmpEq with undef refinement (#98898)
    
    The final case in Simplify (where Res == Absorber and the predicate is
    inverted) is not generally safe when the simplification is a refinement.
    In particular, we may simplify assuming a specific value for undef, but
    then chose a different one later.
    
    However, it *is* safe to refine poison in this context, unlike in the
    equivalent select folds. This is the reason why this fold did not use
    AllowRefinement=false in the first place, and using that option would
    introduce a lot of test regressions.
    
    This patch takes the middle path of disabling undef refinements in
    particular using the getWithoutUndef() SimplifyQuery option. However,
    this option doesn't actually work in this case, because the problematic
    fold is inside constant folding, and we currently don't propagate this
    option all the way from InstSimplify over ConstantFolding to
    ConstantFold. Work around this by explicitly checking for undef operands
    in simplifyWithOpReplaced().
    
    Finally, make sure that places where AllowRefinement=false also use
    Q.getWithoutUndef(). I don't have a specific test case for this (the
    original one does not work because we don't simplify selects with
    constant condition in this mode in the first place) but this seems like
    the correct thing to do to be conservative.
    
    Fixes https://github.com/llvm/llvm-project/issues/98753.

diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index 242c200f7ef1..3a7ae577bb06 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -1975,13 +1975,16 @@ static Value *simplifyAndOrWithICmpEq(unsigned Opcode, Value *Op0, Value *Op1,
     return nullptr;
   };
 
-  if (Value *Res =
-          simplifyWithOpReplaced(Op1, A, B, Q, /* AllowRefinement */ true,
-                                 /* DropFlags */ nullptr, MaxRecurse))
+  // In the final case (Res == Absorber with inverted predicate), it is safe to
+  // refine poison during simplification, but not undef. For simplicity always
+  // disable undef-based folds here.
+  if (Value *Res = simplifyWithOpReplaced(Op1, A, B, Q.getWithoutUndef(),
+                                          /* AllowRefinement */ true,
+                                          /* DropFlags */ nullptr, MaxRecurse))
     return Simplify(Res);
-  if (Value *Res =
-          simplifyWithOpReplaced(Op1, B, A, Q, /* AllowRefinement */ true,
-                                 /* DropFlags */ nullptr, MaxRecurse))
+  if (Value *Res = simplifyWithOpReplaced(Op1, B, A, Q.getWithoutUndef(),
+                                          /* AllowRefinement */ true,
+                                          /* DropFlags */ nullptr, MaxRecurse))
     return Simplify(Res);
 
   return nullptr;
@@ -4300,6 +4303,9 @@ static Value *simplifyWithOpReplaced(Value *V, Value *Op, Value *RepOp,
                                      bool AllowRefinement,
                                      SmallVectorImpl<Instruction *> *DropFlags,
                                      unsigned MaxRecurse) {
+  assert((AllowRefinement || !Q.CanUseUndef) &&
+         "If AllowRefinement=false then CanUseUndef=false");
+
   // Trivial replacement.
   if (V == Op)
     return RepOp;
@@ -4347,6 +4353,11 @@ static Value *simplifyWithOpReplaced(Value *V, Value *Op, Value *RepOp,
     } else {
       NewOps.push_back(InstOp);
     }
+
+    // Bail out if any operand is undef and SimplifyQuery disables undef
+    // simplification. Constant folding currently doesn't respect this option.
+    if (isa<UndefValue>(NewOps.back()) && !Q.CanUseUndef)
+      return nullptr;
   }
 
   if (!AnyReplaced)
@@ -4467,6 +4478,11 @@ Value *llvm::simplifyWithOpReplaced(Value *V, Value *Op, Value *RepOp,
                                     const SimplifyQuery &Q,
                                     bool AllowRefinement,
                                     SmallVectorImpl<Instruction *> *DropFlags) {
+  // If refinement is disabled, also disable undef simplifications (which are
+  // always refinements) in SimplifyQuery.
+  if (!AllowRefinement)
+    return ::simplifyWithOpReplaced(V, Op, RepOp, Q.getWithoutUndef(),
+                                    AllowRefinement, DropFlags, RecursionLimit);
   return ::simplifyWithOpReplaced(V, Op, RepOp, Q, AllowRefinement, DropFlags,
                                   RecursionLimit);
 }
@@ -4606,7 +4622,7 @@ static Value *simplifySelectWithICmpEq(Value *CmpLHS, Value *CmpRHS,
                                        Value *TrueVal, Value *FalseVal,
                                        const SimplifyQuery &Q,
                                        unsigned MaxRecurse) {
-  if (simplifyWithOpReplaced(FalseVal, CmpLHS, CmpRHS, Q,
+  if (simplifyWithOpReplaced(FalseVal, CmpLHS, CmpRHS, Q.getWithoutUndef(),
                              /* AllowRefinement */ false,
                              /* DropFlags */ nullptr, MaxRecurse) == TrueVal)
     return FalseVal;


Regression tests:
file: llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll
commands: opt -S -passes=instsimplify < %s
tests: test_name: and_is_constant
test_body: define i1 @and_is_constant(ptr %arg, ptr %arg2) {
  %icmp = icmp eq ptr %arg, %arg2
  %call = call i1 @llvm.is.constant.i1(i1 %icmp)
  %and = and i1 %call, %icmp
  ret i1 %and
}

; Function Attrs: convergent nocallback nofree nosync nounwind willreturn memory(none)
declare i1 @llvm.is.constant.i1(i1) #0

attributes #0 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }

test_name: pr98753
test_body: define i1 @pr98753(i32 noundef %x, i32 %y) {
  %cmp1 = icmp ne i32 %x, 0
  %sel = select i1 %cmp1, i32 %y, i32 undef
  %cmp2 = icmp sgt i32 %sel, 0
  %and = and i1 %cmp1, %cmp2
  ret i1 %and
}