Bug type: miscompilation
Component: ScalarEvolution
Bug location: llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp: SCEVExpander::expandMinMaxExpr
SCEVExpander::visitSMaxExpr
SCEVExpander::visitUDivExpr
Issue title: miscompilation due to LoopVectorize making a function vulnerable to integer divide-by-zero
Labels: miscompilation
llvm:SCEV
Reported by: regehr

https://alive2.llvm.org/ce/z/Kx2anL

this function:
```llvm
define i64 @f(i64 %0) {
  br label %2

2:                                                ; preds = %5, %1
  %3 = phi i64 [ 0, %1 ], [ %6, %5 ]
  %4 = icmp slt i64 %3, %0
  br i1 %4, label %5, label %9

5:                                                ; preds = %2
  %6 = add i64 %3, 1
  %7 = udiv i64 42, %0
  %8 = icmp slt i64 %3, %7
  br i1 %8, label %2, label %9

9:                                                ; preds = %5, %2
  %10 = phi i64 [ 1, %2 ], [ 0, %5 ]
  ret i64 %10
}
```
is getting optimized to:
```lllvm
define noundef i64 @f(i64 %0) local_unnamed_addr #0 {
  %smax = tail call i64 @llvm.smax.i64(i64 %0, i64 0)
  %2 = udiv i64 42, %0
  %umin = tail call i64 @llvm.umin.i64(i64 %smax, i64 %2)
  %min.iters.check = icmp ult i64 %umin, 4
  br i1 %min.iters.check, label %scalar.ph.preheader, label %vector.ph

vector.ph:                                        ; preds = %1
  %3 = add nuw nsw i64 %umin, 1
  %n.mod.vf = and i64 %3, 3
  %4 = icmp eq i64 %n.mod.vf, 0
  %5 = select i1 %4, i64 4, i64 %n.mod.vf
  %n.vec = sub nsw i64 %3, %5
  br label %vector.body

vector.body:                                      ; preds = %vector.body, %vector.ph
  %index = phi i64 [ 0, %vector.ph ], [ %index.next, %vector.body ]
  %index.next = add nuw i64 %index, 4
  %6 = icmp eq i64 %index.next, %n.vec
  br i1 %6, label %scalar.ph.preheader, label %vector.body, !llvm.loop !0

scalar.ph.preheader:                              ; preds = %vector.body, %1
  %.ph = phi i64 [ 0, %1 ], [ %n.vec, %vector.body ]
  br label %scalar.ph

scalar.ph:                                        ; preds = %scalar.ph.preheader, %9
  %7 = phi i64 [ %10, %9 ], [ %.ph, %scalar.ph.preheader ]
  %8 = icmp slt i64 %7, %0
  br i1 %8, label %9, label %13

9:                                                ; preds = %scalar.ph
  %10 = add nuw nsw i64 %7, 1
  %11 = udiv i64 42, %0
  %12 = icmp ult i64 %7, %11
  br i1 %12, label %scalar.ph, label %13, !llvm.loop !3

13:                                               ; preds = %9, %scalar.ph
  %14 = phi i64 [ 1, %scalar.ph ], [ 0, %9 ]
  ret i64 %14
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i64 @llvm.smax.i64(i64, i64) #1

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i64 @llvm.umin.i64(i64, i64) #1

attributes #0 = { nofree norecurse nosync nounwind memory(none) }
attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

!0 = distinct !{!0, !1, !2}
!1 = !{!"llvm.loop.isvectorized", i32 1}
!2 = !{!"llvm.loop.unroll.runtime.disable"}
!3 = distinct !{!3, !2, !1}
```

the problem is that the optimized code can divide by zero even when the original code doesn't. to see this, pass 0 as an argument to `f`. I also independently verified on an x64-64 that the optimized code traps out with an FPE while the original code does not. it's LoopVectorize that's the culprit.

cc @nunoplopes @hatsunespica


Comments: author: dtcxzyw
body: cc @fhahn 

Fix patch:
commit b060661da8b3b53db55644e5e358bb2dca8b56d7
Author: Florian Hahn <flo@fhahn.com>
Date:   Thu Oct 17 13:55:20 2024 -0700

    [SCEVExpander] Expand UDiv avoiding UB when in seq_min/max. (#92177)
    
    Update SCEVExpander to introduce an SafeUDivMode, which is set
    when expanding operands of SCEVSequentialMinMaxExpr. In this mode,
    the expander will make sure that the divisor of the expanded UDiv is
    neither 0 nor poison.
    
    Fixes https://github.com/llvm/llvm-project/issues/89958.
    
    
    PR https://github.com/llvm/llvm-project/pull/92177

diff --git a/llvm/include/llvm/Transforms/Utils/ScalarEvolutionExpander.h b/llvm/include/llvm/Transforms/Utils/ScalarEvolutionExpander.h
index 5697d983c9ad..7dd754a2bc0d 100644
--- a/llvm/include/llvm/Transforms/Utils/ScalarEvolutionExpander.h
+++ b/llvm/include/llvm/Transforms/Utils/ScalarEvolutionExpander.h
@@ -126,6 +126,11 @@ class SCEVExpander : public SCEVVisitor<SCEVExpander, Value *> {
   /// "expanded" form.
   bool LSRMode;
 
+  /// When true, rewrite any divisors of UDiv expressions that may be 0 to
+  /// umax(Divisor, 1) to avoid introducing UB. If the divisor may be poison,
+  /// freeze it first.
+  bool SafeUDivMode = false;
+
   typedef IRBuilder<InstSimplifyFolder, IRBuilderCallbackInserter> BuilderType;
   BuilderType Builder;
 
diff --git a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
index c412d0398b95..39da38e49181 100644
--- a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
+++ b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
@@ -681,7 +681,21 @@ Value *SCEVExpander::visitUDivExpr(const SCEVUDivExpr *S) {
                          SCEV::FlagAnyWrap, /*IsSafeToHoist*/ true);
   }
 
-  Value *RHS = expand(S->getRHS());
+  const SCEV *RHSExpr = S->getRHS();
+  Value *RHS = expand(RHSExpr);
+  if (SafeUDivMode) {
+    bool GuaranteedNotPoison =
+        ScalarEvolution::isGuaranteedNotToBePoison(RHSExpr);
+    if (!GuaranteedNotPoison)
+      RHS = Builder.CreateFreeze(RHS);
+
+    // We need an umax if either RHSExpr is not known to be zero, or if it is
+    // not guaranteed to be non-poison. In the later case, the frozen poison may
+    // be 0.
+    if (!SE.isKnownNonZero(RHSExpr) || !GuaranteedNotPoison)
+      RHS = Builder.CreateIntrinsic(RHS->getType(), Intrinsic::umax,
+                                    {RHS, ConstantInt::get(RHS->getType(), 1)});
+  }
   return InsertBinop(Instruction::UDiv, LHS, RHS, SCEV::FlagAnyWrap,
                      /*IsSafeToHoist*/ SE.isKnownNonZero(S->getRHS()));
 }
@@ -1376,11 +1390,14 @@ Value *SCEVExpander::visitSignExtendExpr(const SCEVSignExtendExpr *S) {
 Value *SCEVExpander::expandMinMaxExpr(const SCEVNAryExpr *S,
                                       Intrinsic::ID IntrinID, Twine Name,
                                       bool IsSequential) {
+  bool PrevSafeMode = SafeUDivMode;
+  SafeUDivMode |= IsSequential;
   Value *LHS = expand(S->getOperand(S->getNumOperands() - 1));
   Type *Ty = LHS->getType();
   if (IsSequential)
     LHS = Builder.CreateFreeze(LHS);
   for (int i = S->getNumOperands() - 2; i >= 0; --i) {
+    SafeUDivMode = (IsSequential && i != 0) || PrevSafeMode;
     Value *RHS = expand(S->getOperand(i));
     if (IsSequential && i != 0)
       RHS = Builder.CreateFreeze(RHS);
@@ -1395,6 +1412,7 @@ Value *SCEVExpander::expandMinMaxExpr(const SCEVNAryExpr *S,
     }
     LHS = Sel;
   }
+  SafeUDivMode = PrevSafeMode;
   return LHS;
 }
 


Regression tests:
file: <issue>
commands: opt -p loop-vectorize -force-vector-width=4 -S %s
tests: test_name: f
test_body: define i64 @f(i64 %0) {
  br label %2

2:                                                ; preds = %5, %1
  %3 = phi i64 [ 0, %1 ], [ %6, %5 ]
  %4 = icmp slt i64 %3, %0
  br i1 %4, label %5, label %9

5:                                                ; preds = %2
  %6 = add i64 %3, 1
  %7 = udiv i64 42, %0
  %8 = icmp slt i64 %3, %7
  br i1 %8, label %2, label %9

9:                                                ; preds = %5, %2
  %10 = phi i64 [ 1, %2 ], [ 0, %5 ]
  ret i64 %10
}