Bug type: crash
Component: LoopVectorize
Bug location: llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.h: createWidenCast
Issue title: [LV] Assert "OpType must match" in VPIRFlags::intersectFlags
Labels: vectorizers
crash-on-valid
Reported by: sjoerdmeijer

This IR:

```
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux-gnu"

define void @_Z1hiPA2_bPA2_c(ptr %arrayidx6, ptr %arrayidx20) {
entry:
  br label %for.body

for.cond.cleanup.loopexit:                        ; preds = %for.body
  ret void

for.body:                                         ; preds = %for.body, %entry
  %indvars.iv1 = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
  %0 = load i8, ptr %arrayidx6, align 1
  %loadedv = zext i8 %0 to i32
  %conv9 = trunc i32 %loadedv to i16
  store i16 %conv9, ptr null, align 2
  %1 = zext i8 %0 to i16
  store i16 %1, ptr %arrayidx20, align 2
  %indvars.iv.next = add i64 %indvars.iv1, 2
  %tobool.not = icmp eq i64 %indvars.iv.next, 0
  br i1 %tobool.not, label %for.cond.cleanup.loopexit, label %for.body
}
```

Compiled with `opt --passes=loop-vectorize -S `will triggers this assert:

```
opt: /root/llvm-project/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp:404: void llvm::VPIRFlags::intersectFlags(const llvm::VPIRFlags&): Assertion `OpType == Other.OpType && "OpType must match"' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
Stack dump:
0.	Program arguments: /opt/compiler-explorer/clang-assertions-trunk/bin/opt -o /app/output.s -S --passes=loop-vectorize <source>
1.	Running pass "function(loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>)" on module "<source>"
2.	Running pass "loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>" on function "_Z1hiPA2_bPA2_c"
 #0 0x0000000005944c48 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x5944c48)
 #1 0x0000000005941af4 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
 #2 0x0000718d16242520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #3 0x0000718d162969fc pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x969fc)
 #4 0x0000718d16242476 gsignal (/lib/x86_64-linux-gnu/libc.so.6+0x42476)
 #5 0x0000718d162287f3 abort (/lib/x86_64-linux-gnu/libc.so.6+0x287f3)
 #6 0x0000718d1622871b (/lib/x86_64-linux-gnu/libc.so.6+0x2871b)
 #7 0x0000718d16239e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
 #8 0x00000000038376c4 (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x38376c4)
 #9 0x0000000003888c73 llvm::VPlanTransforms::cse(llvm::VPlan&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x3888c73)
#10 0x000000000367d30e llvm::LoopVectorizationPlanner::executePlan(llvm::ElementCount, unsigned int, llvm::VPlan&, llvm::InnerLoopVectorizer&, llvm::DominatorTree*, bool) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x367d30e)
```

See also:  https://godbolt.org/z/nxzGcccff

Can be triggered from source-code:

```
#include <algorithm>
short a;
extern short b[];
int c;
void h(int d, bool e[][2], char f[][2]) {
#pragma clang loop vectorize_predicate(enable)
  for (int g(d); g; g += 2) {
    a = f[g][g] - e[g][g] - c;
    b[g] = std::max(d ? (unsigned long long)e[g][g] : 0, (unsigned long long)0);
  }
}
```

Comments: author: sjoerdmeijer
body: CC: @fhahn 

Fix patch:
commit 9fe1f29541109ac10e1399deb85da0507135f836
Author: Luke Lau <luke@igalia.com>
Date:   Mon Oct 20 18:39:16 2025 +0800

    [VPlan] Set flags when constructing zexts using VPWidenCastRecipe (#164198)
    
    createWidenCast doesn't set the flag type, so when we simplify trunc
    (zext nneg x) -> zext x we would hit an assertion in CSE that the flag
    types don't match with other VPWidenCastRecipes that weren't simplified.
    
    This fixes it the same way trunc flags are handled too.
    
    As an aside I think it should be correct to preserve the nneg flag in
    this case since the input operand is still non-negative after the
    transform. But that's left to another PR.
    
    Fixes https://github.com/llvm/llvm-project/issues/164171

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.h b/llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.h
index 7651ba16b35c..3fed003282f2 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.h
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.h
@@ -325,6 +325,8 @@ public:
     VPIRFlags Flags;
     if (Opcode == Instruction::Trunc)
       Flags = VPIRFlags::TruncFlagsTy(false, false);
+    else if (Opcode == Instruction::ZExt)
+      Flags = VPIRFlags::NonNegFlagsTy(false);
     return tryInsertInstruction(
         new VPWidenCastRecipe(Opcode, Op, ResultTy, Flags));
   }


Regression tests:
file: llvm/test/Transforms/LoopVectorize/cse-casts.ll
commands: opt -p loop-vectorize -force-vector-width=4 -force-vector-interleave=2 -S %s
tests: test_name: preserve_flags_narrowing_extends_and_truncs
test_body: define void @preserve_flags_narrowing_extends_and_truncs(ptr noalias %A, ptr noalias %B, ptr noalias %C) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %gep.A = getelementptr inbounds i8, ptr %A, i64 %iv
  %l = load i8, ptr %gep.A, align 1
  %l.ext = zext i8 %l to i64
  %gep.C = getelementptr inbounds i8, ptr %C, i64 %iv
  store i64 %l.ext, ptr %gep.C, align 4
  %gep.B = getelementptr inbounds i8, ptr %B, i64 %iv
  %l.1 = load i8, ptr %gep.B, align 1
  %masked = and i8 %l.1, 1
  %l.1.trunc = trunc i8 %l.1 to i1
  %sel.0 = select i1 %l.1.trunc, float 1.000000e+00, float 0.000000e+00
  %masked.trunc = trunc i8 %masked to i1
  %sel.1 = select i1 %masked.trunc, float 3.000000e+00, float %sel.0
  %bc = bitcast float %sel.1 to i32
  %bc.trunc = trunc i32 %bc to i8
  store i8 %bc.trunc, ptr %gep.B, align 1
  %iv.next = add i64 %iv, 1
  %ec = icmp eq i64 %iv, 1
  br i1 %ec, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}

test_name: simplified_cast_preserves_irflag_type
test_body: define void @simplified_cast_preserves_irflag_type(ptr noalias %p, ptr noalias %q, ptr noalias %r) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %x = load i8, ptr %p, align 1
  %x.i32 = zext i8 %x to i32
  %trunc = trunc i32 %x.i32 to i16
  store i16 %trunc, ptr %q, align 2
  %x.i16 = zext i8 %x to i16
  store i16 %x.i16, ptr %r, align 2
  %iv.next = add i64 %iv, 2
  %ec = icmp eq i64 %iv.next, 100
  br i1 %ec, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}