Bug type: miscompilation
Component: ScalarEvolution
Bug location: llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp: SCEVExpander::generateOverflowCheck
Issue title: [LoopVectorize] Miscompile with masked IV
Labels: miscompilation
release:backport
release:cherry-pick-failed
llvm:SCEV
Reported by: nikic

```llvm
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

; out[i] = x[(i * 5) & 16]
define void @test(ptr noalias %x, ptr noalias %out) {
start:
  br label %loop

loop:
  %iv = phi i64 [ 0, %start ], [ %iv.next, %loop ]
  %iv.next = add nuw nsw i64 %iv, 1
  %iv.mul5 = mul nuw nsw i64 %iv, 5
  %iv.mul5.masked = and i64 %iv.mul5, 15
  %gep = getelementptr inbounds nuw i32, ptr %x, i64 %iv.mul5.masked
  %v = load i32, ptr %gep, align 4
  %out.i = getelementptr inbounds nuw i32, ptr %out, i64 %iv
  store i32 %v, ptr %out.i, align 4
  %exitcond.not = icmp eq i64 %iv.next, 16
  br i1 %exitcond.not, label %exit, label %loop

exit:
  ret void
}
```
Produces:
```llvm
define void @test(ptr noalias %x, ptr noalias %out) {
start:
  br i1 false, label %scalar.ph, label %vector.ph

vector.ph:
  br label %vector.body

vector.body:
  %index = phi i64 [ 0, %vector.ph ], [ %index.next, %vector.body ]
  %0 = mul nuw nsw i64 %index, 5
  %1 = and i64 %0, 15
  %2 = getelementptr inbounds nuw i32, ptr %x, i64 %1
  %wide.vec = load <20 x i32>, ptr %2, align 4
  %strided.vec = shufflevector <20 x i32> %wide.vec, <20 x i32> poison, <4 x i32> <i32 0, i32 5, i32 10, i32 15>
  %3 = getelementptr inbounds nuw i32, ptr %out, i64 %index
  store <4 x i32> %strided.vec, ptr %3, align 4
  %index.next = add nuw i64 %index, 4
  %4 = icmp eq i64 %index.next, 12
  br i1 %4, label %middle.block, label %vector.body

middle.block:
  br label %scalar.ph

scalar.ph:
  %bc.resume.val = phi i64 [ 12, %middle.block ], [ 0, %start ]
  br label %loop

loop:
  %iv = phi i64 [ %bc.resume.val, %scalar.ph ], [ %iv.next, %loop ]
  %iv.next = add nuw nsw i64 %iv, 1
  %iv.mul5 = mul nuw nsw i64 %iv, 5
  %iv.mul5.masked = and i64 %iv.mul5, 15
  %gep = getelementptr inbounds nuw i32, ptr %x, i64 %iv.mul5.masked
  %v = load i32, ptr %gep, align 4
  %out.i = getelementptr inbounds nuw i32, ptr %out, i64 %iv
  store i32 %v, ptr %out.i, align 4
  %exitcond.not = icmp eq i64 %iv.next, 16
  br i1 %exitcond.not, label %exit, label %loop

exit:
  ret void
}
```

What's happening here with the load and shufflevector doesn't really make sense. It seems like the masking on the IV ended up being ignored?

Comments: author: nikic
body: cc @fhahn 
author: fhahn
body: Looks like an SCEVExpander issue, will share a fix soon
author: nikic
body: Backport PR: https://github.com/llvm/llvm-project/pull/157410

Fix patch:
commit f8972c8280d28660aaff888c093a9e01b9ee71e6
Author: Florian Hahn <flo@fhahn.com>
Date:   Fri Sep 5 16:13:11 2025 +0100

    [SCEVExp] Fix early exit in ComputeEndCheck. (#156910)
    
    ComputeEndCheck incorrectly returned false for unsigned predicates
    starting at zero and a positive step.
    
    The AddRec could still wrap if Step * trunc ExitCount wraps or trunc
    ExitCount strips leading 1s.
    
    Fixes https://github.com/llvm/llvm-project/issues/156849.
    
    PR: https://github.com/llvm/llvm-project/pull/156910

diff --git a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
index 3e3d51bffff3..7c12dac73f3d 100644
--- a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
+++ b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
@@ -2187,8 +2187,15 @@ Value *SCEVExpander::generateOverflowCheck(const SCEVAddRecExpr *AR,
   // negative. If Step is known to be positive or negative, only create
   // either 1. or 2.
   auto ComputeEndCheck = [&]() -> Value * {
-    // Checking <u 0 is always false.
-    if (!Signed && Start->isZero() && SE.isKnownPositive(Step))
+    // Checking <u 0 is always false, if (Step * trunc ExitCount) does not wrap.
+    // TODO: Predicates that can be proven true/false should be discarded when
+    // the predicates are created, not late during expansion.
+    if (!Signed && Start->isZero() && SE.isKnownPositive(Step) &&
+        DstBits < SrcBits &&
+        ExitCount == SE.getZeroExtendExpr(SE.getTruncateExpr(ExitCount, ARTy),
+                                          ExitCount->getType()) &&
+        SE.willNotOverflow(Instruction::Mul, Signed, Step,
+                           SE.getTruncateExpr(ExitCount, ARTy)))
       return ConstantInt::getFalse(Loc->getContext());
 
     // Get the backedge taken count and truncate or extended to the AR type.


Regression tests:
file: llvm/test/Transforms/LoopVectorize/interleaved-accesses-requiring-scev-predicates.ll
commands: opt -S -passes=loop-vectorize -force-vector-width=4 -force-vector-interleave=1 -enable-interleaved-mem-accesses=true %s
tests: test_name: wrap_predicate_for_interleave_group_wraps_for_known_trip_count
test_body: target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

define void @wrap_predicate_for_interleave_group_wraps_for_known_trip_count(ptr noalias %x, ptr noalias %out) {
start:
  br label %loop

loop:                                             ; preds = %loop, %start
  %iv = phi i64 [ 0, %start ], [ %iv.next, %loop ]
  %iv.next = add nuw nsw i64 %iv, 1
  %iv.mul5 = mul nuw nsw i64 %iv, 5
  %iv.mul5.masked = and i64 %iv.mul5, 15
  %gep = getelementptr inbounds nuw i32, ptr %x, i64 %iv.mul5.masked
  %v = load i32, ptr %gep, align 4
  %out.i = getelementptr inbounds nuw i32, ptr %out, i64 %iv
  store i32 %v, ptr %out.i, align 4
  %exitcond.not = icmp eq i64 %iv.next, 16
  br i1 %exitcond.not, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}

additional_args: -src-unroll=16 -tgt-unroll=16